From 4ee9c09c5132d45c88d0bf4fd8dcfecb67eec3fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Boutillier?= Date: Sun, 26 Apr 2015 09:18:37 +0200 Subject: [PATCH] Imported Upstream version 7.10.0 --- .gitattributes | 1 + .gitignore | 60 +- .pkgr.yml | 11 + .rubocop.yml | 1006 +++++++++++++++++ .ruby-version | 1 + .travis.yml | 36 - CHANGELOG | 510 +++++++++ CONTRIBUTING.md | 88 +- GITLAB_SHELL_VERSION | 2 +- Gemfile | 79 +- Gemfile.lock | 407 ++++--- Guardfile | 4 +- LICENSE | 2 +- MAINTENANCE.md | 2 +- PROCESS.md | 17 +- Procfile | 2 +- README.md | 122 +- VERSION | 2 +- .../images/authbuttons/bitbucket_64.png | Bin 0 -> 2163 bytes app/assets/images/authbuttons/github_32.png | Bin 1902 -> 0 bytes app/assets/images/authbuttons/github_64.png | Bin 4444 -> 4196 bytes app/assets/images/authbuttons/gitlab_64.png | Bin 0 -> 6559 bytes app/assets/images/authbuttons/google_32.png | Bin 1611 -> 0 bytes app/assets/images/authbuttons/google_64.png | Bin 3437 -> 3169 bytes app/assets/images/authbuttons/twitter_32.png | Bin 1417 -> 0 bytes app/assets/images/authbuttons/twitter_64.png | Bin 3328 -> 3054 bytes app/assets/images/bg-header.png | Bin 210 -> 90 bytes app/assets/images/bg_fallback.png | Bin 2976 -> 167 bytes app/assets/images/brand_logo.png | Bin 32119 -> 27059 bytes app/assets/images/chosen-sprite.png | Bin 396 -> 367 bytes app/assets/images/dark-scheme-preview.png | Bin 9873 -> 3996 bytes app/assets/images/diff_note_add.png | Bin 691 -> 418 bytes app/assets/images/gitorious-logo-black.png | Bin 0 -> 809 bytes app/assets/images/gitorious-logo-blue.png | Bin 0 -> 495 bytes app/assets/images/icon-link.png | Bin 1019 -> 726 bytes app/assets/images/icon-search.png | Bin 331 -> 222 bytes app/assets/images/icon_sprite.png | Bin 2782 -> 2636 bytes app/assets/images/images.png | Bin 6644 -> 5849 bytes app/assets/images/logo-black.png | Bin 2797 -> 0 bytes app/assets/images/logo-white.png | Bin 7501 -> 7699 bytes app/assets/images/monokai-scheme-preview.png | Bin 4332 -> 3711 bytes app/assets/images/move.png | Bin 260 -> 197 bytes app/assets/images/no_avatar.png | Bin 704 -> 621 bytes app/assets/images/no_group_avatar.png | Bin 4884 -> 942 bytes app/assets/images/slider_handles.png | Bin 4122 -> 1377 bytes .../images/solarized-dark-scheme-preview.png | Bin 9902 -> 3195 bytes .../images/solarized-light-scheme-preview.png | Bin 0 -> 3095 bytes app/assets/images/switch_icon.png | Bin 1197 -> 231 bytes app/assets/images/trans_bg.gif | Bin 50 -> 49 bytes app/assets/images/white-scheme-preview.png | Bin 10022 -> 3751 bytes app/assets/javascripts/activities.js.coffee | 6 +- app/assets/javascripts/admin.js.coffee | 8 +- app/assets/javascripts/api.js.coffee | 64 +- app/assets/javascripts/application.js.coffee | 92 +- app/assets/javascripts/aside.js.coffee | 17 + app/assets/javascripts/autosave.js.coffee | 39 + .../javascripts/behaviors/taskable.js.coffee | 21 + .../behaviors/toggler_behavior.coffee | 4 +- .../javascripts/{ => blob}/blob.js.coffee | 7 +- .../javascripts/blob/edit_blob.js.coffee | 44 + .../javascripts/blob/new_blob.js.coffee | 21 + app/assets/javascripts/branch-graph.js.coffee | 47 +- app/assets/javascripts/calendar.js.coffee | 38 + app/assets/javascripts/chart.js.coffee | 21 - app/assets/javascripts/commit.js.coffee | 4 +- app/assets/javascripts/commit/file.js.coffee | 4 +- .../javascripts/commit/image-file.js.coffee | 4 +- app/assets/javascripts/commits.js.coffee | 4 +- .../confirm_danger_modal.js.coffee | 18 + app/assets/javascripts/dashboard.js.coffee | 34 +- app/assets/javascripts/diff.js.coffee | 6 +- app/assets/javascripts/dispatcher.js.coffee | 115 +- .../javascripts/dropzone_input.js.coffee | 243 ++++ app/assets/javascripts/flash.js.coffee | 5 +- app/assets/javascripts/group_avatar.js.coffee | 9 + app/assets/javascripts/groups.js.coffee | 17 +- .../javascripts/groups_select.js.coffee | 41 + .../javascripts/importer_status.js.coffee | 35 + .../javascripts/issuable_form.js.coffee | 28 + app/assets/javascripts/issue.js.coffee | 23 +- app/assets/javascripts/issues.js.coffee | 46 +- app/assets/javascripts/labels.js.coffee | 4 +- .../javascripts/markdown_area.js.coffee | 196 ---- .../javascripts/merge_request.js.coffee | 69 +- .../javascripts/merge_requests.js.coffee | 37 +- app/assets/javascripts/milestone.js.coffee | 11 +- .../javascripts/namespace_select.js.coffee | 43 +- app/assets/javascripts/network.js.coffee | 6 +- app/assets/javascripts/notes.js.coffee | 204 ++-- app/assets/javascripts/notes_votes.js.coffee | 22 - app/assets/javascripts/profile.js.coffee | 45 +- app/assets/javascripts/project.js.coffee | 71 +- .../javascripts/project_avatar.js.coffee | 9 + app/assets/javascripts/project_fork.js.coffee | 5 + .../javascripts/project_import.js.coffee | 4 +- .../javascripts/project_members.js.coffee | 4 + app/assets/javascripts/project_new.js.coffee | 11 + app/assets/javascripts/project_show.js.coffee | 15 + .../project_users_select.js.coffee | 59 - .../javascripts/projects_list.js.coffee | 24 + .../javascripts/protected_branches.js.coffee | 21 + .../javascripts/search_autocomplete.js.coffee | 4 +- app/assets/javascripts/shortcuts.js.coffee | 25 +- .../shortcuts_dashboard_navigation.js.coffee | 14 + .../javascripts/shortcuts_issueable.coffee | 19 + .../javascripts/shortcuts_navigation.coffee | 20 + .../javascripts/shortcuts_network.js.coffee | 12 + app/assets/javascripts/sidebar.js.coffee | 33 +- app/assets/javascripts/stat_graph.js.coffee | 2 +- .../stat_graph_contributors.js.coffee | 17 +- .../stat_graph_contributors_graph.js.coffee | 10 +- .../stat_graph_contributors_util.js.coffee | 2 +- app/assets/javascripts/subscription.js.coffee | 17 + app/assets/javascripts/team_members.js.coffee | 6 - app/assets/javascripts/tree.js.coffee | 4 +- app/assets/javascripts/user.js.coffee | 4 + app/assets/javascripts/users_select.js.coffee | 122 +- app/assets/javascripts/wikis.js.coffee | 5 +- app/assets/javascripts/zen_mode.js.coffee | 67 ++ app/assets/stylesheets/application.scss | 18 +- .../stylesheets/{ => base}/gl_bootstrap.scss | 169 +-- app/assets/stylesheets/base/gl_variables.scss | 133 +++ .../stylesheets/{main => base}/layout.scss | 10 +- .../stylesheets/{main => base}/mixins.scss | 35 +- app/assets/stylesheets/base/variables.scss | 34 + app/assets/stylesheets/behaviors.scss | 26 +- app/assets/stylesheets/generic/avatar.scss | 29 +- app/assets/stylesheets/generic/buttons.scss | 150 +-- app/assets/stylesheets/generic/calendar.scss | 90 ++ app/assets/stylesheets/generic/common.scss | 104 +- app/assets/stylesheets/generic/files.scss | 40 +- app/assets/stylesheets/generic/filters.scss | 55 + app/assets/stylesheets/generic/flash.scss | 16 +- app/assets/stylesheets/generic/forms.scss | 24 +- app/assets/stylesheets/generic/gfm.scss | 21 + app/assets/stylesheets/generic/highlight.scss | 38 +- app/assets/stylesheets/generic/issue_box.scss | 106 +- app/assets/stylesheets/generic/jquery.scss | 4 +- app/assets/stylesheets/generic/lists.scss | 20 +- .../stylesheets/generic/markdown_area.scss | 35 + app/assets/stylesheets/generic/mobile.scss | 69 ++ .../stylesheets/generic/nav_sidebar.scss | 193 ++++ app/assets/stylesheets/generic/selects.scss | 87 +- app/assets/stylesheets/generic/sidebar.scss | 46 - app/assets/stylesheets/generic/tables.scss | 20 + app/assets/stylesheets/generic/timeline.scss | 134 +++ .../stylesheets/generic/typography.scss | 77 +- app/assets/stylesheets/generic/zen.scss | 98 ++ app/assets/stylesheets/highlight/dark.scss | 267 ++--- app/assets/stylesheets/highlight/monokai.scss | 214 ++-- .../stylesheets/highlight/solarized_dark.scss | 213 ++-- .../highlight/solarized_light.scss | 110 ++ app/assets/stylesheets/highlight/white.scss | 253 ++--- app/assets/stylesheets/main/fonts.scss | 3 - app/assets/stylesheets/main/variables.scss | 48 - .../{sections => pages}/admin.scss | 11 + app/assets/stylesheets/pages/commit.scss | 123 ++ app/assets/stylesheets/pages/commits.scss | 118 ++ .../{sections => pages}/dashboard.scss | 66 +- .../stylesheets/{sections => pages}/diff.scss | 75 +- .../{sections => pages}/editor.scss | 24 +- .../{sections => pages}/errors.scss | 0 .../{sections => pages}/events.scss | 93 +- .../{sections => pages}/explore.scss | 0 .../{sections => pages}/graph.scss | 4 +- .../{sections => pages}/groups.scss | 1 - .../{sections => pages}/header.scss | 119 +- app/assets/stylesheets/pages/help.scss | 70 ++ app/assets/stylesheets/pages/import.scss | 18 + app/assets/stylesheets/pages/issuable.scss | 47 + .../{sections => pages}/issues.scss | 70 +- .../{sections => pages}/labels.scss | 0 app/assets/stylesheets/pages/login.scss | 124 ++ .../stylesheets/pages/merge_requests.scss | 191 ++++ app/assets/stylesheets/pages/milestone.scss | 9 + app/assets/stylesheets/pages/note_form.scss | 175 +++ app/assets/stylesheets/pages/notes.scss | 206 ++++ .../{sections => pages}/notifications.scss | 6 +- .../{sections => pages}/profile.scss | 50 +- .../{sections => pages}/projects.scss | 179 ++- app/assets/stylesheets/pages/search.scss | 7 + .../{sections => pages}/snippets.scss | 0 .../{sections => pages}/stat_graph.scss | 0 .../{sections => pages}/themes.scss | 0 .../stylesheets/{sections => pages}/tree.scss | 44 +- app/assets/stylesheets/pages/ui_dev_kit.scss | 9 + app/assets/stylesheets/pages/votes.scss | 4 + .../stylesheets/{sections => pages}/wiki.scss | 0 app/assets/stylesheets/print.scss | 4 + app/assets/stylesheets/sections/commits.scss | 250 ---- app/assets/stylesheets/sections/help.scss | 19 - app/assets/stylesheets/sections/login.scss | 67 -- .../stylesheets/sections/merge_requests.scss | 124 -- .../stylesheets/sections/milestone.scss | 3 - app/assets/stylesheets/sections/nav.scss | 132 --- app/assets/stylesheets/sections/notes.scss | 364 ------ app/assets/stylesheets/sections/votes.scss | 49 - app/assets/stylesheets/themes/dark-theme.scss | 63 ++ app/assets/stylesheets/themes/ui_basic.scss | 19 +- app/assets/stylesheets/themes/ui_blue.scss | 6 + app/assets/stylesheets/themes/ui_color.scss | 41 +- app/assets/stylesheets/themes/ui_gray.scss | 31 +- app/assets/stylesheets/themes/ui_mars.scss | 37 +- app/assets/stylesheets/themes/ui_modern.scss | 41 +- .../admin/application_settings_controller.rb | 45 + .../admin/applications_controller.rb | 52 + .../admin/background_jobs_controller.rb | 2 +- app/controllers/admin/dashboard_controller.rb | 6 +- .../admin/deploy_keys_controller.rb | 49 + app/controllers/admin/groups_controller.rb | 17 +- app/controllers/admin/keys_controller.rb | 34 + app/controllers/admin/projects_controller.rb | 21 +- app/controllers/admin/services_controller.rb | 54 + app/controllers/admin/users_controller.rb | 19 +- app/controllers/application_controller.rb | 173 ++- app/controllers/autocomplete_controller.rb | 30 + app/controllers/confirmations_controller.rb | 17 + .../dashboard/groups_controller.rb | 5 + .../dashboard/milestones_controller.rb | 34 + .../dashboard/projects_controller.rb | 27 + app/controllers/dashboard_controller.rb | 69 +- app/controllers/explore/groups_controller.rb | 5 +- .../explore/projects_controller.rb | 14 +- app/controllers/files_controller.rb | 16 - .../groups/application_controller.rb | 28 + .../groups/group_members_controller.rb | 84 ++ .../groups/milestones_controller.rb | 14 +- app/controllers/groups_controller.rb | 73 +- app/controllers/help_controller.rb | 78 +- app/controllers/import/base_controller.rb | 19 + .../import/bitbucket_controller.rb | 82 ++ app/controllers/import/github_controller.rb | 68 ++ app/controllers/import/gitlab_controller.rb | 65 ++ .../import/gitorious_controller.rb | 43 + .../import/google_code_controller.rb | 116 ++ app/controllers/invites_controller.rb | 83 ++ app/controllers/namespaces_controller.rb | 19 +- .../oauth/applications_controller.rb | 39 + .../oauth/authorizations_controller.rb | 57 + .../authorized_applications_controller.rb | 8 + .../omniauth_callbacks_controller.rb | 56 +- app/controllers/passwords_controller.rb | 4 +- .../profiles/accounts_controller.rb | 6 + app/controllers/profiles/emails_controller.rb | 5 + app/controllers/profiles/groups_controller.rb | 23 - app/controllers/profiles/keys_controller.rb | 2 +- .../profiles/notifications_controller.rb | 38 +- .../profiles/passwords_controller.rb | 8 +- app/controllers/profiles_controller.rb | 18 +- .../projects/application_controller.rb | 8 +- .../projects/avatars_controller.rb | 29 + .../projects/base_tree_controller.rb | 8 - app/controllers/projects/blame_controller.rb | 9 +- app/controllers/projects/blob_controller.rb | 127 ++- .../projects/branches_controller.rb | 30 +- app/controllers/projects/commit_controller.rb | 34 +- .../projects/commits_controller.rb | 9 +- .../projects/compare_controller.rb | 6 +- .../projects/deploy_keys_controller.rb | 36 +- .../projects/edit_tree_controller.rb | 59 - app/controllers/projects/forks_controller.rb | 25 + app/controllers/projects/graphs_controller.rb | 28 +- app/controllers/projects/hooks_controller.rb | 5 +- .../projects/imports_controller.rb | 51 + app/controllers/projects/issues_controller.rb | 47 +- app/controllers/projects/labels_controller.rb | 20 +- .../projects/merge_requests_controller.rb | 80 +- .../projects/milestones_controller.rb | 16 +- .../projects/network_controller.rb | 5 +- .../projects/new_tree_controller.rb | 20 - app/controllers/projects/notes_controller.rb | 24 +- .../projects/project_members_controller.rb | 98 ++ .../projects/protected_branches_controller.rb | 25 +- app/controllers/projects/raw_controller.rb | 6 +- app/controllers/projects/refs_controller.rb | 34 +- .../projects/repositories_controller.rb | 23 +- .../projects/services_controller.rb | 27 +- .../projects/snippets_controller.rb | 36 +- app/controllers/projects/tags_controller.rb | 33 +- .../projects/team_members_controller.rb | 74 -- app/controllers/projects/tree_controller.rb | 14 +- .../projects/uploads_controller.rb | 56 + app/controllers/projects/wikis_controller.rb | 45 +- app/controllers/projects_controller.rb | 178 ++- app/controllers/registrations_controller.rb | 12 +- app/controllers/search_controller.rb | 49 +- app/controllers/sessions_controller.rb | 34 +- app/controllers/snippets_controller.rb | 65 +- app/controllers/uploads_controller.rb | 71 ++ app/controllers/users_controller.rb | 90 +- app/controllers/users_groups_controller.rb | 48 - app/finders/README.md | 8 +- .../{base_finder.rb => issuable_finder.rb} | 29 +- app/finders/issues_finder.rb | 2 +- app/finders/merge_requests_finder.rb | 2 +- app/finders/notes_finder.rb | 27 +- app/finders/projects_finder.rb | 8 +- app/finders/snippets_finder.rb | 63 ++ app/finders/trending_projects_finder.rb | 2 +- app/helpers/appearances_helper.rb | 4 + app/helpers/application_helper.rb | 220 ++-- app/helpers/application_settings_helper.rb | 38 + app/helpers/blob_helper.rb | 76 +- app/helpers/branches_helper.rb | 9 +- app/helpers/commits_helper.rb | 218 ++-- app/helpers/compare_helper.rb | 11 +- app/helpers/dashboard_helper.rb | 75 +- app/helpers/diff_helper.rb | 145 ++- app/helpers/emails_helper.rb | 38 + app/helpers/events_helper.rb | 146 ++- app/helpers/explore_helper.rb | 17 + app/helpers/external_wiki_helper.rb | 11 + app/helpers/git_helper.rb | 5 + app/helpers/gitlab_markdown_helper.rb | 135 ++- app/helpers/gitlab_routing_helper.rb | 55 + app/helpers/graph_helper.rb | 4 +- app/helpers/groups_helper.rb | 34 +- app/helpers/icons_helper.rb | 72 +- app/helpers/issues_helper.rb | 84 +- app/helpers/labels_helper.rb | 25 +- app/helpers/merge_requests_helper.rb | 25 +- app/helpers/milestones_helper.rb | 33 + app/helpers/namespaces_helper.rb | 8 + app/helpers/nav_helper.rb | 5 + app/helpers/notes_helper.rb | 38 +- app/helpers/notifications_helper.rb | 8 +- app/helpers/oauth_helper.rb | 21 +- app/helpers/profile_helper.rb | 10 +- app/helpers/projects_helper.rb | 215 ++-- app/helpers/search_helper.rb | 48 +- app/helpers/selects_helper.rb | 34 +- app/helpers/snippets_helper.rb | 3 +- app/helpers/sorting_helper.rb | 96 ++ app/helpers/submodule_helper.rb | 55 +- app/helpers/tab_helper.rb | 61 +- app/helpers/tags_helper.rb | 6 +- app/helpers/tree_helper.rb | 53 +- app/helpers/visibility_level_helper.rb | 22 +- app/helpers/wiki_helper.rb | 22 + app/mailers/emails/groups.rb | 49 +- app/mailers/emails/issues.rb | 8 +- app/mailers/emails/merge_requests.rb | 22 +- app/mailers/emails/notes.rb | 13 +- app/mailers/emails/profile.rb | 15 +- app/mailers/emails/projects.rb | 150 ++- app/mailers/notify.rb | 63 +- app/models/ability.rb | 72 +- app/models/application_setting.rb | 61 + app/models/broadcast_message.rb | 2 + app/models/commit.rb | 78 +- app/models/concerns/issuable.rb | 69 +- app/models/concerns/mentionable.rb | 44 +- app/models/concerns/notifiable.rb | 2 +- app/models/concerns/sortable.rb | 35 + app/models/concerns/taskable.rb | 51 + app/models/deploy_key.rb | 18 + app/models/deploy_keys_project.rb | 10 + app/models/email.rb | 2 + app/models/event.rb | 141 +-- app/models/external_issue.rb | 25 + app/models/group.rb | 62 +- app/models/group_milestone.rb | 6 +- app/models/{ => hooks}/project_hook.rb | 0 app/models/{ => hooks}/service_hook.rb | 0 app/models/{ => hooks}/system_hook.rb | 0 app/models/{ => hooks}/web_hook.rb | 16 +- app/models/identity.rb | 19 + app/models/issue.rb | 13 +- app/models/key.rb | 31 +- app/models/label.rb | 16 +- app/models/label_link.rb | 12 + app/models/member.rb | 172 +++ app/models/members/group_member.rb | 75 ++ app/models/members/project_member.rb | 165 +++ app/models/merge_request.rb | 72 +- app/models/merge_request_diff.rb | 6 +- app/models/milestone.rb | 1 + app/models/namespace.rb | 61 +- app/models/network/graph.rb | 12 +- app/models/note.rb | 323 +++++- app/models/notification.rb | 10 +- app/models/personal_snippet.rb | 22 +- app/models/project.rb | 332 ++++-- app/models/project_import_data.rb | 19 + app/models/project_services/asana_service.rb | 127 +++ .../project_services/assembla_service.rb | 38 +- app/models/project_services/bamboo_service.rb | 137 +++ .../project_services/buildkite_service.rb | 135 +++ .../project_services/campfire_service.rb | 44 +- app/models/project_services/ci_service.rb | 27 +- .../custom_issue_tracker_service.rb | 57 + .../emails_on_push_service.rb | 56 +- .../project_services/external_wiki_service.rb | 48 + .../project_services/flowdock_service.rb | 44 +- .../project_services/gemnasium_service.rb | 44 +- .../project_services/gitlab_ci_service.rb | 94 +- .../gitlab_issue_tracker_service.rb | 62 + .../project_services/hipchat_service.rb | 211 +++- app/models/project_services/irker_service.rb | 163 +++ .../project_services/issue_tracker_service.rb | 125 ++ app/models/project_services/jira_service.rb | 58 + .../pivotaltracker_service.rb | 38 +- .../project_services/pushover_service.rb | 125 ++ .../project_services/redmine_service.rb | 44 + app/models/project_services/slack_message.rb | 110 -- app/models/project_services/slack_service.rb | 91 +- .../slack_service/base_message.rb | 31 + .../slack_service/issue_message.rb | 56 + .../slack_service/merge_message.rb | 60 + .../slack_service/note_message.rb | 82 ++ .../slack_service/push_message.rb | 110 ++ .../project_services/teamcity_service.rb | 145 +++ app/models/project_snippet.rb | 22 +- app/models/project_team.rb | 92 +- app/models/project_wiki.rb | 23 +- app/models/protected_branch.rb | 11 +- app/models/repository.rb | 194 +++- app/models/service.rb | 115 +- app/models/snippet.rb | 65 +- app/models/subscription.rb | 21 + app/models/tree.rb | 56 +- app/models/user.rb | 304 +++-- app/models/users_group.rb | 61 - app/models/users_project.rb | 152 --- app/models/users_star_project.rb | 10 +- app/models/wiki_page.rb | 9 +- app/services/archive_repository_service.rb | 64 +- app/services/base_service.rb | 45 +- app/services/compare_service.rb | 4 +- app/services/create_branch_service.rb | 39 +- app/services/create_snippet_service.rb | 20 + app/services/create_tag_service.rb | 44 +- app/services/delete_branch_service.rb | 44 +- app/services/delete_tag_service.rb | 42 + app/services/event_create_service.rb | 52 +- app/services/files/base_service.rb | 14 - app/services/files/create_service.rb | 32 +- app/services/files/delete_service.rb | 6 +- app/services/files/update_service.rb | 23 +- app/services/git_push_service.rb | 175 +-- app/services/git_tag_push_service.rb | 49 +- app/services/gravatar_service.rb | 4 +- app/services/issuable_base_service.rb | 18 + app/services/issues/base_service.rb | 22 +- app/services/issues/bulk_update_service.rb | 35 +- app/services/issues/close_service.rb | 2 +- app/services/issues/update_service.rb | 19 +- .../merge_requests/auto_merge_service.rb | 7 +- .../merge_requests/base_merge_service.rb | 12 +- app/services/merge_requests/base_service.rb | 26 +- app/services/merge_requests/build_service.rb | 16 +- app/services/merge_requests/close_service.rb | 4 +- app/services/merge_requests/merge_service.rb | 7 +- .../merge_requests/refresh_service.rb | 94 ++ app/services/merge_requests/reopen_service.rb | 4 +- app/services/merge_requests/update_service.rb | 25 +- app/services/notes/create_service.rb | 12 + app/services/notes/update_service.rb | 25 + app/services/notification_service.rb | 210 ++-- .../oauth2/access_token_validation_service.rb | 41 + app/services/projects/autocomplete_service.rb | 15 + app/services/projects/create_service.rb | 81 +- app/services/projects/fork_service.rb | 46 +- app/services/projects/image_service.rb | 39 - app/services/projects/participants_service.rb | 41 +- app/services/projects/transfer_service.rb | 5 +- app/services/projects/update_service.rb | 9 +- app/services/projects/upload_service.rb | 28 + app/services/search/global_service.rb | 20 +- app/services/search/project_service.rb | 36 +- app/services/search/snippet_service.rb | 14 + app/services/system_hooks_service.rb | 45 +- app/services/test_hook_service.rb | 5 +- app/services/update_snippet_service.rb | 22 + app/uploaders/attachment_uploader.rb | 10 - app/uploaders/avatar_uploader.rb | 32 + app/uploaders/file_uploader.rb | 43 +- .../application_settings/_form.html.haml | 69 ++ .../admin/application_settings/show.html.haml | 3 + .../admin/applications/_delete_form.html.haml | 4 + app/views/admin/applications/_form.html.haml | 26 + app/views/admin/applications/edit.html.haml | 3 + app/views/admin/applications/index.html.haml | 22 + app/views/admin/applications/new.html.haml | 3 + app/views/admin/applications/show.html.haml | 26 + .../admin/background_jobs/show.html.haml | 12 +- .../admin/broadcast_messages/index.html.haml | 12 +- app/views/admin/dashboard/index.html.haml | 128 +-- app/views/admin/deploy_keys/index.html.haml | 27 + app/views/admin/deploy_keys/new.html.haml | 26 + app/views/admin/deploy_keys/show.html.haml | 34 + app/views/admin/groups/_form.html.haml | 38 +- app/views/admin/groups/index.html.haml | 29 +- app/views/admin/groups/show.html.haml | 31 +- app/views/admin/hooks/index.html.haml | 4 +- app/views/admin/keys/show.html.haml | 1 + app/views/admin/logs/show.html.haml | 87 +- app/views/admin/projects/index.html.haml | 62 +- app/views/admin/projects/show.html.haml | 54 +- app/views/admin/services/_form.html.haml | 94 ++ app/views/admin/services/edit.html.haml | 1 + app/views/admin/services/index.html.haml | 22 + app/views/admin/users/index.html.haml | 51 +- app/views/admin/users/show.html.haml | 137 ++- app/views/dashboard/_activities.html.haml | 7 +- app/views/dashboard/_groups.html.haml | 20 - app/views/dashboard/_project.html.haml | 12 - app/views/dashboard/_projects.html.haml | 29 +- .../dashboard/_projects_filter.html.haml | 55 - app/views/dashboard/_sidebar.html.haml | 26 +- .../_zero_authorized_projects.html.haml | 27 +- app/views/dashboard/groups/index.html.haml | 40 + app/views/dashboard/issues.atom.builder | 19 +- app/views/dashboard/issues.html.haml | 10 +- app/views/dashboard/merge_requests.html.haml | 10 +- .../dashboard/milestones/_issue.html.haml | 10 + .../dashboard/milestones/_issues.html.haml | 6 + .../milestones/_merge_request.html.haml | 10 + .../milestones/_merge_requests.html.haml | 6 + .../dashboard/milestones/_milestone.html.haml | 20 + .../dashboard/milestones/index.html.haml | 20 + app/views/dashboard/milestones/show.html.haml | 81 ++ app/views/dashboard/projects.html.haml | 73 -- .../dashboard/projects/starred.html.haml | 23 + app/views/dashboard/show.atom.builder | 23 +- app/views/dashboard/show.html.haml | 9 +- app/views/devise/confirmations/new.html.haml | 15 +- .../mailer/confirmation_instructions.html.erb | 2 +- .../reset_password_instructions.html.erb | 2 +- .../mailer/unlock_instructions.html.erb | 2 +- app/views/devise/passwords/edit.html.haml | 21 +- app/views/devise/passwords/new.html.haml | 15 +- app/views/devise/registrations/edit.html.erb | 4 +- app/views/devise/registrations/new.html.haml | 30 +- app/views/devise/sessions/_new_base.html.haml | 10 +- app/views/devise/sessions/_new_ldap.html.haml | 7 +- .../sessions/_oauth_providers.html.haml | 11 - app/views/devise/sessions/new.html.haml | 54 +- .../devise/shared/_omniauth_box.html.haml | 10 + app/views/devise/shared/_signin_box.html.haml | 26 + app/views/devise/shared/_signup_box.html.haml | 27 + .../applications/_delete_form.html.haml | 4 + .../doorkeeper/applications/_form.html.haml | 24 + .../doorkeeper/applications/edit.html.haml | 2 + .../doorkeeper/applications/index.html.haml | 16 + .../doorkeeper/applications/new.html.haml | 2 + .../doorkeeper/applications/show.html.haml | 26 + .../doorkeeper/authorizations/error.html.haml | 3 + .../doorkeeper/authorizations/new.html.haml | 28 + .../doorkeeper/authorizations/show.html.haml | 3 + .../_delete_form.html.haml | 4 + .../authorized_applications/index.html.haml | 16 + app/views/events/_commit.html.haml | 2 +- app/views/events/_event.html.haml | 9 +- app/views/events/_event_issue.atom.haml | 5 +- app/views/events/_event_last_push.html.haml | 4 +- .../events/_event_merge_request.atom.haml | 3 +- app/views/events/_event_note.atom.haml | 4 +- app/views/events/_event_push.atom.haml | 4 +- app/views/events/_events.html.haml | 2 +- app/views/events/event/_common.html.haml | 12 +- .../events/event/_created_project.html.haml | 27 + app/views/events/event/_note.html.haml | 16 +- app/views/events/event/_push.html.haml | 14 +- app/views/explore/groups/index.html.haml | 29 +- app/views/explore/projects/_filter.html.haml | 67 ++ app/views/explore/projects/_project.html.haml | 26 +- app/views/explore/projects/index.html.haml | 29 +- app/views/explore/projects/starred.html.haml | 2 +- app/views/explore/projects/trending.html.haml | 2 +- app/views/groups/_filter.html.haml | 12 - app/views/groups/_new_group_member.html.haml | 11 - app/views/groups/_projects.html.haml | 29 +- app/views/groups/_settings_nav.html.haml | 17 +- app/views/groups/edit.html.haml | 85 +- .../group_members/_group_member.html.haml | 54 + .../group_members/_new_group_member.html.haml | 18 + .../index.html.haml} | 14 +- .../group_members}/update.js.haml | 0 app/views/groups/issues.atom.builder | 13 +- app/views/groups/issues.html.haml | 10 +- app/views/groups/merge_requests.html.haml | 10 +- app/views/groups/milestones/_issue.html.haml | 6 +- .../milestones/_merge_request.html.haml | 6 +- .../groups/milestones/_milestone.html.haml | 25 + app/views/groups/milestones/index.html.haml | 50 +- app/views/groups/milestones/show.html.haml | 75 +- app/views/groups/new.html.haml | 25 +- app/views/groups/projects.html.haml | 52 +- app/views/groups/show.atom.builder | 22 +- app/views/groups/show.html.haml | 53 +- app/views/help/_shortcuts.html.haml | 229 +++- app/views/help/index.html.haml | 14 +- app/views/help/show.html.haml | 2 +- app/views/help/ui.html.haml | 227 ++++ app/views/import/base/create.js.haml | 25 + app/views/import/bitbucket/status.html.haml | 45 + app/views/import/github/status.html.haml | 45 + app/views/import/gitlab/status.html.haml | 45 + app/views/import/gitorious/status.html.haml | 45 + app/views/import/google_code/new.html.haml | 60 + .../import/google_code/new_user_map.html.haml | 42 + app/views/import/google_code/status.html.haml | 49 + app/views/invites/show.html.haml | 29 + app/views/layouts/_broadcast.html.haml | 2 +- app/views/layouts/_collapse_button.html.haml | 4 + app/views/layouts/_head.html.haml | 15 +- app/views/layouts/_head_panel.html.haml | 34 +- .../layouts/_init_auto_complete.html.haml | 2 +- app/views/layouts/_page.html.haml | 23 + .../layouts/_public_head_panel.html.haml | 22 +- app/views/layouts/_search.html.haml | 22 +- app/views/layouts/admin.html.haml | 13 +- app/views/layouts/application.html.haml | 12 +- app/views/layouts/devise.html.haml | 51 +- app/views/layouts/errors.html.haml | 4 +- app/views/layouts/explore.html.haml | 6 +- app/views/layouts/group.html.haml | 12 +- app/views/layouts/nav/_admin.html.haml | 64 +- app/views/layouts/nav/_dashboard.html.haml | 48 +- app/views/layouts/nav/_group.html.haml | 55 +- app/views/layouts/nav/_profile.html.haml | 60 +- app/views/layouts/nav/_project.html.haml | 125 +- app/views/layouts/navless.html.haml | 7 +- app/views/layouts/notify.html.haml | 18 +- app/views/layouts/profile.html.haml | 12 +- app/views/layouts/project_settings.html.haml | 19 +- app/views/layouts/projects.html.haml | 14 +- app/views/layouts/public_group.html.haml | 10 +- app/views/layouts/public_projects.html.haml | 8 +- app/views/layouts/public_users.html.haml | 8 +- app/views/layouts/search.html.haml | 7 +- app/views/layouts/user_team.html.haml | 12 - app/views/notify/_note_message.html.haml | 2 +- .../_reassigned_issuable_email.html.haml | 10 + .../_reassigned_issuable_email.text.erb | 6 + app/views/notify/closed_issue_email.text.haml | 2 +- .../closed_merge_request_email.text.haml | 2 +- .../group_access_granted_email.html.haml | 2 +- .../group_access_granted_email.text.erb | 2 +- .../group_invite_accepted_email.html.haml | 6 + .../group_invite_accepted_email.text.erb | 3 + .../group_invite_declined_email.html.haml | 5 + .../group_invite_declined_email.text.erb | 3 + .../group_member_invited_email.html.haml | 14 + .../group_member_invited_email.text.erb | 4 + .../issue_status_changed_email.text.erb | 2 +- .../merge_request_status_email.text.haml | 2 +- .../merged_merge_request_email.text.haml | 2 +- app/views/notify/new_issue_email.html.haml | 2 +- app/views/notify/new_issue_email.text.erb | 2 +- .../notify/new_merge_request_email.html.haml | 2 +- .../notify/new_merge_request_email.text.erb | 2 +- app/views/notify/new_ssh_key_email.html.haml | 2 +- app/views/notify/new_ssh_key_email.text.erb | 4 +- app/views/notify/note_commit_email.text.erb | 2 +- app/views/notify/note_issue_email.text.erb | 2 +- .../notify/note_merge_request_email.text.erb | 2 +- .../project_access_granted_email.html.haml | 4 +- .../project_access_granted_email.text.erb | 4 +- .../project_invite_accepted_email.html.haml | 6 + .../project_invite_accepted_email.text.erb | 3 + .../project_invite_declined_email.html.haml | 5 + .../project_invite_declined_email.text.erb | 3 + .../project_member_invited_email.html.haml | 13 + .../project_member_invited_email.text.erb | 4 + .../notify/project_was_moved_email.html.haml | 8 +- .../notify/project_was_moved_email.text.erb | 2 +- .../notify/reassigned_issue_email.html.haml | 12 +- .../notify/reassigned_issue_email.text.erb | 6 +- .../reassigned_merge_request_email.html.haml | 8 +- .../reassigned_merge_request_email.text.erb | 8 +- .../notify/repository_push_email.html.haml | 86 +- .../notify/repository_push_email.text.haml | 72 +- app/views/profiles/accounts/show.html.haml | 32 +- app/views/profiles/applications.html.haml | 49 + app/views/profiles/design.html.haml | 9 +- app/views/profiles/emails/index.html.haml | 16 +- app/views/profiles/groups/index.html.haml | 39 - app/views/profiles/history.html.haml | 4 +- app/views/profiles/keys/_key.html.haml | 21 +- .../profiles/keys/_key_details.html.haml | 22 + app/views/profiles/keys/_key_table.html.haml | 19 + app/views/profiles/keys/index.html.haml | 18 +- app/views/profiles/keys/new.html.haml | 8 +- app/views/profiles/keys/show.html.haml | 23 +- .../notifications/_settings.html.haml | 4 +- .../profiles/notifications/show.html.haml | 93 +- app/views/profiles/passwords/edit.html.haml | 29 +- app/views/profiles/passwords/new.html.haml | 9 +- app/views/profiles/show.html.haml | 34 +- app/views/profiles/update.js.erb | 4 +- .../_bitbucket_import_modal.html.haml | 13 + app/views/projects/_commit_button.html.haml | 6 + app/views/projects/_dropdown.html.haml | 28 +- .../projects/_github_import_modal.html.haml | 13 + .../projects/_gitlab_import_modal.html.haml | 13 + app/views/projects/_home_panel.html.haml | 73 +- app/views/projects/_issuable_form.html.haml | 87 ++ app/views/projects/_md_preview.html.haml | 13 + app/views/projects/_settings_nav.html.haml | 46 +- .../projects/_visibility_level.html.haml | 2 +- app/views/projects/_zen.html.haml | 10 + app/views/projects/blame/show.html.haml | 40 +- app/views/projects/blob/_actions.html.haml | 29 +- app/views/projects/blob/_blob.html.haml | 24 +- app/views/projects/blob/_download.html.haml | 4 +- app/views/projects/blob/_editor.html.haml | 25 + app/views/projects/blob/_remove.html.haml | 15 +- app/views/projects/blob/_text.html.haml | 2 +- app/views/projects/blob/diff.html.haml | 4 +- app/views/projects/blob/edit.html.haml | 31 + app/views/projects/blob/new.html.haml | 19 + app/views/projects/blob/preview.html.haml | 25 + app/views/projects/branches/_branch.html.haml | 16 +- app/views/projects/branches/destroy.js.haml | 4 +- app/views/projects/branches/index.html.haml | 16 +- app/views/projects/branches/new.html.haml | 17 +- .../projects/commit/_commit_box.html.haml | 32 +- app/views/projects/commit/branches.html.haml | 16 + app/views/projects/commit/show.html.haml | 2 +- app/views/projects/commits/_commit.html.haml | 20 +- .../projects/commits/_commit_list.html.haml | 11 + app/views/projects/commits/_commits.html.haml | 16 +- .../projects/commits/_diff_file.html.haml | 44 - .../projects/commits/_diff_warning.html.haml | 19 - app/views/projects/commits/_diffs.html.haml | 23 - app/views/projects/commits/_head.html.haml | 16 +- .../projects/commits/_inline_commit.html.haml | 4 +- .../projects/commits/_parallel_view.html.haml | 38 - .../projects/commits/_text_file.html.haml | 33 - app/views/projects/commits/show.atom.builder | 10 +- app/views/projects/commits/show.html.haml | 8 +- app/views/projects/compare/_form.html.haml | 4 +- app/views/projects/compare/show.html.haml | 16 +- app/views/projects/create.js.haml | 13 - .../deploy_keys/_deploy_key.html.haml | 36 +- .../projects/deploy_keys/_form.html.haml | 4 +- .../projects/deploy_keys/index.html.haml | 31 +- app/views/projects/deploy_keys/show.html.haml | 4 +- app/views/projects/diffs/_diffs.html.haml | 23 + app/views/projects/diffs/_file.html.haml | 50 + .../{commits => diffs}/_image.html.haml | 5 +- .../{commits => }/diffs/_match_line.html.haml | 0 .../diffs/_match_line_parallel.html.haml | 4 + .../projects/diffs/_parallel_view.html.haml | 41 + .../_stats.html.haml} | 17 +- app/views/projects/diffs/_text_file.html.haml | 36 + app/views/projects/diffs/_warning.html.haml | 19 + app/views/projects/edit.html.haml | 216 ++-- app/views/projects/edit_tree/_diff.html.haml | 13 - .../projects/edit_tree/preview.html.haml | 26 - app/views/projects/edit_tree/show.html.haml | 81 -- app/views/projects/empty.html.haml | 29 +- app/views/projects/fork.html.haml | 19 - app/views/projects/forks/error.html.haml | 20 + app/views/projects/forks/new.html.haml | 39 + app/views/projects/go_import.html.haml | 5 + app/views/projects/graphs/_head.html.haml | 5 + app/views/projects/graphs/commits.html.haml | 85 ++ app/views/projects/graphs/show.html.haml | 32 +- app/views/projects/graphs/show.js.haml | 19 - app/views/projects/hooks/index.html.haml | 6 +- app/views/projects/import.html.haml | 30 - app/views/projects/imports/new.html.haml | 21 + app/views/projects/imports/show.html.haml | 9 + .../projects/issues/_discussion.html.haml | 33 + app/views/projects/issues/_form.html.haml | 66 +- app/views/projects/issues/_head.html.haml | 36 - app/views/projects/issues/_issue.html.haml | 44 +- .../projects/issues/_issue_context.html.haml | 60 +- app/views/projects/issues/_issues.html.haml | 63 -- app/views/projects/issues/index.atom.builder | 19 +- app/views/projects/issues/index.html.haml | 28 +- app/views/projects/issues/show.html.haml | 105 +- app/views/projects/issues/update.js.haml | 11 +- app/views/projects/labels/_form.html.haml | 10 +- app/views/projects/labels/_label.html.haml | 6 +- app/views/projects/labels/destroy.js.haml | 2 + app/views/projects/labels/edit.html.haml | 2 +- app/views/projects/labels/index.html.haml | 20 +- app/views/projects/labels/new.html.haml | 2 +- .../merge_requests/_discussion.html.haml | 31 + .../projects/merge_requests/_form.html.haml | 69 +- .../projects/merge_requests/_head.html.haml | 2 +- .../merge_requests/_merge_request.html.haml | 57 +- .../merge_requests/_merge_requests.html.haml | 13 + .../merge_requests/_new_compare.html.haml | 12 +- .../merge_requests/_new_submit.html.haml | 164 +-- .../projects/merge_requests/_show.html.haml | 78 +- .../projects/merge_requests/automerge.js.haml | 3 +- .../projects/merge_requests/index.html.haml | 87 +- .../merge_requests/show/_commits.html.haml | 31 +- .../merge_requests/show/_context.html.haml | 62 +- .../merge_requests/show/_diffs.html.haml | 6 +- .../show/_how_to_merge.html.haml | 6 +- .../merge_requests/show/_mr_accept.html.haml | 60 +- .../merge_requests/show/_mr_box.html.haml | 24 +- .../merge_requests/show/_mr_ci.html.haml | 25 +- .../merge_requests/show/_mr_title.html.haml | 50 +- .../show/_participants.html.haml | 8 +- .../show/_remove_source_branch.html.haml | 6 +- .../show/_state_widget.html.haml | 33 +- .../projects/merge_requests/update.js.haml | 8 +- app/views/projects/milestones/_form.html.haml | 21 +- .../projects/milestones/_issue.html.haml | 12 +- .../milestones/_merge_request.html.haml | 9 +- .../projects/milestones/_milestone.html.haml | 33 +- app/views/projects/milestones/index.html.haml | 44 +- app/views/projects/milestones/show.html.haml | 75 +- app/views/projects/network/show.html.haml | 17 +- app/views/projects/new.html.haml | 107 +- app/views/projects/new_tree/show.html.haml | 54 - app/views/projects/no_repo.html.haml | 22 + .../projects/notes/_diff_note_link.html.haml | 10 - .../notes/_diff_notes_with_reply.html.haml | 4 +- .../_diff_notes_with_reply_parallel.html.haml | 15 +- .../projects/notes/_discussion.html.haml | 19 +- app/views/projects/notes/_edit_form.html.haml | 15 + app/views/projects/notes/_form.html.haml | 37 +- app/views/projects/notes/_note.html.haml | 129 ++- .../projects/notes/_notes_with_form.html.haml | 4 +- .../notes/discussions/_active.html.haml | 5 +- .../notes/discussions/_commit.html.haml | 5 +- .../notes/discussions/_diff.html.haml | 29 +- .../notes/discussions/_outdated.html.haml | 3 +- .../project_members/_group_members.html.haml | 16 + .../_new_project_member.html.haml | 18 + .../project_members/_project_member.html.haml | 53 + .../projects/project_members/_team.html.haml | 11 + .../import.html.haml | 8 +- .../projects/project_members/index.html.haml | 35 + .../projects/project_members/update.js.haml | 3 + .../_branches_list.html.haml | 34 + .../protected_branches/index.html.haml | 47 +- app/views/projects/refs/logs_tree.js.haml | 4 +- .../repositories/_download_archive.html.haml | 34 +- .../projects/repositories/_feed.html.haml | 4 +- .../projects/repositories/stats.html.haml | 33 - app/views/projects/services/_form.html.haml | 72 +- app/views/projects/services/index.html.haml | 27 +- app/views/projects/show.html.haml | 170 +-- app/views/projects/snippets/edit.html.haml | 2 +- app/views/projects/snippets/index.html.haml | 2 +- app/views/projects/snippets/new.html.haml | 2 +- app/views/projects/snippets/show.html.haml | 16 +- app/views/projects/tags/_tag.html.haml | 13 +- app/views/projects/tags/destroy.js.haml | 3 + app/views/projects/tags/index.html.haml | 29 +- app/views/projects/tags/new.html.haml | 22 +- .../projects/team_members/_form.html.haml | 24 - .../team_members/_group_members.html.haml | 14 - .../projects/team_members/_team.html.haml | 9 - .../team_members/_team_member.html.haml | 17 - .../projects/team_members/index.html.haml | 16 - app/views/projects/team_members/new.html.haml | 1 - .../projects/team_members/update.js.haml | 6 - app/views/projects/transfer.js.haml | 9 +- app/views/projects/tree/_blob_item.html.haml | 4 +- app/views/projects/tree/_readme.html.haml | 7 +- app/views/projects/tree/_spinner.html.haml | 2 +- .../projects/tree/_submodule_item.html.haml | 12 +- app/views/projects/tree/_tree.html.haml | 16 +- .../tree/_tree_commit_column.html.haml | 2 +- app/views/projects/tree/_tree_item.html.haml | 5 +- app/views/projects/tree/show.html.haml | 2 +- app/views/projects/update.js.haml | 2 +- app/views/projects/wikis/_form.html.haml | 21 +- .../projects/wikis/_main_links.html.haml | 6 +- app/views/projects/wikis/_nav.html.haml | 10 +- app/views/projects/wikis/_new.html.haml | 2 +- app/views/projects/wikis/edit.html.haml | 2 +- app/views/projects/wikis/history.html.haml | 15 +- app/views/projects/wikis/pages.html.haml | 4 +- app/views/projects/wikis/show.html.haml | 4 +- app/views/search/_filter.html.haml | 18 +- app/views/search/_global_filter.html.haml | 16 + app/views/search/_global_results.html.haml | 5 - app/views/search/_project_filter.html.haml | 32 + app/views/search/_project_results.html.haml | 24 - app/views/search/_results.html.haml | 33 +- app/views/search/_snippet_filter.html.haml | 13 + app/views/search/results/_blob.html.haml | 6 +- app/views/search/results/_empty.html.haml | 2 +- app/views/search/results/_issue.html.haml | 21 +- .../search/results/_merge_request.html.haml | 30 +- app/views/search/results/_note.html.haml | 35 +- app/views/search/results/_project.html.haml | 9 +- .../search/results/_snippet_blob.html.haml | 59 + .../search/results/_snippet_title.html.haml | 23 + app/views/search/results/_wiki_blob.html.haml | 9 + app/views/search/show.html.haml | 10 +- .../_choose_group_avatar_button.html.haml | 7 + app/views/shared/_clone_panel.html.haml | 21 +- .../_commit_message_container.html.haml | 17 +- app/views/shared/_confirm_modal.html.haml | 22 + app/views/shared/_event_filter.html.haml | 16 +- app/views/shared/_file_highlight.html.haml | 11 + app/views/shared/_file_hljs.html.haml | 12 - app/views/shared/_filter.html.haml | 50 - app/views/shared/_group_form.html.haml | 29 + app/views/shared/_group_tips.html.haml | 6 + app/views/shared/_issuable_filter.html.haml | 58 + .../shared/_issuable_search_form.html.haml | 9 + app/views/shared/_issues.html.haml | 2 +- app/views/shared/_merge_requests.html.haml | 2 +- app/views/shared/_milestones_filter.html.haml | 14 + app/views/shared/_no_password.html.haml | 8 + app/views/shared/_no_ssh.html.haml | 22 +- app/views/shared/_outdated_browser.html.haml | 8 + app/views/shared/_project.html.haml | 21 + app/views/shared/_project_filter.html.haml | 64 -- app/views/shared/_projects_list.html.haml | 17 + app/views/shared/_promo.html.haml | 7 +- app/views/shared/_ref_switcher.html.haml | 2 +- app/views/shared/_sort_dropdown.html.haml | 32 +- app/views/shared/snippets/_blob.html.haml | 2 +- app/views/shared/snippets/_form.html.haml | 20 +- .../snippets/_visibility_level.html.haml | 27 + app/views/snippets/_snippet.html.haml | 4 +- .../snippets/current_user_index.html.haml | 7 +- app/views/snippets/index.html.haml | 10 +- app/views/snippets/show.html.haml | 18 +- app/views/snippets/user_index.html.haml | 5 +- app/views/users/_groups.html.haml | 7 +- app/views/users/_profile.html.haml | 12 +- app/views/users/_projects.html.haml | 19 +- app/views/users/calendar.html.haml | 12 + app/views/users/calendar_activities.html.haml | 23 + app/views/users/show.atom.builder | 12 + app/views/users/show.html.haml | 64 +- app/views/users_groups/_users_group.html.haml | 31 - app/views/votes/_votes_block.html.haml | 14 +- app/views/votes/_votes_inline.html.haml | 4 +- app/workers/auto_merge_worker.rb | 13 + app/workers/emails_on_push_worker.rb | 58 +- app/workers/fork_registration_worker.rb | 12 + app/workers/irker_worker.rb | 169 +++ app/workers/post_receive.rb | 52 +- app/workers/project_service_worker.rb | 10 + app/workers/project_web_hook_worker.rb | 3 +- app/workers/repository_archive_worker.rb | 43 + app/workers/repository_import_worker.rb | 32 +- bin/background_jobs | 20 +- bin/guard | 16 + bin/pkgr_before_precompile.sh | 3 - bin/rspec | 2 +- bin/web | 12 +- config/application.rb | 43 +- config/database.yml.mysql | 3 + config/environments/production.rb | 16 +- config/environments/test.rb | 2 +- config/gitlab.yml.example | 240 ++-- config/initializers/1_settings.rb | 58 +- config/initializers/2_app.rb | 5 - config/initializers/4_sidekiq.rb | 3 +- config/initializers/5_backend.rb | 7 + config/initializers/6_rack_profiler.rb | 2 + config/initializers/7_omniauth.rb | 12 + .../initializers/acts_as_taggable_on_patch.rb | 130 --- config/initializers/carrierwave.rb | 28 +- config/initializers/devise.rb | 35 +- .../initializers/disable_email_interceptor.rb | 2 + config/initializers/doorkeeper.rb | 102 ++ .../initializers/gitlab_shell_secret_token.rb | 19 + config/initializers/mime_types.rb | 2 + config/initializers/public_key.rb | 2 + .../rack_attack_git_basic_auth.rb | 12 + config/initializers/redis-store-fix-expiry.rb | 44 + config/initializers/session_store.rb | 3 +- config/initializers/smtp_settings.rb.sample | 8 +- config/initializers/static_files.rb | 15 + config/initializers/time_zone.rb | 1 + config/locales/devise.en.yml | 6 +- config/locales/doorkeeper.en.yml | 73 ++ config/newrelic.yml | 16 + config/resque.yml.example | 2 +- config/routes.rb | 543 ++++++--- config/unicorn.rb.example | 24 +- config/unicorn.rb.example.development | 2 +- db/fixtures/development/01_admin.rb | 22 +- db/fixtures/development/04_project.rb | 8 +- db/fixtures/development/05_users.rb | 26 +- db/fixtures/development/06_teams.rb | 4 +- db/fixtures/development/10_merge_requests.rb | 18 + db/fixtures/development/12_snippets.rb | 34 +- db/fixtures/production/001_admin.rb | 17 +- .../20140125162722_add_avatar_to_projects.rb | 5 + .../20140903115954_migrate_to_new_shell.rb | 10 + ...0907220153_serialize_service_properties.rb | 42 + .../20140914113604_add_members_table.rb | 19 + ...0914145549_migrate_to_new_members_model.rb | 11 + ...20140914173417_remove_old_member_tables.rb | 26 + ...006143943_move_slack_service_to_webhook.rb | 17 + ...7100818_add_visibility_level_to_snippet.rb | 21 + ...0141121133009_add_timestamps_to_members.rb | 15 + .../20141121161704_add_identity_table.rb | 46 + ...05134006_add_locked_at_to_merge_request.rb | 5 + ...20141216155758_create_doorkeeper_tables.rb | 42 + ...20141217125223_add_owner_to_application.rb | 7 + ...135007_add_import_data_to_project_table.rb | 8 + ...velopers_can_push_to_protected_branches.rb | 5 + ...50108073740_create_application_settings.rb | 13 + ..._home_page_url_for_application_settings.rb | 5 + ...6234545_add_gitlab_access_token_to_user.rb | 5 + ...0_add_default_branch_protection_setting.rb | 5 + ...0205211843_add_timestamps_to_identities.rb | 5 + .../20150206181414_add_index_to_created_at.rb | 16 + ...06222854_add_notification_email_to_user.rb | 11 + .../20150209222013_add_missing_index.rb | 5 + .../20150211172122_add_template_to_service.rb | 5 + ...74341_allow_null_in_services_project_id.rb | 5 + ...sharing_enabled_to_application_settings.rb | 5 + ...0213114800_add_hide_no_password_to_user.rb | 5 + ..._add_password_automatically_set_to_user.rb | 5 + ...tbucket_access_token_and_secret_to_user.rb | 6 + .../20150219004514_add_events_to_services.rb | 8 + ...0223022001_set_missing_last_activity_at.rb | 8 + ...50225065047_add_note_events_to_services.rb | 5 + ...sibility_levels_to_application_settings.rb | 5 + ...0150306023106_fix_namespace_duplication.rb | 21 + ...306023112_add_unique_index_to_namespace.rb | 9 + ...150313012111_create_subscriptions_table.rb | 16 + .../20150320234437_add_location_to_user.rb | 5 + ...55957_set_incorrect_assignee_id_to_null.rb | 6 + .../20150327122227_add_public_to_key.rb | 5 + ...150327150017_add_import_data_to_project.rb | 5 + ...attachment_size_to_application_settings.rb | 5 + ...0150406133311_add_invite_data_to_member.rb | 12 + db/migrate/20150411000035_fix_identities.rb | 45 + .../20150411180045_rename_buildbox_service.rb | 9 + ...0150413192223_add_public_email_to_users.rb | 5 + ...150417121913_create_project_import_data.rb | 8 + ...7122318_remove_import_data_from_project.rb | 5 + db/schema.rb | 226 +++- doc/README.md | 22 +- doc/api/README.md | 81 +- doc/api/branches.md | 152 ++- doc/api/commits.md | 63 ++ doc/api/groups.md | 48 +- doc/api/issues.md | 28 +- doc/api/merge_requests.md | 81 +- doc/api/milestones.md | 13 + doc/api/notes.md | 49 +- doc/api/oauth2.md | 102 ++ doc/api/projects.md | 112 +- doc/api/repositories.md | 67 +- doc/api/services.md | 46 + doc/api/users.md | 44 +- doc/customization/issue_closing.md | 36 + doc/customization/libravatar.md | 69 ++ doc/customization/welcome_message.md | 38 + doc/development/README.md | 3 + doc/development/architecture.md | 42 +- doc/development/ci_setup.md | 46 + doc/development/omnibus.md | 32 + doc/development/rake_tasks.md | 6 +- doc/development/shell_commands.md | 76 +- doc/development/sidekiq_debugging.md | 14 + doc/development/ui_guide.md | 12 + doc/hooks/custom_hooks.md | 41 + doc/install/database_mysql.md | 16 +- doc/install/installation.md | 229 ++-- doc/install/requirements.md | 53 +- doc/install/structure.md | 4 +- doc/integration/README.md | 8 +- doc/integration/bitbucket.md | 122 ++ doc/integration/external-issue-tracker.md | 38 +- doc/integration/github.md | 63 +- doc/integration/github_app.png | Bin 75607 -> 75297 bytes doc/integration/gitlab.md | 84 ++ doc/integration/gitlab_actions.png | Bin 0 -> 17321 bytes doc/integration/gitlab_app.png | Bin 0 -> 55325 bytes doc/integration/gitlab_buttons_in_gmail.md | 28 + doc/integration/google.md | 47 +- doc/integration/ldap.md | 129 +++ doc/integration/oauth_provider.md | 35 + .../oauth_provider/admin_application.png | Bin 0 -> 55533 bytes .../oauth_provider/application_form.png | Bin 0 -> 25075 bytes .../oauth_provider/authorized_application.png | Bin 0 -> 17260 bytes .../oauth_provider/user_wide_applications.png | Bin 0 -> 46238 bytes doc/integration/omniauth.md | 113 +- doc/integration/redmine_configuration.png | Bin 0 -> 118752 bytes doc/integration/redmine_service_template.png | Bin 0 -> 198077 bytes doc/integration/shibboleth.md | 78 ++ doc/integration/slack.md | 30 +- doc/integration/twitter.md | 45 +- doc/logs/logs.md | 102 ++ doc/markdown/markdown.md | 78 +- doc/operations/README.md | 4 + doc/operations/cleaning_up_redis_sessions.md | 52 + doc/operations/sidekiq_memory_killer.md | 38 + doc/permissions/permissions.md | 18 +- doc/project_services/bamboo.md | 60 + doc/project_services/hipchat.md | 54 + doc/project_services/irker.md | 46 + doc/project_services/project_services.md | 20 + doc/public_access/public_access.md | 2 +- doc/raketasks/README.md | 3 + doc/raketasks/backup_restore.md | 144 ++- doc/raketasks/cleanup.md | 4 +- doc/raketasks/features.md | 2 +- doc/raketasks/import.md | 66 +- doc/raketasks/maintenance.md | 104 +- doc/raketasks/user_management.md | 16 +- doc/raketasks/web_hooks.md | 12 +- doc/release/howto_rc1.md | 55 + doc/release/howto_update_guides.md | 55 + doc/release/monthly.md | 357 +++--- doc/release/patch.md | 42 +- doc/release/security.md | 12 +- doc/security/README.md | 2 + doc/security/information_exclusivity.md | 9 + doc/security/webhooks.md | 13 + doc/ssh/README.md | 73 +- doc/ssh/deploy_keys.md | 9 - doc/ssh/ssh.md | 21 - doc/system_hooks/system_hooks.md | 91 +- doc/update/2.6-to-3.0.md | 15 +- doc/update/2.9-to-3.0.md | 1 + doc/update/3.0-to-3.1.md | 1 + doc/update/3.1-to-4.0.md | 1 + doc/update/4.0-to-4.1.md | 1 + doc/update/4.1-to-4.2.md | 1 + doc/update/4.2-to-5.0.md | 60 +- doc/update/5.0-to-5.1.md | 1 + doc/update/5.1-to-5.2.md | 1 + doc/update/5.1-to-5.4.md | 1 + doc/update/5.1-to-6.0.md | 97 +- doc/update/5.2-to-5.3.md | 1 + doc/update/5.3-to-5.4.md | 1 + doc/update/5.4-to-6.0.md | 6 +- doc/update/6.0-to-6.1.md | 2 +- doc/update/6.0-to-7.1.md | 182 --- doc/update/6.0-to-7.2.md | 194 ---- doc/update/6.1-to-6.2.md | 4 +- doc/update/6.2-to-6.3.md | 2 +- doc/update/6.3-to-6.4.md | 1 + doc/update/6.4-to-6.5.md | 1 + doc/update/6.5-to-6.6.md | 1 + doc/update/6.6-to-6.7.md | 4 + doc/update/6.7-to-6.8.md | 2 +- doc/update/6.8-to-6.9.md | 1 + doc/update/6.9-to-7.0.md | 5 +- doc/update/6.x-or-7.x-to-7.10.md | 298 +++++ doc/update/7.0-to-7.1.md | 2 +- doc/update/7.1-to-7.2.md | 13 +- doc/update/7.2-to-7.3.md | 145 +++ doc/update/7.3-to-7.4.md | 197 ++++ doc/update/7.4-to-7.5.md | 108 ++ doc/update/7.5-to-7.6.md | 114 ++ doc/update/7.6-to-7.7.md | 119 ++ doc/update/7.7-to-7.8.md | 120 ++ doc/update/7.8-to-7.9.md | 120 ++ doc/update/README.md | 20 +- doc/update/mysql_to_postgresql.md | 22 +- doc/update/patch_versions.md | 7 +- doc/update/upgrader.md | 22 +- doc/web_hooks/web_hooks.md | 86 +- doc/workflow/README.md | 12 +- doc/workflow/ci_mr.png | Bin 0 -> 40065 bytes doc/workflow/close_issue_mr.png | Bin 0 -> 146292 bytes doc/workflow/environment_branches.png | Bin 0 -> 40210 bytes doc/workflow/forking/branch_select.png | Bin 0 -> 55352 bytes doc/workflow/forking/fork_button.png | Bin 0 -> 68271 bytes doc/workflow/forking/groups.png | Bin 0 -> 98109 bytes doc/workflow/forking/merge_request.png | Bin 0 -> 60597 bytes doc/workflow/forking_workflow.md | 36 + doc/workflow/four_stages.png | Bin 0 -> 20934 bytes doc/workflow/git_pull.png | Bin 0 -> 167056 bytes doc/workflow/gitdashflow.png | Bin 0 -> 184726 bytes doc/workflow/github_flow.png | Bin 0 -> 20600 bytes doc/workflow/github_importer/importer.png | Bin 0 -> 39335 bytes .../github_importer/new_project_page.png | Bin 0 -> 46276 bytes doc/workflow/gitlab_flow.md | 316 ++++++ doc/workflow/gitlab_flow.png | Bin 0 -> 90883 bytes doc/workflow/gitlab_importer/importer.png | Bin 0 -> 40778 bytes .../gitlab_importer/new_project_page.png | Bin 0 -> 72663 bytes doc/workflow/good_commit.png | Bin 0 -> 28433 bytes doc/workflow/import_projects_from_github.md | 13 + .../import_projects_from_gitlab_com.md | 18 + doc/workflow/merge_commits.png | Bin 0 -> 41422 bytes doc/workflow/merge_request.png | Bin 0 -> 169503 bytes doc/workflow/messy_flow.png | Bin 0 -> 33829 bytes doc/workflow/migrating_from_svn.md | 17 + doc/workflow/mr_inline_comments.png | Bin 0 -> 193311 bytes doc/workflow/notifications.md | 71 ++ doc/workflow/notifications/settings.png | Bin 0 -> 114727 bytes doc/workflow/production_branch.png | Bin 0 -> 21716 bytes doc/workflow/protected_branches.md | 33 + .../protected_branches1.png | Bin 0 -> 170113 bytes .../protected_branches2.png | Bin 0 -> 25851 bytes doc/workflow/rebase.png | Bin 0 -> 123041 bytes doc/workflow/release_branches.png | Bin 0 -> 44173 bytes doc/workflow/remove_checkbox.png | Bin 0 -> 22272 bytes doc/workflow/voting_slider.png | Bin 0 -> 5329 bytes doc/workflow/web_editor.md | 26 + doc/workflow/web_editor/edit_file.png | Bin 0 -> 89039 bytes doc/workflow/web_editor/empty_project.png | Bin 0 -> 122296 bytes doc/workflow/web_editor/new_file.png | Bin 0 -> 85526 bytes doc/workflow/web_editor/show_file.png | Bin 0 -> 111479 bytes doc/workflow/workflow.md | 2 +- docker/.dockerignore | 1 + docker/Dockerfile | 33 + docker/README.md | 88 ++ docker/assets/wrapper | 17 + docker/data/Dockerfile | 8 + docker/data/assets/gitlab.rb | 37 + docker/troubleshooting.md | 63 ++ features/admin/active_tab.feature | 2 +- features/admin/applications.feature | 18 + features/admin/deploy_keys.feature | 21 + features/admin/groups.feature | 7 + features/admin/settings.feature | 16 + features/admin/users.feature | 16 + features/dashboard/active_tab.feature | 2 +- features/dashboard/archived_projects.feature | 7 +- features/dashboard/dashboard.feature | 4 +- features/dashboard/event_filters.feature | 2 +- features/{profile => dashboard}/group.feature | 26 +- features/dashboard/help.feature | 2 +- features/dashboard/issues.feature | 2 + features/dashboard/merge_requests.feature | 2 + features/dashboard/new_project.feature | 13 + features/dashboard/projects.feature | 9 - features/dashboard/search.feature | 10 - features/dashboard/shortcuts.feature | 21 + features/dashboard/starred_projects.feature | 12 + .../{public_groups.feature => groups.feature} | 6 +- features/explore/projects.feature | 2 +- features/{group.feature => groups.feature} | 23 +- features/invites.feature | 45 + features/profile/active_tab.feature | 2 +- features/profile/profile.feature | 14 + features/project/active_tab.feature | 19 +- features/project/archived.feature | 9 - features/project/commits/branches.feature | 28 +- features/project/commits/comments.feature | 19 +- features/project/commits/commits.feature | 12 +- .../project/commits/diff_comments.feature | 12 +- features/project/commits/tags.feature | 32 +- features/project/commits/user_lookup.feature | 2 +- features/project/create.feature | 2 +- features/project/deploy_keys.feature | 21 +- features/project/edit_issuetracker.feature | 18 - features/project/fork.feature | 4 +- .../project/forked_merge_requests.feature | 26 +- features/project/graph.feature | 7 +- features/project/issues/filter_labels.feature | 8 +- features/project/issues/issues.feature | 128 +++ features/project/issues/labels.feature | 12 +- features/project/issues/milestones.feature | 2 +- features/project/merge_requests.feature | 100 +- ...{network.feature => network_graph.feature} | 0 features/project/project.feature | 24 +- features/project/service.feature | 36 + features/project/shortcuts.feature | 52 + features/project/source/browse_files.feature | 123 +- features/project/source/git_blame.feature | 4 +- .../project/source/markdown_render.feature | 2 +- .../project/source/multiselect_blob.feature | 2 +- features/project/source/search_code.feature | 2 +- features/project/star.feature | 2 +- features/project/team_management.feature | 10 +- features/project/wiki.feature | 24 + features/search.feature | 46 + features/snippet_search.feature | 20 + features/snippets/discover.feature | 4 +- features/snippets/public_snippets.feature | 10 + features/snippets/snippets.feature | 4 +- features/snippets/user.feature | 13 +- features/steps/admin/active_tab.rb | 18 +- features/steps/admin/applications.rb | 55 + features/steps/admin/deploy_keys.rb | 57 + features/steps/admin/groups.rb | 45 +- features/steps/admin/logs.rb | 4 +- features/steps/admin/projects.rb | 14 +- features/steps/admin/settings.rb | 47 + features/steps/admin/users.rb | 65 +- features/steps/dashboard/active_tab.rb | 16 +- ...hived_projects.rb => archived_projects.rb} | 8 +- features/steps/dashboard/dashboard.rb | 42 +- features/steps/dashboard/event_filters.rb | 24 +- .../steps/{profile => dashboard}/group.rb | 29 +- features/steps/{ => dashboard}/help.rb | 2 +- features/steps/dashboard/issues.rb | 23 +- features/steps/dashboard/merge_requests.rb | 59 +- features/steps/dashboard/new_project.rb | 27 + features/steps/dashboard/projects.rb | 11 - features/steps/dashboard/search.rb | 19 - features/steps/dashboard/shortcuts.rb | 6 + features/steps/dashboard/starred_projects.rb | 15 + .../explore/{groups_feature.rb => groups.rb} | 6 +- features/steps/explore/projects.rb | 18 +- features/steps/{group/group.rb => groups.rb} | 109 +- features/steps/invites.rb | 80 ++ features/steps/profile/active_tab.rb | 12 +- features/steps/profile/emails.rb | 18 +- features/steps/profile/notifications.rb | 4 +- features/steps/profile/profile.rb | 60 +- features/steps/profile/ssh_keys.rb | 24 +- features/steps/project/active_tab.rb | 91 +- features/steps/project/archived.rb | 8 +- features/steps/project/browse_branches.rb | 46 - features/steps/project/browse_commits.rb | 91 -- features/steps/project/browse_files.rb | 93 -- features/steps/project/browse_tags.rb | 10 - features/steps/project/commits/branches.rb | 85 ++ .../comments.rb} | 2 +- features/steps/project/commits/commits.rb | 103 ++ .../diff_comments.rb} | 2 +- features/steps/project/commits/tags.rb | 82 ++ .../user_lookup.rb} | 10 +- features/steps/project/create.rb | 22 +- features/steps/project/deploy_keys.rb | 28 +- features/steps/project/fork.rb | 8 +- .../steps/project/forked_merge_requests.rb | 22 +- features/steps/project/graph.rb | 16 +- features/steps/project/hooks.rb | 6 +- features/steps/project/issue_tracker.rb | 31 - features/steps/project/issues.rb | 190 ---- .../project/{ => issues}/filter_labels.rb | 25 +- features/steps/project/issues/issues.rb | 276 +++++ features/steps/project/{ => issues}/labels.rb | 38 +- .../steps/project/{ => issues}/milestones.rb | 22 +- features/steps/project/markdown_render.rb | 277 ----- features/steps/project/merge_requests.rb | 110 +- features/steps/project/network_graph.rb | 28 +- features/steps/project/project.rb | 69 +- features/steps/project/project_shortcuts.rb | 36 + features/steps/project/redirects.rb | 22 +- features/steps/project/services.rb | 124 +- features/steps/project/snippets.rb | 40 +- features/steps/project/source/browse_files.rb | 218 ++++ .../git_blame.rb} | 10 +- .../steps/project/source/markdown_render.rb | 288 +++++ .../project/{ => source}/multiselect_blob.rb | 8 +- .../steps/project/{ => source}/search_code.rb | 5 +- features/steps/project/star.rb | 8 +- features/steps/project/team_management.rb | 75 +- features/steps/project/wiki.rb | 100 +- features/steps/search.rb | 69 ++ features/steps/shared/active_tab.rb | 36 +- features/steps/shared/admin.rb | 4 +- features/steps/shared/authentication.rb | 4 +- features/steps/shared/diff_note.rb | 78 +- features/steps/shared/issuable.rb | 15 + features/steps/shared/markdown.rb | 94 +- features/steps/shared/note.rb | 85 +- features/steps/shared/paths.rb | 188 +-- features/steps/shared/project.rb | 32 +- features/steps/shared/project_tab.rb | 48 + features/steps/shared/search.rb | 11 + features/steps/shared/shortcuts.rb | 18 + features/steps/shared/snippet.rb | 50 +- features/steps/snippet_search.rb | 56 + features/steps/snippets/discover.rb | 10 +- features/steps/snippets/public_snippets.rb | 25 + features/steps/snippets/snippets.rb | 26 +- features/steps/snippets/user.rb | 32 +- features/steps/user.rb | 35 +- features/support/env.rb | 12 +- features/user.feature | 9 + lib/api/api.rb | 6 +- lib/api/api_guard.rb | 172 +++ lib/api/branches.rb | 36 +- lib/api/commits.rb | 61 + lib/api/deploy_keys.rb | 2 +- lib/api/entities.rb | 89 +- lib/api/files.rb | 11 +- lib/api/group_members.rb | 87 ++ lib/api/groups.rb | 90 +- lib/api/helpers.rb | 81 +- lib/api/internal.rb | 62 +- lib/api/issues.rb | 67 +- lib/api/labels.rb | 31 +- lib/api/merge_requests.rb | 57 +- lib/api/milestones.rb | 19 +- lib/api/namespaces.rb | 4 +- lib/api/notes.rb | 35 +- lib/api/project_hooks.rb | 20 +- lib/api/project_members.rb | 40 +- lib/api/project_snippets.rb | 27 +- lib/api/projects.rb | 136 ++- lib/api/repositories.rb | 57 +- lib/api/services.rb | 38 +- lib/api/system_hooks.rb | 4 +- lib/api/users.rb | 75 +- lib/backup/database.rb | 15 +- lib/backup/manager.rb | 121 +- lib/backup/repository.rb | 75 +- lib/disable_email_interceptor.rb | 8 + lib/email_validator.rb | 2 +- lib/event_filter.rb | 8 +- lib/extracts_path.rb | 11 +- lib/file_size_validator.rb | 12 +- lib/gitlab.rb | 5 + lib/gitlab/access.rb | 21 + lib/gitlab/app_logger.rb | 4 +- lib/gitlab/auth.rb | 16 +- lib/gitlab/backend/grack_auth.rb | 110 +- lib/gitlab/backend/rack_attack_helpers.rb | 31 + lib/gitlab/backend/shell.rb | 63 +- lib/gitlab/backend/shell_adapter.rb | 1 - lib/gitlab/bitbucket_import.rb | 6 + lib/gitlab/bitbucket_import/client.rb | 99 ++ lib/gitlab/bitbucket_import/importer.rb | 52 + lib/gitlab/bitbucket_import/key_adder.rb | 23 + lib/gitlab/bitbucket_import/key_deleter.rb | 23 + .../bitbucket_import/project_creator.rb | 26 + lib/gitlab/blacklist.rb | 27 +- lib/gitlab/closing_issue_extractor.rb | 24 +- lib/gitlab/contributions_calendar.rb | 56 + .../{contributors.rb => contributor.rb} | 0 lib/gitlab/current_settings.rb | 28 + lib/gitlab/diff/file.rb | 49 + lib/gitlab/diff/line.rb | 12 + lib/gitlab/diff/line_code.rb | 9 + lib/gitlab/diff/parser.rb | 81 ++ lib/gitlab/diff_parser.rb | 83 -- lib/gitlab/force_push_check.rb | 15 + lib/gitlab/git.rb | 25 + lib/gitlab/git_access.rb | 228 +++- lib/gitlab/git_access_status.rb | 15 + lib/gitlab/git_access_wiki.rb | 11 + lib/gitlab/git_logger.rb | 4 +- lib/gitlab/git_ref_validator.rb | 12 + lib/gitlab/github_import/client.rb | 53 + lib/gitlab/github_import/importer.rb | 46 + lib/gitlab/github_import/project_creator.rb | 26 + lib/gitlab/gitlab_import/client.rb | 82 ++ lib/gitlab/gitlab_import/importer.rb | 50 + lib/gitlab/gitlab_import/project_creator.rb | 26 + lib/gitlab/gitorious_import/client.rb | 31 + .../gitorious_import/project_creator.rb | 26 + lib/gitlab/gitorious_import/repository.rb | 37 + lib/gitlab/google_code_import/client.rb | 48 + lib/gitlab/google_code_import/importer.rb | 377 ++++++ .../google_code_import/project_creator.rb | 37 + lib/gitlab/google_code_import/repository.rb | 43 + lib/gitlab/graphs/commits.rb | 49 + lib/gitlab/import_formatter.rb | 15 + lib/gitlab/inline_diff.rb | 6 +- lib/gitlab/issues_labels.rb | 1 - lib/gitlab/key_fingerprint.rb | 55 + lib/gitlab/ldap/access.rb | 44 +- lib/gitlab/ldap/adapter.rb | 74 +- lib/gitlab/ldap/authentication.rb | 71 ++ lib/gitlab/ldap/config.rb | 122 ++ lib/gitlab/ldap/person.rb | 27 +- lib/gitlab/ldap/user.rb | 142 +-- lib/gitlab/logger.rb | 6 +- lib/gitlab/markdown.rb | 314 +++-- lib/gitlab/markdown_helper.rb | 4 + lib/gitlab/middleware/static.rb | 13 + lib/gitlab/note_data_builder.rb | 77 ++ lib/gitlab/o_auth/auth_hash.rb | 54 + lib/gitlab/o_auth/user.rb | 106 ++ lib/gitlab/oauth/user.rb | 113 -- lib/gitlab/popen.rb | 5 +- lib/gitlab/production_logger.rb | 7 + lib/gitlab/project_search_results.rb | 77 ++ lib/gitlab/push_data_builder.rb | 90 ++ lib/gitlab/reference_extractor.rb | 93 +- lib/gitlab/regex.rb | 67 +- lib/gitlab/satellite/action.rb | 2 +- .../satellite/files/delete_file_action.rb | 4 +- .../satellite/files/edit_file_action.rb | 32 +- lib/gitlab/satellite/files/new_file_action.rb | 18 +- lib/gitlab/satellite/merge_action.rb | 30 +- lib/gitlab/satellite/satellite.rb | 22 +- lib/gitlab/search_results.rb | 69 ++ lib/gitlab/sidekiq_logger.rb | 7 + .../sidekiq_middleware/memory_killer.rb | 53 + lib/gitlab/snippet_search_results.rb | 131 +++ lib/gitlab/theme.rb | 20 +- lib/gitlab/upgrader.rb | 4 +- lib/gitlab/url_builder.rb | 45 +- lib/gitlab/utils.rb | 13 + lib/gitlab/visibility_level.rb | 24 +- lib/redcarpet/render/gitlab_html.rb | 52 +- lib/repository_cache.rb | 21 + lib/support/deploy/deploy.sh | 2 +- lib/support/nginx/gitlab | 125 +- lib/support/nginx/gitlab-ssl | 105 +- lib/tasks/brakeman.rake | 9 + lib/tasks/gitlab/backup.rake | 64 +- lib/tasks/gitlab/bulk_add_permission.rake | 12 +- lib/tasks/gitlab/check.rake | 192 ++-- lib/tasks/gitlab/cleanup.rake | 9 +- .../db/drop_all_postgres_sequences.rake | 10 + lib/tasks/gitlab/import.rake | 15 +- .../mail_google_schema_whitelisting.rake | 73 ++ lib/tasks/gitlab/shell.rake | 48 +- lib/tasks/gitlab/sidekiq.rake | 47 + lib/tasks/gitlab/task_helpers.rake | 16 + lib/tasks/gitlab/test.rake | 2 + lib/tasks/rubocop.rake | 4 + lib/tasks/spinach.rake | 8 +- lib/tasks/test.rake | 7 + safe/public.pem | 9 + .../application_controller_spec.rb | 20 +- .../autocomplete_controller_spec.rb | 51 + spec/controllers/blob_controller_spec.rb | 23 +- spec/controllers/branches_controller_spec.rb | 58 + spec/controllers/commit_controller_spec.rb | 36 +- spec/controllers/commits_controller_spec.rb | 7 +- spec/controllers/help_controller_spec.rb | 61 + .../import/bitbucket_controller_spec.rb | 163 +++ .../import/github_controller_spec.rb | 153 +++ .../import/gitlab_controller_spec.rb | 152 +++ .../import/gitorious_controller_spec.rb | 67 ++ .../import/google_code_controller_spec.rb | 47 + .../merge_requests_controller_spec.rb | 26 +- .../controllers/namespaces_controller_spec.rb | 121 ++ .../protected_branches_controller_spec.rb | 10 + .../projects/refs_controller_spec.rb | 41 + .../projects/repositories_controller_spec.rb | 65 ++ .../projects/uploads_controller_spec.rb | 280 +++++ spec/controllers/projects_controller_spec.rb | 61 +- spec/controllers/tree_controller_spec.rb | 26 +- spec/controllers/uploads_controller_spec.rb | 296 +++++ spec/controllers/users_controller_spec.rb | 46 + spec/factories.rb | 42 +- .../{users_groups.rb => group_members.rb} | 6 +- spec/factories/label_links.rb | 12 + spec/factories/labels.rb | 12 + spec/factories/merge_requests.rb | 25 +- spec/factories/notes.rb | 25 + spec/factories/projects.rb | 73 +- spec/factories_spec.rb | 8 +- spec/features/admin/admin_hooks_spec.rb | 12 +- spec/features/admin/admin_projects_spec.rb | 12 +- spec/features/admin/admin_users_spec.rb | 46 +- spec/features/admin/security_spec.rb | 20 +- spec/features/atom/dashboard_issues_spec.rb | 13 +- spec/features/atom/dashboard_spec.rb | 34 +- spec/features/atom/issues_spec.rb | 35 +- spec/features/atom/users_spec.rb | 77 ++ .../features/gitlab_flavored_markdown_spec.rb | 48 +- spec/features/help_pages_spec.rb | 13 + spec/features/issues_spec.rb | 188 +-- spec/features/notes_on_merge_requests_spec.rb | 193 ++-- spec/features/profile_spec.rb | 24 +- spec/features/projects_spec.rb | 23 +- spec/features/search_spec.rb | 2 +- .../security/dashboard_access_spec.rb | 54 +- .../security/group/group_access_spec.rb | 94 +- .../group/internal_group_access_spec.rb | 74 +- .../security/group/mixed_group_access_spec.rb | 74 +- .../group/public_group_access_spec.rb | 74 +- spec/features/security/profile_access_spec.rb | 107 +- .../security/project/internal_access_spec.rb | 251 ++-- .../security/project/private_access_spec.rb | 223 ++-- .../security/project/public_access_spec.rb | 251 ++-- spec/features/users_spec.rb | 46 +- spec/finders/issues_finder_spec.rb | 85 +- spec/finders/merge_requests_finder_spec.rb | 4 +- spec/finders/notes_finder_spec.rb | 4 +- spec/finders/projects_finder_spec.rb | 32 +- spec/finders/snippets_finder_spec.rb | 101 ++ spec/fixtures/GoogleCodeProjectHosting.json | 407 +++++++ spec/helpers/application_helper_spec.rb | 225 ++-- .../helpers/broadcast_messages_helper_spec.rb | 5 +- spec/helpers/diff_helper_spec.rb | 102 ++ spec/helpers/events_helper_spec.rb | 65 ++ spec/helpers/gitlab_markdown_helper_spec.rb | 609 ++++++++-- spec/helpers/groups_helper.rb | 21 + spec/helpers/icons_helper_spec.rb | 109 ++ spec/helpers/issues_helper_spec.rb | 63 +- spec/helpers/merge_requests_helper.rb | 2 +- spec/helpers/nav_helper_spec.rb | 25 + spec/helpers/notifications_helper_spec.rb | 11 +- spec/helpers/oauth_helper_spec.rb | 20 + spec/helpers/projects_helper_spec.rb | 22 +- spec/helpers/search_helper_spec.rb | 14 +- spec/helpers/submodule_helper_spec.rb | 69 +- spec/helpers/tab_helper_spec.rb | 30 +- spec/helpers/tree_helper_spec.rb | 28 + spec/lib/auth_spec.rb | 28 - spec/lib/disable_email_interceptor_spec.rb | 26 + spec/lib/extracts_path_spec.rb | 20 +- spec/lib/file_size_validator_spec.rb | 43 + spec/lib/git_ref_validator_spec.rb | 20 + spec/lib/gitlab/auth_spec.rb | 54 + spec/lib/gitlab/backend/grack_auth_spec.rb | 196 ++++ .../backend/rack_attack_helpers_spec.rb | 35 + spec/lib/gitlab/backend/shell_spec.rb | 12 +- .../gitlab/bitbucket_import/client_spec.rb | 17 + .../bitbucket_import/project_creator_spec.rb | 26 + .../gitlab/closing_issue_extractor_spec.rb | 176 +++ spec/lib/gitlab/diff/file_spec.rb | 21 + spec/lib/gitlab/diff/parser_spec.rb | 93 ++ spec/lib/gitlab/git_access_spec.rb | 235 ++++ spec/lib/gitlab/git_access_wiki_spec.rb | 22 + spec/lib/gitlab/github_import/client_spec.rb | 16 + .../github_import/project_creator_spec.rb | 28 + spec/lib/gitlab/gitlab_import/client_spec.rb | 16 + .../gitlab_import/project_creator_spec.rb | 28 + .../lib/gitlab/gitlab_markdown_helper_spec.rb | 8 +- .../gitorious_import/project_creator_spec.rb | 26 + .../gitlab/google_code_import/client_spec.rb | 34 + .../google_code_import/importer_spec.rb | 85 ++ .../project_creator_spec.rb | 27 + spec/lib/gitlab/key_fingerprint_spec.rb | 12 + spec/lib/gitlab/ldap/access_spec.rb | 55 + .../{ldap_adapter_spec.rb => adapter_spec.rb} | 8 +- spec/lib/gitlab/ldap/authentication_spec.rb | 53 + spec/lib/gitlab/ldap/config_spec.rb | 20 + spec/lib/gitlab/ldap/ldap_access_spec.rb | 32 - spec/lib/gitlab/ldap/ldap_user_auth_spec.rb | 58 - spec/lib/gitlab/ldap/user_spec.rb | 106 ++ spec/lib/gitlab/note_data_builder_spec.rb | 73 ++ spec/lib/gitlab/o_auth/auth_hash_spec.rb | 55 + spec/lib/gitlab/o_auth/user_spec.rb | 109 ++ spec/lib/gitlab/popen_spec.rb | 12 +- spec/lib/gitlab/push_data_builder_spec.rb | 39 + spec/lib/gitlab/reference_extractor_spec.rb | 179 ++- spec/lib/gitlab/regex_spec.rb | 26 +- spec/lib/gitlab/satellite/action_spec.rb | 48 +- .../lib/gitlab/satellite/merge_action_spec.rb | 32 +- spec/lib/gitlab/upgrader_spec.rb | 6 +- spec/lib/gitlab/url_builder_spec.rb | 68 +- spec/lib/gitlab/version_info_spec.rb | 52 +- spec/lib/oauth_spec.rb | 45 - spec/lib/repository_cache_spec.rb | 34 + spec/lib/votes_spec.rb | 153 ++- spec/mailers/notify_spec.rb | 419 +++++-- spec/models/application_setting_spec.rb | 24 + spec/models/assembla_service_spec.rb | 53 - spec/models/broadcast_message_spec.rb | 8 +- spec/models/commit_spec.rb | 52 +- spec/models/concerns/issuable_spec.rb | 46 +- spec/models/concerns/mentionable_spec.rb | 14 + spec/models/deploy_key_spec.rb | 4 +- spec/models/deploy_keys_project_spec.rb | 56 +- spec/models/event_spec.rb | 27 +- spec/models/external_wiki_service_spec.rb | 39 + spec/models/flowdock_service_spec.rb | 52 - spec/models/forked_project_link_spec.rb | 10 +- spec/models/gemnasium_service_spec.rb | 48 - spec/models/gitlab_ci_service_spec.rb | 49 - spec/models/group_spec.rb | 38 +- spec/models/{ => hooks}/project_hook_spec.rb | 0 spec/models/{ => hooks}/service_hook_spec.rb | 2 +- spec/models/hooks/system_hook_spec.rb | 100 ++ spec/models/{ => hooks}/web_hook_spec.rb | 30 +- spec/models/issue_spec.rb | 14 +- spec/models/key_spec.rb | 37 +- spec/models/label_link_spec.rb | 18 +- spec/models/label_spec.rb | 42 +- spec/models/member_spec.rb | 148 +++ spec/models/members/group_member_spec.rb | 46 + spec/models/members/project_member_spec.rb | 92 ++ spec/models/merge_request_spec.rb | 38 +- spec/models/milestone_spec.rb | 42 +- spec/models/namespace_spec.rb | 50 +- spec/models/note_spec.rb | 452 ++++++-- spec/models/project_security_spec.rb | 34 +- .../project_services/asana_service_spec.rb | 65 ++ .../project_services/assembla_service_spec.rb | 53 + .../buildkite_service_spec.rb | 82 ++ .../project_services/flowdock_service_spec.rb | 52 + .../gemnasium_service_spec.rb | 48 + .../gitlab_ci_service_spec.rb | 70 ++ .../gitlab_issue_tracker_service_spec.rb | 66 ++ .../project_services/hipchat_service_spec.rb | 217 ++++ .../project_services/irker_service_spec.rb | 108 ++ .../project_services/jira_service_spec.rb | 102 ++ .../project_services/pushover_service_spec.rb | 74 ++ .../slack_service/issue_message_spec.rb | 56 + .../slack_service/merge_message_spec.rb | 51 + .../slack_service/note_message_spec.rb | 129 +++ .../slack_service/push_message_spec.rb} | 55 +- .../project_services/slack_service_spec.rb | 170 +++ spec/models/project_snippet_spec.rb | 26 +- spec/models/project_spec.rb | 223 ++-- spec/models/project_team_spec.rb | 40 +- spec/models/project_wiki_spec.rb | 80 +- spec/models/protected_branch_spec.rb | 17 +- spec/models/repository_spec.rb | 28 + spec/models/service_spec.rb | 59 +- spec/models/slack_service_spec.rb | 70 -- spec/models/snippet_spec.rb | 38 +- spec/models/system_hook_spec.rb | 65 -- spec/models/user_spec.rb | 362 +++--- spec/models/users_group_spec.rb | 67 -- spec/models/users_project_spec.rb | 113 -- spec/models/wiki_page_spec.rb | 71 +- spec/requests/api/api_helpers_spec.rb | 77 +- spec/requests/api/branches_spec.rb | 108 +- spec/requests/api/commits_spec.rb | 99 +- spec/requests/api/doorkeeper_access_spec.rb | 31 + spec/requests/api/files_spec.rb | 64 +- spec/requests/api/fork_spec.rb | 73 ++ spec/requests/api/group_members_spec.rb | 199 ++++ spec/requests/api/groups_spec.rb | 183 +-- spec/requests/api/internal_spec.rb | 113 +- spec/requests/api/issues_spec.rb | 248 +++- spec/requests/api/labels_spec.rb | 81 +- spec/requests/api/merge_requests_spec.rb | 280 +++-- spec/requests/api/milestones_spec.rb | 97 +- spec/requests/api/namespaces_spec.rb | 9 +- spec/requests/api/notes_spec.rb | 112 +- spec/requests/api/project_hooks_spec.rb | 44 +- spec/requests/api/project_members_spec.rb | 102 +- spec/requests/api/projects_spec.rb | 829 ++++++++------ spec/requests/api/repositories_spec.rb | 183 ++- spec/requests/api/services_spec.rb | 34 +- spec/requests/api/session_spec.rb | 56 +- spec/requests/api/system_hooks_spec.rb | 20 +- spec/requests/api/users_spec.rb | 336 ++++-- spec/routing/admin_routing_spec.rb | 40 +- spec/routing/notifications_routing_spec.rb | 4 +- spec/routing/project_routing_spec.rb | 420 +++---- spec/routing/routing_spec.rb | 127 +-- .../archive_repository_service_spec.rb | 93 ++ spec/services/create_snippet_service_spec.rb | 44 + spec/services/event_create_service_spec.rb | 18 +- spec/services/fork_service_spec.rb | 57 - spec/services/git_push_service_spec.rb | 139 ++- spec/services/git_tag_push_service_spec.rb | 74 +- .../issues/bulk_update_context_spec.rb | 110 -- .../issues/bulk_update_service_spec.rb | 121 ++ spec/services/issues/close_service_spec.rb | 10 +- spec/services/issues/create_service_spec.rb | 4 +- spec/services/issues/update_service_spec.rb | 26 +- .../merge_requests/close_service_spec.rb | 23 +- .../merge_requests/create_service_spec.rb | 23 +- .../merge_requests/merge_service_spec.rb | 44 + .../merge_requests/refresh_service_spec.rb | 98 ++ .../merge_requests/reopen_service_spec.rb | 45 + .../merge_requests/update_service_spec.rb | 47 +- spec/services/notes/create_service_spec.rb | 4 +- spec/services/notification_service_spec.rb | 129 ++- spec/services/projects/create_service_spec.rb | 43 +- spec/services/projects/fork_service_spec.rb | 108 ++ spec/services/projects/image_service_spec.rb | 62 - .../projects/transfer_service_spec.rb | 32 +- spec/services/projects/update_service_spec.rb | 34 +- spec/services/projects/upload_service_spec.rb | 85 ++ spec/services/search_service_spec.rb | 8 +- spec/services/system_hooks_service_spec.rb | 62 +- spec/services/test_hook_service_spec.rb | 2 +- spec/services/update_snippet_service_spec.rb | 52 + spec/spec_helper.rb | 22 +- spec/support/db_cleaner.rb | 11 + spec/support/login_helpers.rb | 2 +- spec/support/matchers.rb | 6 +- spec/support/mentionable_shared_examples.rb | 65 +- spec/support/repo_helpers.rb | 19 + spec/support/select2_helper.rb | 4 +- spec/support/taskable_shared_examples.rb | 42 + spec/support/test_env.rb | 91 +- spec/tasks/gitlab/backup_rake_spec.rb | 124 +- .../gitlab/mail_google_schema_whitelisting.rb | 27 + spec/workers/fork_registration_worker_spec.rb | 10 + spec/workers/post_receive_spec.rb | 25 +- .../workers/repository_archive_worker_spec.rb | 80 ++ vendor/assets/javascripts/chart-lib.min.js | 11 + vendor/assets/javascripts/highlight.pack.js | 1 - .../javascripts/jquery.sticky-kit.min.js | 9 + vendor/assets/stylesheets/highlightjs.min.css | 1 - vendor/plugins/.gitkeep | 0 1764 files changed, 54619 insertions(+), 21639 deletions(-) create mode 100644 .gitattributes create mode 100644 .rubocop.yml create mode 100644 .ruby-version delete mode 100644 .travis.yml create mode 100644 app/assets/images/authbuttons/bitbucket_64.png delete mode 100644 app/assets/images/authbuttons/github_32.png create mode 100644 app/assets/images/authbuttons/gitlab_64.png delete mode 100644 app/assets/images/authbuttons/google_32.png delete mode 100644 app/assets/images/authbuttons/twitter_32.png create mode 100644 app/assets/images/gitorious-logo-black.png create mode 100644 app/assets/images/gitorious-logo-blue.png delete mode 100644 app/assets/images/logo-black.png create mode 100644 app/assets/images/solarized-light-scheme-preview.png create mode 100644 app/assets/javascripts/aside.js.coffee create mode 100644 app/assets/javascripts/autosave.js.coffee create mode 100644 app/assets/javascripts/behaviors/taskable.js.coffee rename app/assets/javascripts/{ => blob}/blob.js.coffee (96%) create mode 100644 app/assets/javascripts/blob/edit_blob.js.coffee create mode 100644 app/assets/javascripts/blob/new_blob.js.coffee create mode 100644 app/assets/javascripts/calendar.js.coffee delete mode 100644 app/assets/javascripts/chart.js.coffee create mode 100644 app/assets/javascripts/confirm_danger_modal.js.coffee create mode 100644 app/assets/javascripts/dropzone_input.js.coffee create mode 100644 app/assets/javascripts/group_avatar.js.coffee create mode 100644 app/assets/javascripts/groups_select.js.coffee create mode 100644 app/assets/javascripts/importer_status.js.coffee create mode 100644 app/assets/javascripts/issuable_form.js.coffee delete mode 100644 app/assets/javascripts/markdown_area.js.coffee delete mode 100644 app/assets/javascripts/notes_votes.js.coffee create mode 100644 app/assets/javascripts/project_avatar.js.coffee create mode 100644 app/assets/javascripts/project_fork.js.coffee create mode 100644 app/assets/javascripts/project_members.js.coffee create mode 100644 app/assets/javascripts/project_new.js.coffee create mode 100644 app/assets/javascripts/project_show.js.coffee delete mode 100644 app/assets/javascripts/project_users_select.js.coffee create mode 100644 app/assets/javascripts/projects_list.js.coffee create mode 100644 app/assets/javascripts/protected_branches.js.coffee create mode 100644 app/assets/javascripts/shortcuts_dashboard_navigation.js.coffee create mode 100644 app/assets/javascripts/shortcuts_issueable.coffee create mode 100644 app/assets/javascripts/shortcuts_navigation.coffee create mode 100644 app/assets/javascripts/shortcuts_network.js.coffee create mode 100644 app/assets/javascripts/subscription.js.coffee delete mode 100644 app/assets/javascripts/team_members.js.coffee create mode 100644 app/assets/javascripts/user.js.coffee create mode 100644 app/assets/javascripts/zen_mode.js.coffee rename app/assets/stylesheets/{ => base}/gl_bootstrap.scss (57%) create mode 100644 app/assets/stylesheets/base/gl_variables.scss rename app/assets/stylesheets/{main => base}/layout.scss (68%) rename app/assets/stylesheets/{main => base}/mixins.scss (80%) create mode 100644 app/assets/stylesheets/base/variables.scss create mode 100644 app/assets/stylesheets/generic/calendar.scss create mode 100644 app/assets/stylesheets/generic/filters.scss create mode 100644 app/assets/stylesheets/generic/gfm.scss create mode 100644 app/assets/stylesheets/generic/mobile.scss create mode 100644 app/assets/stylesheets/generic/nav_sidebar.scss delete mode 100644 app/assets/stylesheets/generic/sidebar.scss create mode 100644 app/assets/stylesheets/generic/tables.scss create mode 100644 app/assets/stylesheets/generic/timeline.scss create mode 100644 app/assets/stylesheets/generic/zen.scss create mode 100644 app/assets/stylesheets/highlight/solarized_light.scss delete mode 100644 app/assets/stylesheets/main/fonts.scss delete mode 100644 app/assets/stylesheets/main/variables.scss rename app/assets/stylesheets/{sections => pages}/admin.scss (78%) create mode 100644 app/assets/stylesheets/pages/commit.scss create mode 100644 app/assets/stylesheets/pages/commits.scss rename app/assets/stylesheets/{sections => pages}/dashboard.scss (57%) rename app/assets/stylesheets/{sections => pages}/diff.scss (84%) rename app/assets/stylesheets/{sections => pages}/editor.scss (55%) rename app/assets/stylesheets/{sections => pages}/errors.scss (100%) rename app/assets/stylesheets/{sections => pages}/events.scss (70%) rename app/assets/stylesheets/{sections => pages}/explore.scss (100%) rename app/assets/stylesheets/{sections => pages}/graph.scss (84%) rename app/assets/stylesheets/{sections => pages}/groups.scss (87%) rename app/assets/stylesheets/{sections => pages}/header.scss (60%) create mode 100644 app/assets/stylesheets/pages/help.scss create mode 100644 app/assets/stylesheets/pages/import.scss create mode 100644 app/assets/stylesheets/pages/issuable.scss rename app/assets/stylesheets/{sections => pages}/issues.scss (66%) rename app/assets/stylesheets/{sections => pages}/labels.scss (100%) create mode 100644 app/assets/stylesheets/pages/login.scss create mode 100644 app/assets/stylesheets/pages/merge_requests.scss create mode 100644 app/assets/stylesheets/pages/milestone.scss create mode 100644 app/assets/stylesheets/pages/note_form.scss create mode 100644 app/assets/stylesheets/pages/notes.scss rename app/assets/stylesheets/{sections => pages}/notifications.scss (75%) rename app/assets/stylesheets/{sections => pages}/profile.scss (69%) rename app/assets/stylesheets/{sections => pages}/projects.scss (61%) create mode 100644 app/assets/stylesheets/pages/search.scss rename app/assets/stylesheets/{sections => pages}/snippets.scss (100%) rename app/assets/stylesheets/{sections => pages}/stat_graph.scss (100%) rename app/assets/stylesheets/{sections => pages}/themes.scss (100%) rename app/assets/stylesheets/{sections => pages}/tree.scss (79%) create mode 100644 app/assets/stylesheets/pages/ui_dev_kit.scss create mode 100644 app/assets/stylesheets/pages/votes.scss rename app/assets/stylesheets/{sections => pages}/wiki.scss (100%) delete mode 100644 app/assets/stylesheets/sections/commits.scss delete mode 100644 app/assets/stylesheets/sections/help.scss delete mode 100644 app/assets/stylesheets/sections/login.scss delete mode 100644 app/assets/stylesheets/sections/merge_requests.scss delete mode 100644 app/assets/stylesheets/sections/milestone.scss delete mode 100644 app/assets/stylesheets/sections/nav.scss delete mode 100644 app/assets/stylesheets/sections/notes.scss delete mode 100644 app/assets/stylesheets/sections/votes.scss create mode 100644 app/assets/stylesheets/themes/dark-theme.scss create mode 100644 app/assets/stylesheets/themes/ui_blue.scss create mode 100644 app/controllers/admin/application_settings_controller.rb create mode 100644 app/controllers/admin/applications_controller.rb create mode 100644 app/controllers/admin/deploy_keys_controller.rb create mode 100644 app/controllers/admin/keys_controller.rb create mode 100644 app/controllers/admin/services_controller.rb create mode 100644 app/controllers/autocomplete_controller.rb create mode 100644 app/controllers/confirmations_controller.rb create mode 100644 app/controllers/dashboard/groups_controller.rb create mode 100644 app/controllers/dashboard/milestones_controller.rb create mode 100644 app/controllers/dashboard/projects_controller.rb delete mode 100644 app/controllers/files_controller.rb create mode 100644 app/controllers/groups/application_controller.rb create mode 100644 app/controllers/groups/group_members_controller.rb create mode 100644 app/controllers/import/base_controller.rb create mode 100644 app/controllers/import/bitbucket_controller.rb create mode 100644 app/controllers/import/github_controller.rb create mode 100644 app/controllers/import/gitlab_controller.rb create mode 100644 app/controllers/import/gitorious_controller.rb create mode 100644 app/controllers/import/google_code_controller.rb create mode 100644 app/controllers/invites_controller.rb create mode 100644 app/controllers/oauth/applications_controller.rb create mode 100644 app/controllers/oauth/authorizations_controller.rb create mode 100644 app/controllers/oauth/authorized_applications_controller.rb delete mode 100644 app/controllers/profiles/groups_controller.rb create mode 100644 app/controllers/projects/avatars_controller.rb delete mode 100644 app/controllers/projects/base_tree_controller.rb delete mode 100644 app/controllers/projects/edit_tree_controller.rb create mode 100644 app/controllers/projects/forks_controller.rb create mode 100644 app/controllers/projects/imports_controller.rb delete mode 100644 app/controllers/projects/new_tree_controller.rb create mode 100644 app/controllers/projects/project_members_controller.rb delete mode 100644 app/controllers/projects/team_members_controller.rb create mode 100644 app/controllers/projects/uploads_controller.rb create mode 100644 app/controllers/uploads_controller.rb delete mode 100644 app/controllers/users_groups_controller.rb rename app/finders/{base_finder.rb => issuable_finder.rb} (81%) create mode 100644 app/finders/snippets_finder.rb create mode 100644 app/helpers/application_settings_helper.rb create mode 100644 app/helpers/emails_helper.rb create mode 100644 app/helpers/explore_helper.rb create mode 100644 app/helpers/external_wiki_helper.rb create mode 100644 app/helpers/git_helper.rb create mode 100644 app/helpers/gitlab_routing_helper.rb create mode 100644 app/helpers/milestones_helper.rb create mode 100644 app/helpers/nav_helper.rb create mode 100644 app/helpers/sorting_helper.rb create mode 100644 app/helpers/wiki_helper.rb create mode 100644 app/models/application_setting.rb create mode 100644 app/models/concerns/sortable.rb create mode 100644 app/models/concerns/taskable.rb create mode 100644 app/models/external_issue.rb rename app/models/{ => hooks}/project_hook.rb (100%) rename app/models/{ => hooks}/service_hook.rb (100%) rename app/models/{ => hooks}/system_hook.rb (100%) rename app/models/{ => hooks}/web_hook.rb (69%) create mode 100644 app/models/identity.rb create mode 100644 app/models/member.rb create mode 100644 app/models/members/group_member.rb create mode 100644 app/models/members/project_member.rb create mode 100644 app/models/project_import_data.rb create mode 100644 app/models/project_services/asana_service.rb create mode 100644 app/models/project_services/bamboo_service.rb create mode 100644 app/models/project_services/buildkite_service.rb create mode 100644 app/models/project_services/custom_issue_tracker_service.rb create mode 100644 app/models/project_services/external_wiki_service.rb create mode 100644 app/models/project_services/gitlab_issue_tracker_service.rb create mode 100644 app/models/project_services/irker_service.rb create mode 100644 app/models/project_services/issue_tracker_service.rb create mode 100644 app/models/project_services/jira_service.rb create mode 100644 app/models/project_services/pushover_service.rb create mode 100644 app/models/project_services/redmine_service.rb delete mode 100644 app/models/project_services/slack_message.rb create mode 100644 app/models/project_services/slack_service/base_message.rb create mode 100644 app/models/project_services/slack_service/issue_message.rb create mode 100644 app/models/project_services/slack_service/merge_message.rb create mode 100644 app/models/project_services/slack_service/note_message.rb create mode 100644 app/models/project_services/slack_service/push_message.rb create mode 100644 app/models/project_services/teamcity_service.rb create mode 100644 app/models/subscription.rb delete mode 100644 app/models/users_group.rb delete mode 100644 app/models/users_project.rb create mode 100644 app/services/create_snippet_service.rb create mode 100644 app/services/delete_tag_service.rb create mode 100644 app/services/issuable_base_service.rb create mode 100644 app/services/merge_requests/refresh_service.rb create mode 100644 app/services/notes/update_service.rb create mode 100644 app/services/oauth2/access_token_validation_service.rb create mode 100644 app/services/projects/autocomplete_service.rb delete mode 100644 app/services/projects/image_service.rb create mode 100644 app/services/projects/upload_service.rb create mode 100644 app/services/search/snippet_service.rb create mode 100644 app/services/update_snippet_service.rb create mode 100644 app/uploaders/avatar_uploader.rb create mode 100644 app/views/admin/application_settings/_form.html.haml create mode 100644 app/views/admin/application_settings/show.html.haml create mode 100644 app/views/admin/applications/_delete_form.html.haml create mode 100644 app/views/admin/applications/_form.html.haml create mode 100644 app/views/admin/applications/edit.html.haml create mode 100644 app/views/admin/applications/index.html.haml create mode 100644 app/views/admin/applications/new.html.haml create mode 100644 app/views/admin/applications/show.html.haml create mode 100644 app/views/admin/deploy_keys/index.html.haml create mode 100644 app/views/admin/deploy_keys/new.html.haml create mode 100644 app/views/admin/deploy_keys/show.html.haml create mode 100644 app/views/admin/keys/show.html.haml create mode 100644 app/views/admin/services/_form.html.haml create mode 100644 app/views/admin/services/edit.html.haml create mode 100644 app/views/admin/services/index.html.haml delete mode 100644 app/views/dashboard/_groups.html.haml delete mode 100644 app/views/dashboard/_project.html.haml delete mode 100644 app/views/dashboard/_projects_filter.html.haml create mode 100644 app/views/dashboard/groups/index.html.haml create mode 100644 app/views/dashboard/milestones/_issue.html.haml create mode 100644 app/views/dashboard/milestones/_issues.html.haml create mode 100644 app/views/dashboard/milestones/_merge_request.html.haml create mode 100644 app/views/dashboard/milestones/_merge_requests.html.haml create mode 100644 app/views/dashboard/milestones/_milestone.html.haml create mode 100644 app/views/dashboard/milestones/index.html.haml create mode 100644 app/views/dashboard/milestones/show.html.haml delete mode 100644 app/views/dashboard/projects.html.haml create mode 100644 app/views/dashboard/projects/starred.html.haml mode change 100755 => 100644 app/views/devise/confirmations/new.html.haml mode change 100755 => 100644 app/views/devise/passwords/new.html.haml delete mode 100644 app/views/devise/sessions/_oauth_providers.html.haml create mode 100644 app/views/devise/shared/_omniauth_box.html.haml create mode 100644 app/views/devise/shared/_signin_box.html.haml create mode 100644 app/views/devise/shared/_signup_box.html.haml create mode 100644 app/views/doorkeeper/applications/_delete_form.html.haml create mode 100644 app/views/doorkeeper/applications/_form.html.haml create mode 100644 app/views/doorkeeper/applications/edit.html.haml create mode 100644 app/views/doorkeeper/applications/index.html.haml create mode 100644 app/views/doorkeeper/applications/new.html.haml create mode 100644 app/views/doorkeeper/applications/show.html.haml create mode 100644 app/views/doorkeeper/authorizations/error.html.haml create mode 100644 app/views/doorkeeper/authorizations/new.html.haml create mode 100644 app/views/doorkeeper/authorizations/show.html.haml create mode 100644 app/views/doorkeeper/authorized_applications/_delete_form.html.haml create mode 100644 app/views/doorkeeper/authorized_applications/index.html.haml create mode 100644 app/views/events/event/_created_project.html.haml create mode 100644 app/views/explore/projects/_filter.html.haml delete mode 100644 app/views/groups/_filter.html.haml delete mode 100644 app/views/groups/_new_group_member.html.haml create mode 100644 app/views/groups/group_members/_group_member.html.haml create mode 100644 app/views/groups/group_members/_new_group_member.html.haml rename app/views/groups/{members.html.haml => group_members/index.html.haml} (70%) rename app/views/{users_groups => groups/group_members}/update.js.haml (100%) create mode 100644 app/views/groups/milestones/_milestone.html.haml create mode 100644 app/views/help/ui.html.haml create mode 100644 app/views/import/base/create.js.haml create mode 100644 app/views/import/bitbucket/status.html.haml create mode 100644 app/views/import/github/status.html.haml create mode 100644 app/views/import/gitlab/status.html.haml create mode 100644 app/views/import/gitorious/status.html.haml create mode 100644 app/views/import/google_code/new.html.haml create mode 100644 app/views/import/google_code/new_user_map.html.haml create mode 100644 app/views/import/google_code/status.html.haml create mode 100644 app/views/invites/show.html.haml create mode 100644 app/views/layouts/_collapse_button.html.haml create mode 100644 app/views/layouts/_page.html.haml delete mode 100644 app/views/layouts/user_team.html.haml create mode 100644 app/views/notify/_reassigned_issuable_email.html.haml create mode 100644 app/views/notify/_reassigned_issuable_email.text.erb create mode 100644 app/views/notify/group_invite_accepted_email.html.haml create mode 100644 app/views/notify/group_invite_accepted_email.text.erb create mode 100644 app/views/notify/group_invite_declined_email.html.haml create mode 100644 app/views/notify/group_invite_declined_email.text.erb create mode 100644 app/views/notify/group_member_invited_email.html.haml create mode 100644 app/views/notify/group_member_invited_email.text.erb create mode 100644 app/views/notify/project_invite_accepted_email.html.haml create mode 100644 app/views/notify/project_invite_accepted_email.text.erb create mode 100644 app/views/notify/project_invite_declined_email.html.haml create mode 100644 app/views/notify/project_invite_declined_email.text.erb create mode 100644 app/views/notify/project_member_invited_email.html.haml create mode 100644 app/views/notify/project_member_invited_email.text.erb create mode 100644 app/views/profiles/applications.html.haml delete mode 100644 app/views/profiles/groups/index.html.haml create mode 100644 app/views/profiles/keys/_key_details.html.haml create mode 100644 app/views/profiles/keys/_key_table.html.haml create mode 100644 app/views/projects/_bitbucket_import_modal.html.haml create mode 100644 app/views/projects/_commit_button.html.haml create mode 100644 app/views/projects/_github_import_modal.html.haml create mode 100644 app/views/projects/_gitlab_import_modal.html.haml create mode 100644 app/views/projects/_issuable_form.html.haml create mode 100644 app/views/projects/_md_preview.html.haml create mode 100644 app/views/projects/_zen.html.haml create mode 100644 app/views/projects/blob/_editor.html.haml create mode 100644 app/views/projects/blob/edit.html.haml create mode 100644 app/views/projects/blob/new.html.haml create mode 100644 app/views/projects/blob/preview.html.haml create mode 100644 app/views/projects/commit/branches.html.haml create mode 100644 app/views/projects/commits/_commit_list.html.haml delete mode 100644 app/views/projects/commits/_diff_file.html.haml delete mode 100644 app/views/projects/commits/_diff_warning.html.haml delete mode 100644 app/views/projects/commits/_diffs.html.haml delete mode 100644 app/views/projects/commits/_parallel_view.html.haml delete mode 100644 app/views/projects/commits/_text_file.html.haml delete mode 100644 app/views/projects/create.js.haml create mode 100644 app/views/projects/diffs/_diffs.html.haml create mode 100644 app/views/projects/diffs/_file.html.haml rename app/views/projects/{commits => diffs}/_image.html.haml (88%) rename app/views/projects/{commits => }/diffs/_match_line.html.haml (100%) create mode 100644 app/views/projects/diffs/_match_line_parallel.html.haml create mode 100644 app/views/projects/diffs/_parallel_view.html.haml rename app/views/projects/{commits/_diff_stats.html.haml => diffs/_stats.html.haml} (74%) create mode 100644 app/views/projects/diffs/_text_file.html.haml create mode 100644 app/views/projects/diffs/_warning.html.haml delete mode 100644 app/views/projects/edit_tree/_diff.html.haml delete mode 100644 app/views/projects/edit_tree/preview.html.haml delete mode 100644 app/views/projects/edit_tree/show.html.haml delete mode 100644 app/views/projects/fork.html.haml create mode 100644 app/views/projects/forks/error.html.haml create mode 100644 app/views/projects/forks/new.html.haml create mode 100644 app/views/projects/go_import.html.haml create mode 100644 app/views/projects/graphs/_head.html.haml create mode 100644 app/views/projects/graphs/commits.html.haml delete mode 100644 app/views/projects/graphs/show.js.haml delete mode 100644 app/views/projects/import.html.haml create mode 100644 app/views/projects/imports/new.html.haml create mode 100644 app/views/projects/imports/show.html.haml create mode 100644 app/views/projects/issues/_discussion.html.haml delete mode 100644 app/views/projects/issues/_head.html.haml create mode 100644 app/views/projects/labels/destroy.js.haml create mode 100644 app/views/projects/merge_requests/_discussion.html.haml create mode 100644 app/views/projects/merge_requests/_merge_requests.html.haml delete mode 100644 app/views/projects/new_tree/show.html.haml create mode 100644 app/views/projects/no_repo.html.haml delete mode 100644 app/views/projects/notes/_diff_note_link.html.haml create mode 100644 app/views/projects/notes/_edit_form.html.haml create mode 100644 app/views/projects/project_members/_group_members.html.haml create mode 100644 app/views/projects/project_members/_new_project_member.html.haml create mode 100644 app/views/projects/project_members/_project_member.html.haml create mode 100644 app/views/projects/project_members/_team.html.haml rename app/views/projects/{team_members => project_members}/import.html.haml (53%) create mode 100644 app/views/projects/project_members/index.html.haml create mode 100644 app/views/projects/project_members/update.js.haml create mode 100644 app/views/projects/protected_branches/_branches_list.html.haml delete mode 100644 app/views/projects/repositories/stats.html.haml create mode 100644 app/views/projects/tags/destroy.js.haml delete mode 100644 app/views/projects/team_members/_form.html.haml delete mode 100644 app/views/projects/team_members/_group_members.html.haml delete mode 100644 app/views/projects/team_members/_team.html.haml delete mode 100644 app/views/projects/team_members/_team_member.html.haml delete mode 100644 app/views/projects/team_members/index.html.haml delete mode 100644 app/views/projects/team_members/new.html.haml delete mode 100644 app/views/projects/team_members/update.js.haml create mode 100644 app/views/search/_global_filter.html.haml delete mode 100644 app/views/search/_global_results.html.haml create mode 100644 app/views/search/_project_filter.html.haml delete mode 100644 app/views/search/_project_results.html.haml create mode 100644 app/views/search/_snippet_filter.html.haml create mode 100644 app/views/search/results/_snippet_blob.html.haml create mode 100644 app/views/search/results/_snippet_title.html.haml create mode 100644 app/views/search/results/_wiki_blob.html.haml create mode 100644 app/views/shared/_choose_group_avatar_button.html.haml create mode 100644 app/views/shared/_confirm_modal.html.haml create mode 100644 app/views/shared/_file_highlight.html.haml delete mode 100644 app/views/shared/_file_hljs.html.haml delete mode 100644 app/views/shared/_filter.html.haml create mode 100644 app/views/shared/_group_form.html.haml create mode 100644 app/views/shared/_group_tips.html.haml create mode 100644 app/views/shared/_issuable_filter.html.haml create mode 100644 app/views/shared/_issuable_search_form.html.haml create mode 100644 app/views/shared/_milestones_filter.html.haml create mode 100644 app/views/shared/_no_password.html.haml create mode 100644 app/views/shared/_outdated_browser.html.haml create mode 100644 app/views/shared/_project.html.haml delete mode 100644 app/views/shared/_project_filter.html.haml create mode 100644 app/views/shared/_projects_list.html.haml create mode 100644 app/views/shared/snippets/_visibility_level.html.haml create mode 100644 app/views/users/calendar.html.haml create mode 100644 app/views/users/calendar_activities.html.haml create mode 100644 app/views/users/show.atom.builder delete mode 100644 app/views/users_groups/_users_group.html.haml create mode 100644 app/workers/auto_merge_worker.rb create mode 100644 app/workers/fork_registration_worker.rb create mode 100644 app/workers/irker_worker.rb create mode 100644 app/workers/project_service_worker.rb create mode 100644 app/workers/repository_archive_worker.rb create mode 100755 bin/guard create mode 100644 config/initializers/7_omniauth.rb delete mode 100644 config/initializers/acts_as_taggable_on_patch.rb create mode 100644 config/initializers/disable_email_interceptor.rb create mode 100644 config/initializers/doorkeeper.rb create mode 100644 config/initializers/gitlab_shell_secret_token.rb create mode 100644 config/initializers/public_key.rb create mode 100644 config/initializers/rack_attack_git_basic_auth.rb create mode 100644 config/initializers/redis-store-fix-expiry.rb create mode 100644 config/initializers/static_files.rb create mode 100644 config/initializers/time_zone.rb create mode 100644 config/locales/doorkeeper.en.yml create mode 100644 config/newrelic.yml create mode 100644 db/migrate/20140125162722_add_avatar_to_projects.rb create mode 100644 db/migrate/20140903115954_migrate_to_new_shell.rb create mode 100644 db/migrate/20140907220153_serialize_service_properties.rb create mode 100644 db/migrate/20140914113604_add_members_table.rb create mode 100644 db/migrate/20140914145549_migrate_to_new_members_model.rb create mode 100644 db/migrate/20140914173417_remove_old_member_tables.rb create mode 100644 db/migrate/20141006143943_move_slack_service_to_webhook.rb create mode 100644 db/migrate/20141007100818_add_visibility_level_to_snippet.rb create mode 100644 db/migrate/20141121133009_add_timestamps_to_members.rb create mode 100644 db/migrate/20141121161704_add_identity_table.rb create mode 100644 db/migrate/20141205134006_add_locked_at_to_merge_request.rb create mode 100644 db/migrate/20141216155758_create_doorkeeper_tables.rb create mode 100644 db/migrate/20141217125223_add_owner_to_application.rb create mode 100644 db/migrate/20141223135007_add_import_data_to_project_table.rb create mode 100644 db/migrate/20141226080412_add_developers_can_push_to_protected_branches.rb create mode 100644 db/migrate/20150108073740_create_application_settings.rb create mode 100644 db/migrate/20150116234544_add_home_page_url_for_application_settings.rb create mode 100644 db/migrate/20150116234545_add_gitlab_access_token_to_user.rb create mode 100644 db/migrate/20150125163100_add_default_branch_protection_setting.rb create mode 100644 db/migrate/20150205211843_add_timestamps_to_identities.rb create mode 100644 db/migrate/20150206181414_add_index_to_created_at.rb create mode 100644 db/migrate/20150206222854_add_notification_email_to_user.rb create mode 100644 db/migrate/20150209222013_add_missing_index.rb create mode 100644 db/migrate/20150211172122_add_template_to_service.rb create mode 100644 db/migrate/20150211174341_allow_null_in_services_project_id.rb create mode 100644 db/migrate/20150213104043_add_twitter_sharing_enabled_to_application_settings.rb create mode 100644 db/migrate/20150213114800_add_hide_no_password_to_user.rb create mode 100644 db/migrate/20150213121042_add_password_automatically_set_to_user.rb create mode 100644 db/migrate/20150217123345_add_bitbucket_access_token_and_secret_to_user.rb create mode 100644 db/migrate/20150219004514_add_events_to_services.rb create mode 100644 db/migrate/20150223022001_set_missing_last_activity_at.rb create mode 100644 db/migrate/20150225065047_add_note_events_to_services.rb create mode 100644 db/migrate/20150301014758_add_restricted_visibility_levels_to_application_settings.rb create mode 100644 db/migrate/20150306023106_fix_namespace_duplication.rb create mode 100644 db/migrate/20150306023112_add_unique_index_to_namespace.rb create mode 100644 db/migrate/20150313012111_create_subscriptions_table.rb create mode 100644 db/migrate/20150320234437_add_location_to_user.rb create mode 100644 db/migrate/20150324155957_set_incorrect_assignee_id_to_null.rb create mode 100644 db/migrate/20150327122227_add_public_to_key.rb create mode 100644 db/migrate/20150327150017_add_import_data_to_project.rb create mode 100644 db/migrate/20150328132231_add_max_attachment_size_to_application_settings.rb create mode 100644 db/migrate/20150406133311_add_invite_data_to_member.rb create mode 100644 db/migrate/20150411000035_fix_identities.rb create mode 100644 db/migrate/20150411180045_rename_buildbox_service.rb create mode 100644 db/migrate/20150413192223_add_public_email_to_users.rb create mode 100644 db/migrate/20150417121913_create_project_import_data.rb create mode 100644 db/migrate/20150417122318_remove_import_data_from_project.rb create mode 100644 doc/api/oauth2.md create mode 100644 doc/api/services.md create mode 100644 doc/customization/issue_closing.md create mode 100644 doc/customization/libravatar.md create mode 100644 doc/customization/welcome_message.md create mode 100644 doc/development/ci_setup.md create mode 100644 doc/development/omnibus.md create mode 100644 doc/development/sidekiq_debugging.md create mode 100644 doc/development/ui_guide.md create mode 100644 doc/hooks/custom_hooks.md create mode 100644 doc/integration/bitbucket.md create mode 100644 doc/integration/gitlab.md create mode 100644 doc/integration/gitlab_actions.png create mode 100644 doc/integration/gitlab_app.png create mode 100644 doc/integration/gitlab_buttons_in_gmail.md create mode 100644 doc/integration/oauth_provider.md create mode 100644 doc/integration/oauth_provider/admin_application.png create mode 100644 doc/integration/oauth_provider/application_form.png create mode 100644 doc/integration/oauth_provider/authorized_application.png create mode 100644 doc/integration/oauth_provider/user_wide_applications.png create mode 100644 doc/integration/redmine_configuration.png create mode 100644 doc/integration/redmine_service_template.png create mode 100644 doc/integration/shibboleth.md create mode 100644 doc/logs/logs.md create mode 100644 doc/operations/README.md create mode 100644 doc/operations/cleaning_up_redis_sessions.md create mode 100644 doc/operations/sidekiq_memory_killer.md create mode 100644 doc/project_services/bamboo.md create mode 100644 doc/project_services/hipchat.md create mode 100644 doc/project_services/irker.md create mode 100644 doc/project_services/project_services.md create mode 100644 doc/release/howto_rc1.md create mode 100644 doc/release/howto_update_guides.md create mode 100644 doc/security/information_exclusivity.md create mode 100644 doc/security/webhooks.md delete mode 100644 doc/ssh/deploy_keys.md delete mode 100644 doc/ssh/ssh.md delete mode 100644 doc/update/6.0-to-7.1.md delete mode 100644 doc/update/6.0-to-7.2.md create mode 100644 doc/update/6.x-or-7.x-to-7.10.md create mode 100644 doc/update/7.2-to-7.3.md create mode 100644 doc/update/7.3-to-7.4.md create mode 100644 doc/update/7.4-to-7.5.md create mode 100644 doc/update/7.5-to-7.6.md create mode 100644 doc/update/7.6-to-7.7.md create mode 100644 doc/update/7.7-to-7.8.md create mode 100644 doc/update/7.8-to-7.9.md create mode 100644 doc/workflow/ci_mr.png create mode 100644 doc/workflow/close_issue_mr.png create mode 100644 doc/workflow/environment_branches.png create mode 100644 doc/workflow/forking/branch_select.png create mode 100644 doc/workflow/forking/fork_button.png create mode 100644 doc/workflow/forking/groups.png create mode 100644 doc/workflow/forking/merge_request.png create mode 100644 doc/workflow/forking_workflow.md create mode 100644 doc/workflow/four_stages.png create mode 100644 doc/workflow/git_pull.png create mode 100644 doc/workflow/gitdashflow.png create mode 100644 doc/workflow/github_flow.png create mode 100644 doc/workflow/github_importer/importer.png create mode 100644 doc/workflow/github_importer/new_project_page.png create mode 100644 doc/workflow/gitlab_flow.md create mode 100644 doc/workflow/gitlab_flow.png create mode 100644 doc/workflow/gitlab_importer/importer.png create mode 100644 doc/workflow/gitlab_importer/new_project_page.png create mode 100644 doc/workflow/good_commit.png create mode 100644 doc/workflow/import_projects_from_github.md create mode 100644 doc/workflow/import_projects_from_gitlab_com.md create mode 100644 doc/workflow/merge_commits.png create mode 100644 doc/workflow/merge_request.png create mode 100644 doc/workflow/messy_flow.png create mode 100644 doc/workflow/migrating_from_svn.md create mode 100644 doc/workflow/mr_inline_comments.png create mode 100644 doc/workflow/notifications.md create mode 100644 doc/workflow/notifications/settings.png create mode 100644 doc/workflow/production_branch.png create mode 100644 doc/workflow/protected_branches.md create mode 100644 doc/workflow/protected_branches/protected_branches1.png create mode 100644 doc/workflow/protected_branches/protected_branches2.png create mode 100644 doc/workflow/rebase.png create mode 100644 doc/workflow/release_branches.png create mode 100644 doc/workflow/remove_checkbox.png create mode 100644 doc/workflow/voting_slider.png create mode 100644 doc/workflow/web_editor.md create mode 100644 doc/workflow/web_editor/edit_file.png create mode 100644 doc/workflow/web_editor/empty_project.png create mode 100644 doc/workflow/web_editor/new_file.png create mode 100644 doc/workflow/web_editor/show_file.png create mode 100644 docker/.dockerignore create mode 100644 docker/Dockerfile create mode 100644 docker/README.md create mode 100755 docker/assets/wrapper create mode 100644 docker/data/Dockerfile create mode 100644 docker/data/assets/gitlab.rb create mode 100644 docker/troubleshooting.md create mode 100644 features/admin/applications.feature create mode 100644 features/admin/deploy_keys.feature create mode 100644 features/admin/settings.feature rename features/{profile => dashboard}/group.feature (73%) create mode 100644 features/dashboard/new_project.feature delete mode 100644 features/dashboard/projects.feature delete mode 100644 features/dashboard/search.feature create mode 100644 features/dashboard/shortcuts.feature create mode 100644 features/dashboard/starred_projects.feature rename features/explore/{public_groups.feature => groups.feature} (97%) rename features/{group.feature => groups.feature} (91%) create mode 100644 features/invites.feature delete mode 100644 features/project/edit_issuetracker.feature rename features/project/{network.feature => network_graph.feature} (100%) create mode 100644 features/project/shortcuts.feature create mode 100644 features/search.feature create mode 100644 features/snippet_search.feature create mode 100644 features/snippets/public_snippets.feature create mode 100644 features/steps/admin/applications.rb create mode 100644 features/steps/admin/deploy_keys.rb create mode 100644 features/steps/admin/settings.rb rename features/steps/dashboard/{with_archived_projects.rb => archived_projects.rb} (61%) rename features/steps/{profile => dashboard}/group.rb (57%) rename features/steps/{ => dashboard}/help.rb (90%) create mode 100644 features/steps/dashboard/new_project.rb delete mode 100644 features/steps/dashboard/projects.rb delete mode 100644 features/steps/dashboard/search.rb create mode 100644 features/steps/dashboard/shortcuts.rb create mode 100644 features/steps/dashboard/starred_projects.rb rename features/steps/explore/{groups_feature.rb => groups.rb} (92%) rename features/steps/{group/group.rb => groups.rb} (75%) create mode 100644 features/steps/invites.rb delete mode 100644 features/steps/project/browse_branches.rb delete mode 100644 features/steps/project/browse_commits.rb delete mode 100644 features/steps/project/browse_files.rb delete mode 100644 features/steps/project/browse_tags.rb create mode 100644 features/steps/project/commits/branches.rb rename features/steps/project/{comments_on_commits.rb => commits/comments.rb} (58%) create mode 100644 features/steps/project/commits/commits.rb rename features/steps/project/{comments_on_commit_diffs.rb => commits/diff_comments.rb} (58%) create mode 100644 features/steps/project/commits/tags.rb rename features/steps/project/{browse_commits_user_lookup.rb => commits/user_lookup.rb} (77%) delete mode 100644 features/steps/project/issue_tracker.rb delete mode 100644 features/steps/project/issues.rb rename features/steps/project/{ => issues}/filter_labels.rb (75%) create mode 100644 features/steps/project/issues/issues.rb rename features/steps/project/{ => issues}/labels.rb (74%) rename features/steps/project/{ => issues}/milestones.rb (72%) delete mode 100644 features/steps/project/markdown_render.rb create mode 100644 features/steps/project/project_shortcuts.rb create mode 100644 features/steps/project/source/browse_files.rb rename features/steps/project/{browse_git_repo.rb => source/git_blame.rb} (54%) create mode 100644 features/steps/project/source/markdown_render.rb rename features/steps/project/{ => source}/multiselect_blob.rb (88%) rename features/steps/project/{ => source}/search_code.rb (72%) create mode 100644 features/steps/search.rb create mode 100644 features/steps/shared/issuable.rb create mode 100644 features/steps/shared/project_tab.rb create mode 100644 features/steps/shared/search.rb create mode 100644 features/steps/shared/shortcuts.rb create mode 100644 features/steps/snippet_search.rb create mode 100644 features/steps/snippets/public_snippets.rb create mode 100644 lib/api/api_guard.rb create mode 100644 lib/api/group_members.rb create mode 100644 lib/disable_email_interceptor.rb create mode 100644 lib/gitlab.rb create mode 100644 lib/gitlab/backend/rack_attack_helpers.rb create mode 100644 lib/gitlab/bitbucket_import.rb create mode 100644 lib/gitlab/bitbucket_import/client.rb create mode 100644 lib/gitlab/bitbucket_import/importer.rb create mode 100644 lib/gitlab/bitbucket_import/key_adder.rb create mode 100644 lib/gitlab/bitbucket_import/key_deleter.rb create mode 100644 lib/gitlab/bitbucket_import/project_creator.rb create mode 100644 lib/gitlab/contributions_calendar.rb rename lib/gitlab/{contributors.rb => contributor.rb} (100%) create mode 100644 lib/gitlab/current_settings.rb create mode 100644 lib/gitlab/diff/file.rb create mode 100644 lib/gitlab/diff/line.rb create mode 100644 lib/gitlab/diff/line_code.rb create mode 100644 lib/gitlab/diff/parser.rb delete mode 100644 lib/gitlab/diff_parser.rb create mode 100644 lib/gitlab/force_push_check.rb create mode 100644 lib/gitlab/git.rb create mode 100644 lib/gitlab/git_access_status.rb create mode 100644 lib/gitlab/git_access_wiki.rb create mode 100644 lib/gitlab/git_ref_validator.rb create mode 100644 lib/gitlab/github_import/client.rb create mode 100644 lib/gitlab/github_import/importer.rb create mode 100644 lib/gitlab/github_import/project_creator.rb create mode 100644 lib/gitlab/gitlab_import/client.rb create mode 100644 lib/gitlab/gitlab_import/importer.rb create mode 100644 lib/gitlab/gitlab_import/project_creator.rb create mode 100644 lib/gitlab/gitorious_import/client.rb create mode 100644 lib/gitlab/gitorious_import/project_creator.rb create mode 100644 lib/gitlab/gitorious_import/repository.rb create mode 100644 lib/gitlab/google_code_import/client.rb create mode 100644 lib/gitlab/google_code_import/importer.rb create mode 100644 lib/gitlab/google_code_import/project_creator.rb create mode 100644 lib/gitlab/google_code_import/repository.rb create mode 100644 lib/gitlab/graphs/commits.rb create mode 100644 lib/gitlab/import_formatter.rb create mode 100644 lib/gitlab/key_fingerprint.rb create mode 100644 lib/gitlab/ldap/authentication.rb create mode 100644 lib/gitlab/ldap/config.rb create mode 100644 lib/gitlab/middleware/static.rb create mode 100644 lib/gitlab/note_data_builder.rb create mode 100644 lib/gitlab/o_auth/auth_hash.rb create mode 100644 lib/gitlab/o_auth/user.rb delete mode 100644 lib/gitlab/oauth/user.rb create mode 100644 lib/gitlab/production_logger.rb create mode 100644 lib/gitlab/project_search_results.rb create mode 100644 lib/gitlab/push_data_builder.rb create mode 100644 lib/gitlab/search_results.rb create mode 100644 lib/gitlab/sidekiq_logger.rb create mode 100644 lib/gitlab/sidekiq_middleware/memory_killer.rb create mode 100644 lib/gitlab/snippet_search_results.rb create mode 100644 lib/gitlab/utils.rb create mode 100644 lib/repository_cache.rb create mode 100644 lib/tasks/brakeman.rake create mode 100644 lib/tasks/gitlab/db/drop_all_postgres_sequences.rake create mode 100644 lib/tasks/gitlab/mail_google_schema_whitelisting.rake create mode 100644 lib/tasks/gitlab/sidekiq.rake create mode 100644 lib/tasks/rubocop.rake create mode 100644 safe/public.pem create mode 100644 spec/controllers/autocomplete_controller_spec.rb create mode 100644 spec/controllers/branches_controller_spec.rb create mode 100644 spec/controllers/help_controller_spec.rb create mode 100644 spec/controllers/import/bitbucket_controller_spec.rb create mode 100644 spec/controllers/import/github_controller_spec.rb create mode 100644 spec/controllers/import/gitlab_controller_spec.rb create mode 100644 spec/controllers/import/gitorious_controller_spec.rb create mode 100644 spec/controllers/import/google_code_controller_spec.rb create mode 100644 spec/controllers/namespaces_controller_spec.rb create mode 100644 spec/controllers/projects/protected_branches_controller_spec.rb create mode 100644 spec/controllers/projects/refs_controller_spec.rb create mode 100644 spec/controllers/projects/repositories_controller_spec.rb create mode 100644 spec/controllers/projects/uploads_controller_spec.rb create mode 100644 spec/controllers/uploads_controller_spec.rb create mode 100644 spec/controllers/users_controller_spec.rb rename spec/factories/{users_groups.rb => group_members.rb} (81%) create mode 100644 spec/features/atom/users_spec.rb create mode 100644 spec/features/help_pages_spec.rb create mode 100644 spec/finders/snippets_finder_spec.rb create mode 100644 spec/fixtures/GoogleCodeProjectHosting.json create mode 100644 spec/helpers/diff_helper_spec.rb create mode 100644 spec/helpers/events_helper_spec.rb create mode 100644 spec/helpers/groups_helper.rb create mode 100644 spec/helpers/icons_helper_spec.rb create mode 100644 spec/helpers/nav_helper_spec.rb create mode 100644 spec/helpers/oauth_helper_spec.rb create mode 100644 spec/helpers/tree_helper_spec.rb delete mode 100644 spec/lib/auth_spec.rb create mode 100644 spec/lib/disable_email_interceptor_spec.rb create mode 100644 spec/lib/file_size_validator_spec.rb create mode 100644 spec/lib/git_ref_validator_spec.rb create mode 100644 spec/lib/gitlab/auth_spec.rb create mode 100644 spec/lib/gitlab/backend/grack_auth_spec.rb create mode 100644 spec/lib/gitlab/backend/rack_attack_helpers_spec.rb create mode 100644 spec/lib/gitlab/bitbucket_import/client_spec.rb create mode 100644 spec/lib/gitlab/bitbucket_import/project_creator_spec.rb create mode 100644 spec/lib/gitlab/closing_issue_extractor_spec.rb create mode 100644 spec/lib/gitlab/diff/file_spec.rb create mode 100644 spec/lib/gitlab/diff/parser_spec.rb create mode 100644 spec/lib/gitlab/git_access_spec.rb create mode 100644 spec/lib/gitlab/git_access_wiki_spec.rb create mode 100644 spec/lib/gitlab/github_import/client_spec.rb create mode 100644 spec/lib/gitlab/github_import/project_creator_spec.rb create mode 100644 spec/lib/gitlab/gitlab_import/client_spec.rb create mode 100644 spec/lib/gitlab/gitlab_import/project_creator_spec.rb create mode 100644 spec/lib/gitlab/gitorious_import/project_creator_spec.rb create mode 100644 spec/lib/gitlab/google_code_import/client_spec.rb create mode 100644 spec/lib/gitlab/google_code_import/importer_spec.rb create mode 100644 spec/lib/gitlab/google_code_import/project_creator_spec.rb create mode 100644 spec/lib/gitlab/key_fingerprint_spec.rb create mode 100644 spec/lib/gitlab/ldap/access_spec.rb rename spec/lib/gitlab/ldap/{ldap_adapter_spec.rb => adapter_spec.rb} (78%) create mode 100644 spec/lib/gitlab/ldap/authentication_spec.rb create mode 100644 spec/lib/gitlab/ldap/config_spec.rb delete mode 100644 spec/lib/gitlab/ldap/ldap_access_spec.rb delete mode 100644 spec/lib/gitlab/ldap/ldap_user_auth_spec.rb create mode 100644 spec/lib/gitlab/ldap/user_spec.rb create mode 100644 spec/lib/gitlab/note_data_builder_spec.rb create mode 100644 spec/lib/gitlab/o_auth/auth_hash_spec.rb create mode 100644 spec/lib/gitlab/o_auth/user_spec.rb create mode 100644 spec/lib/gitlab/push_data_builder_spec.rb delete mode 100644 spec/lib/oauth_spec.rb create mode 100644 spec/lib/repository_cache_spec.rb create mode 100644 spec/models/application_setting_spec.rb delete mode 100644 spec/models/assembla_service_spec.rb create mode 100644 spec/models/concerns/mentionable_spec.rb create mode 100644 spec/models/external_wiki_service_spec.rb delete mode 100644 spec/models/flowdock_service_spec.rb delete mode 100644 spec/models/gemnasium_service_spec.rb delete mode 100644 spec/models/gitlab_ci_service_spec.rb rename spec/models/{ => hooks}/project_hook_spec.rb (100%) rename spec/models/{ => hooks}/service_hook_spec.rb (94%) create mode 100644 spec/models/hooks/system_hook_spec.rb rename spec/models/{ => hooks}/web_hook_spec.rb (59%) create mode 100644 spec/models/member_spec.rb create mode 100644 spec/models/members/group_member_spec.rb create mode 100644 spec/models/members/project_member_spec.rb create mode 100644 spec/models/project_services/asana_service_spec.rb create mode 100644 spec/models/project_services/assembla_service_spec.rb create mode 100644 spec/models/project_services/buildkite_service_spec.rb create mode 100644 spec/models/project_services/flowdock_service_spec.rb create mode 100644 spec/models/project_services/gemnasium_service_spec.rb create mode 100644 spec/models/project_services/gitlab_ci_service_spec.rb create mode 100644 spec/models/project_services/gitlab_issue_tracker_service_spec.rb create mode 100644 spec/models/project_services/hipchat_service_spec.rb create mode 100644 spec/models/project_services/irker_service_spec.rb create mode 100644 spec/models/project_services/jira_service_spec.rb create mode 100644 spec/models/project_services/pushover_service_spec.rb create mode 100644 spec/models/project_services/slack_service/issue_message_spec.rb create mode 100644 spec/models/project_services/slack_service/merge_message_spec.rb create mode 100644 spec/models/project_services/slack_service/note_message_spec.rb rename spec/models/{slack_message_spec.rb => project_services/slack_service/push_message_spec.rb} (50%) create mode 100644 spec/models/project_services/slack_service_spec.rb create mode 100644 spec/models/repository_spec.rb delete mode 100644 spec/models/slack_service_spec.rb delete mode 100644 spec/models/system_hook_spec.rb delete mode 100644 spec/models/users_group_spec.rb delete mode 100644 spec/models/users_project_spec.rb create mode 100644 spec/requests/api/doorkeeper_access_spec.rb create mode 100644 spec/requests/api/fork_spec.rb create mode 100644 spec/requests/api/group_members_spec.rb create mode 100644 spec/services/archive_repository_service_spec.rb create mode 100644 spec/services/create_snippet_service_spec.rb delete mode 100644 spec/services/fork_service_spec.rb delete mode 100644 spec/services/issues/bulk_update_context_spec.rb create mode 100644 spec/services/issues/bulk_update_service_spec.rb create mode 100644 spec/services/merge_requests/merge_service_spec.rb create mode 100644 spec/services/merge_requests/refresh_service_spec.rb create mode 100644 spec/services/merge_requests/reopen_service_spec.rb create mode 100644 spec/services/projects/fork_service_spec.rb delete mode 100644 spec/services/projects/image_service_spec.rb create mode 100644 spec/services/projects/upload_service_spec.rb create mode 100644 spec/services/update_snippet_service_spec.rb create mode 100644 spec/support/taskable_shared_examples.rb create mode 100644 spec/tasks/gitlab/mail_google_schema_whitelisting.rb create mode 100644 spec/workers/fork_registration_worker_spec.rb create mode 100644 spec/workers/repository_archive_worker_spec.rb create mode 100644 vendor/assets/javascripts/chart-lib.min.js delete mode 100644 vendor/assets/javascripts/highlight.pack.js create mode 100644 vendor/assets/javascripts/jquery.sticky-kit.min.js delete mode 100644 vendor/assets/stylesheets/highlightjs.min.css delete mode 100644 vendor/plugins/.gitkeep diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..7e800609e6 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +CHANGELOG merge=union \ No newline at end of file diff --git a/.gitignore b/.gitignore index 92ca729dc1..7a7b5c9393 100644 --- a/.gitignore +++ b/.gitignore @@ -1,40 +1,42 @@ -.bundle -.rbx/ -db/*.sqlite3 -db/*.sqlite3-journal -log/*.log* -tmp/ -.sass-cache/ -coverage/* -backups/* +*.log *.swp -public/uploads/ -.ruby-version -.ruby-gemset -.rvmrc -.rbenv-version +.DS_Store +.bundle +.chef .directory -nohup.out -Vagrantfile +.envrc +.gitlab_shell_secret +.idea +.rbenv-version +.rbx/ +.ruby-gemset +.ruby-version +.rvmrc +.sass-cache/ +.secret .vagrant -config/gitlab.yml +Vagrantfile +backups/* +config/aws.yml config/database.yml +config/gitlab.yml config/initializers/omniauth.rb config/initializers/rack_attack.rb config/initializers/smtp_settings.rb -config/unicorn.rb config/resque.yml -config/aws.yml +config/unicorn.rb +coverage/* +db/*.sqlite3 +db/*.sqlite3-journal db/data.yml -.idea -.DS_Store -.chef -vendor/bundle/* -rails_best_practices_output.html doc/code/* -.secret -*.log -public/uploads.* -public/assets/ -.envrc dump.rdb +log/*.log* +nohup.out +public/assets/ +public/uploads.* +public/uploads/ +rails_best_practices_output.html +tags +tmp/ +vendor/bundle/* diff --git a/.pkgr.yml b/.pkgr.yml index 97d78b6ef6..8fc9fddf8f 100644 --- a/.pkgr.yml +++ b/.pkgr.yml @@ -1,10 +1,15 @@ user: git group: git +services: + - postgres before_precompile: ./bin/pkgr_before_precompile.sh targets: debian-7: &wheezy build_dependencies: + - libkrb5-dev - libicu-dev + - cmake + - pkg-config dependencies: - libicu48 - libpcre3 @@ -12,14 +17,20 @@ targets: ubuntu-12.04: *wheezy ubuntu-14.04: build_dependencies: + - libkrb5-dev - libicu-dev + - cmake + - pkg-config dependencies: - libicu52 - libpcre3 - git centos-6: build_dependencies: + - krb5-devel - libicu-devel + - cmake + - pkgconfig dependencies: - libicu - pcre diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000000..03b78d6884 --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,1006 @@ +Style/AccessModifierIndentation: + Description: Check indentation of private/protected visibility modifiers. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#indent-public-private-protected' + Enabled: true + +Style/AccessorMethodName: + Description: Check the naming of accessor methods for get_/set_. + Enabled: false + +Style/Alias: + Description: 'Use alias_method instead of alias.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#alias-method' + Enabled: true + +Style/AlignArray: + Description: >- + Align the elements of an array literal if they span more than + one line. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#align-multiline-arrays' + Enabled: true + +Style/AlignHash: + Description: >- + Align the elements of a hash literal if they span more than + one line. + Enabled: true + +Style/AlignParameters: + Description: >- + Align the parameters of a method call if they span more + than one line. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-double-indent' + Enabled: false + +Style/AndOr: + Description: 'Use &&/|| instead of and/or.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-and-or-or' + Enabled: false + +Style/ArrayJoin: + Description: 'Use Array#join instead of Array#*.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#array-join' + Enabled: false + +Style/AsciiComments: + Description: 'Use only ascii symbols in comments.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-comments' + Enabled: true + +Style/AsciiIdentifiers: + Description: 'Use only ascii symbols in identifiers.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#english-identifiers' + Enabled: true + +Style/Attr: + Description: 'Checks for uses of Module#attr.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr' + Enabled: false + +Style/BeginBlock: + Description: 'Avoid the use of BEGIN blocks.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-BEGIN-blocks' + Enabled: true + +Style/BarePercentLiterals: + Description: 'Checks if usage of %() or %Q() matches configuration.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q-shorthand' + Enabled: false + +Style/BlockComments: + Description: 'Do not use block comments.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-block-comments' + Enabled: false + +Style/BlockEndNewline: + Description: 'Put end statement of multiline block on its own line.' + Enabled: true + +Style/Blocks: + Description: >- + Avoid using {...} for multi-line blocks (multiline chaining is + always ugly). + Prefer {...} over do...end for single-line blocks. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks' + Enabled: true + +Style/BracesAroundHashParameters: + Description: 'Enforce braces style around hash parameters.' + Enabled: false + +Style/CaseEquality: + Description: 'Avoid explicit use of the case equality operator(===).' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-case-equality' + Enabled: false + +Style/CaseIndentation: + Description: 'Indentation of when in a case/when/[else/]end.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#indent-when-to-case' + Enabled: true + +Style/CharacterLiteral: + Description: 'Checks for uses of character literals.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-character-literals' + Enabled: true + +Style/ClassAndModuleCamelCase: + Description: 'Use CamelCase for classes and modules.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#camelcase-classes' + Enabled: true + +Style/ClassAndModuleChildren: + Description: 'Checks style of children classes and modules.' + Enabled: false + +Style/ClassCheck: + Description: 'Enforces consistent use of `Object#is_a?` or `Object#kind_of?`.' + Enabled: false + +Style/ClassMethods: + Description: 'Use self when defining module/class methods.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#def-self-singletons' + Enabled: false + +Style/ClassVars: + Description: 'Avoid the use of class variables.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-class-vars' + Enabled: true + +Style/ColonMethodCall: + Description: 'Do not use :: for method call.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#double-colons' + Enabled: false + +Style/CommentAnnotation: + Description: >- + Checks formatting of special comments + (TODO, FIXME, OPTIMIZE, HACK, REVIEW). + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#annotate-keywords' + Enabled: false + +Style/CommentIndentation: + Description: 'Indentation of comments.' + Enabled: true + +Style/ConstantName: + Description: 'Constants should use SCREAMING_SNAKE_CASE.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#screaming-snake-case' + Enabled: true + +Style/DefWithParentheses: + Description: 'Use def with parentheses when there are arguments.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens' + Enabled: false + +Style/DeprecatedHashMethods: + Description: 'Checks for use of deprecated Hash methods.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-key' + Enabled: false + +Style/Documentation: + Description: 'Document classes and non-namespace modules.' + Enabled: false + +Style/DotPosition: + Description: 'Checks the position of the dot in multi-line method calls.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-multi-line-chains' + Enabled: false + +Style/DoubleNegation: + Description: 'Checks for uses of double negation (!!).' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-bang-bang' + Enabled: false + +Style/EachWithObject: + Description: 'Prefer `each_with_object` over `inject` or `reduce`.' + Enabled: false + +Style/ElseAlignment: + Description: 'Align elses and elsifs correctly.' + Enabled: true + +Style/EmptyElse: + Description: 'Avoid empty else-clauses.' + Enabled: false + +Style/EmptyLineBetweenDefs: + Description: 'Use empty lines between defs.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#empty-lines-between-methods' + Enabled: false + +Style/EmptyLines: + Description: "Don't use several empty lines in a row." + Enabled: false + +Style/EmptyLinesAroundAccessModifier: + Description: "Keep blank lines around access modifiers." + Enabled: false + +Style/EmptyLinesAroundBlockBody: + Description: "Keeps track of empty lines around block bodies." + Enabled: false + +Style/EmptyLinesAroundClassBody: + Description: "Keeps track of empty lines around class bodies." + Enabled: false + +Style/EmptyLinesAroundModuleBody: + Description: "Keeps track of empty lines around module bodies." + Enabled: false + +Style/EmptyLinesAroundMethodBody: + Description: "Keeps track of empty lines around method bodies." + Enabled: false + +Style/EmptyLiteral: + Description: 'Prefer literals to Array.new/Hash.new/String.new.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#literal-array-hash' + Enabled: false + +Style/EndBlock: + Description: 'Avoid the use of END blocks.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-END-blocks' + Enabled: false + +Style/EndOfLine: + Description: 'Use Unix-style line endings.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#crlf' + Enabled: false + +Style/EvenOdd: + Description: 'Favor the use of Fixnum#even? && Fixnum#odd?' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods' + Enabled: false + +Style/FileName: + Description: 'Use snake_case for source file names.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-files' + Enabled: false + +Style/FlipFlop: + Description: 'Checks for flip flops' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-flip-flops' + Enabled: false + +Style/For: + Description: 'Checks use of for or each in multiline loops.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-for-loops' + Enabled: false + +Style/FormatString: + Description: 'Enforce the use of Kernel#sprintf, Kernel#format or String#%.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#sprintf' + Enabled: false + +Style/GlobalVars: + Description: 'Do not introduce global variables.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#instance-vars' + Enabled: false + +Style/GuardClause: + Description: 'Check for conditionals that can be replaced with guard clauses' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals' + Enabled: false + +Style/HashSyntax: + Description: >- + Prefer Ruby 1.9 hash syntax { a: 1, b: 2 } over 1.8 syntax + { :a => 1, :b => 2 }. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-literals' + Enabled: true + +Style/IfUnlessModifier: + Description: >- + Favor modifier if/unless usage when you have a + single-line body. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#if-as-a-modifier' + Enabled: false + +Style/IfWithSemicolon: + Description: 'Do not use if x; .... Use the ternary operator instead.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon-ifs' + Enabled: false + +Style/IndentationConsistency: + Description: 'Keep indentation straight.' + Enabled: true + +Style/IndentationWidth: + Description: 'Use 2 spaces for indentation.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation' + Enabled: true + +Style/IndentArray: + Description: >- + Checks the indentation of the first element in an array + literal. + Enabled: false + +Style/IndentHash: + Description: 'Checks the indentation of the first key in a hash literal.' + Enabled: false + +Style/InfiniteLoop: + Description: 'Use Kernel#loop for infinite loops.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#infinite-loop' + Enabled: false + +Style/Lambda: + Description: 'Use the new lambda literal syntax for single-line blocks.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#lambda-multi-line' + Enabled: false + +Style/LambdaCall: + Description: 'Use lambda.call(...) instead of lambda.(...).' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc-call' + Enabled: false + +Style/LeadingCommentSpace: + Description: 'Comments should start with a space.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#hash-space' + Enabled: false + +Style/LineEndConcatenation: + Description: >- + Use \ instead of + or << to concatenate two string literals at + line end. + Enabled: false + +Style/MethodCallParentheses: + Description: 'Do not use parentheses for method calls with no arguments.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-args-no-parens' + Enabled: false + +Style/MethodDefParentheses: + Description: >- + Checks if the method definitions have or don't have + parentheses. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#method-parens' + Enabled: false + +Style/MethodName: + Description: 'Use the configured style when naming methods.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars' + Enabled: false + +Style/ModuleFunction: + Description: 'Checks for usage of `extend self` in modules.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#module-function' + Enabled: false + +Style/MultilineBlockChain: + Description: 'Avoid multi-line chains of blocks.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#single-line-blocks' + Enabled: false + +Style/MultilineBlockLayout: + Description: 'Ensures newlines after multiline block do statements.' + Enabled: true + +Style/MultilineIfThen: + Description: 'Do not use then for multi-line if/unless.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-then' + Enabled: false + +Style/MultilineOperationIndentation: + Description: >- + Checks indentation of binary operations that span more than + one line. + Enabled: false + +Style/MultilineTernaryOperator: + Description: >- + Avoid multi-line ?: (the ternary operator); + use if/unless instead. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-multiline-ternary' + Enabled: false + +Style/NegatedIf: + Description: >- + Favor unless over if for negative conditions + (or control flow or). + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#unless-for-negatives' + Enabled: false + +Style/NegatedWhile: + Description: 'Favor until over while for negative conditions.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#until-for-negatives' + Enabled: false + +Style/NestedTernaryOperator: + Description: 'Use one expression per branch in a ternary operator.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-ternary' + Enabled: true + +Style/Next: + Description: 'Use `next` to skip iteration instead of a condition at the end.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-nested-conditionals' + Enabled: false + +Style/NilComparison: + Description: 'Prefer x.nil? to x == nil.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#predicate-methods' + Enabled: true + +Style/NonNilCheck: + Description: 'Checks for redundant nil checks.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-non-nil-checks' + Enabled: true + +Style/Not: + Description: 'Use ! instead of not.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bang-not-not' + Enabled: true + +Style/NumericLiterals: + Description: >- + Add underscores to large numeric literals to improve their + readability. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscores-in-numerics' + Enabled: false + +Style/OneLineConditional: + Description: >- + Favor the ternary operator(?:) over + if/then/else/end constructs. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#ternary-operator' + Enabled: true + +Style/OpMethod: + Description: 'When defining binary operators, name the argument other.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#other-arg' + Enabled: false + +Style/ParenthesesAroundCondition: + Description: >- + Don't use parentheses around the condition of an + if/unless/while. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-parens-if' + Enabled: true + +Style/PercentLiteralDelimiters: + Description: 'Use `%`-literal delimiters consistently' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-literal-braces' + Enabled: false + +Style/PercentQLiterals: + Description: 'Checks if uses of %Q/%q match the configured preference.' + Enabled: false + +Style/PerlBackrefs: + Description: 'Avoid Perl-style regex back references.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-perl-regexp-last-matchers' + Enabled: false + +Style/PredicateName: + Description: 'Check the names of predicate methods.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#bool-methods-qmark' + Enabled: false + +Style/Proc: + Description: 'Use proc instead of Proc.new.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#proc' + Enabled: false + +Style/RaiseArgs: + Description: 'Checks the arguments passed to raise/fail.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#exception-class-messages' + Enabled: false + +Style/RedundantBegin: + Description: "Don't use begin blocks when they are not needed." + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#begin-implicit' + Enabled: false + +Style/RedundantException: + Description: "Checks for an obsolete RuntimeException argument in raise/fail." + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-explicit-runtimeerror' + Enabled: false + +Style/RedundantReturn: + Description: "Don't use return where it's not required." + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-explicit-return' + Enabled: true + +Style/RedundantSelf: + Description: "Don't use self where it's not needed." + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-self-unless-required' + Enabled: false + +Style/RegexpLiteral: + Description: >- + Use %r for regular expressions matching more than + `MaxSlashes` '/' characters. + Use %r only for regular expressions matching more than + `MaxSlashes` '/' character. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-r' + Enabled: false + +Style/RescueModifier: + Description: 'Avoid using rescue in its modifier form.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-rescue-modifiers' + Enabled: false + +Style/SelfAssignment: + Description: >- + Checks for places where self-assignment shorthand should have + been used. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#self-assignment' + Enabled: false + +Style/Semicolon: + Description: "Don't use semicolons to terminate expressions." + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-semicolon' + Enabled: false + +Style/SignalException: + Description: 'Checks for proper usage of fail and raise.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#fail-method' + Enabled: false + +Style/SingleLineBlockParams: + Description: 'Enforces the names of some block params.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#reduce-blocks' + Enabled: false + +Style/SingleLineMethods: + Description: 'Avoid single-line methods.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-single-line-methods' + Enabled: false + +Style/SingleSpaceBeforeFirstArg: + Description: >- + Checks that exactly one space is used between a method name + and the first argument for method calls without parentheses. + Enabled: false + +Style/SpaceAfterColon: + Description: 'Use spaces after colons.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' + Enabled: false + +Style/SpaceAfterComma: + Description: 'Use spaces after commas.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' + Enabled: false + +Style/SpaceAfterControlKeyword: + Description: 'Use spaces after if/elsif/unless/while/until/case/when.' + Enabled: false + +Style/SpaceAfterMethodName: + Description: >- + Do not put a space between a method name and the opening + parenthesis in a method definition. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces' + Enabled: false + +Style/SpaceAfterNot: + Description: Tracks redundant space after the ! operator. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-space-bang' + Enabled: false + +Style/SpaceAfterSemicolon: + Description: 'Use spaces after semicolons.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' + Enabled: false + +Style/SpaceBeforeBlockBraces: + Description: >- + Checks that the left block brace has or doesn't have space + before it. + Enabled: false + +Style/SpaceBeforeComma: + Description: 'No spaces before commas.' + Enabled: false + +Style/SpaceBeforeComment: + Description: >- + Checks for missing space between code and a comment on the + same line. + Enabled: false + +Style/SpaceBeforeSemicolon: + Description: 'No spaces before semicolons.' + Enabled: false + +Style/SpaceInsideBlockBraces: + Description: >- + Checks that block braces have or don't have surrounding space. + For blocks taking parameters, checks that the left brace has + or doesn't have trailing space. + Enabled: false + +Style/SpaceAroundEqualsInParameterDefault: + Description: >- + Checks that the equals signs in parameter default assignments + have or don't have surrounding space depending on + configuration. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-around-equals' + Enabled: false + +Style/SpaceAroundOperators: + Description: 'Use spaces around operators.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' + Enabled: false + +Style/SpaceBeforeModifierKeyword: + Description: 'Put a space before the modifier keyword.' + Enabled: false + +Style/SpaceInsideBrackets: + Description: 'No spaces after [ or before ].' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces' + Enabled: false + +Style/SpaceInsideHashLiteralBraces: + Description: "Use spaces inside hash literal braces - or don't." + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-operators' + Enabled: true + +Style/SpaceInsideParens: + Description: 'No spaces after ( or before ).' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-spaces-braces' + Enabled: false + +Style/SpaceInsideRangeLiteral: + Description: 'No spaces inside range literals.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-space-inside-range-literals' + Enabled: false + +Style/SpecialGlobalVars: + Description: 'Avoid Perl-style global variables.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-cryptic-perlisms' + Enabled: false + +Style/StringLiterals: + Description: 'Checks if uses of quotes match the configured preference.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#consistent-string-literals' + Enabled: false + +Style/StringLiteralsInInterpolation: + Description: >- + Checks if uses of quotes inside expressions in interpolated + strings match the configured preference. + Enabled: false + +Style/SymbolProc: + Description: 'Use symbols as procs instead of blocks when possible.' + Enabled: false + +Style/Tab: + Description: 'No hard tabs.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#spaces-indentation' + Enabled: true + +Style/TrailingBlankLines: + Description: 'Checks trailing blank lines and final newline.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#newline-eof' + Enabled: true + +Style/TrailingComma: + Description: 'Checks for trailing comma in parameter lists and literals.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-array-commas' + Enabled: false + +Style/TrailingWhitespace: + Description: 'Avoid trailing whitespace.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-trailing-whitespace' + Enabled: false + +Style/TrivialAccessors: + Description: 'Prefer attr_* methods to trivial readers/writers.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#attr_family' + Enabled: false + +Style/UnlessElse: + Description: >- + Do not use unless with else. Rewrite these with the positive + case first. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-else-with-unless' + Enabled: false + +Style/UnneededCapitalW: + Description: 'Checks for %W when interpolation is not needed.' + Enabled: false + +Style/UnneededPercentQ: + Description: 'Checks for %q/%Q when single quotes or double quotes would do.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-q' + Enabled: false + +Style/UnneededPercentX: + Description: 'Checks for %x when `` would do.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-x' + Enabled: false + +Style/VariableInterpolation: + Description: >- + Don't interpolate global, instance and class variables + directly in strings. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#curlies-interpolate' + Enabled: false + +Style/VariableName: + Description: 'Use the configured style when naming variables.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#snake-case-symbols-methods-vars' + Enabled: false + +Style/WhenThen: + Description: 'Use when x then ... for one-line cases.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#one-line-cases' + Enabled: false + +Style/WhileUntilDo: + Description: 'Checks for redundant do after while or until.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-multiline-while-do' + Enabled: false + +Style/WhileUntilModifier: + Description: >- + Favor modifier while/until usage when you have a + single-line body. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#while-as-a-modifier' + Enabled: false + +Style/WordArray: + Description: 'Use %w or %W for arrays of words.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#percent-w' + Enabled: false + +#################### Metrics ################################ + +Metrics/AbcSize: + Description: >- + A calculated magnitude based on number of assignments, + branches, and conditions. + Enabled: false + +Metrics/BlockNesting: + Description: 'Avoid excessive block nesting' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#three-is-the-number-thou-shalt-count' + Enabled: false + +Metrics/ClassLength: + Description: 'Avoid classes longer than 100 lines of code.' + Enabled: false + +Metrics/CyclomaticComplexity: + Description: >- + A complexity metric that is strongly correlated to the number + of test cases needed to validate a method. + Enabled: false + +Metrics/LineLength: + Description: 'Limit lines to 80 characters.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#80-character-limits' + Enabled: false + +Metrics/MethodLength: + Description: 'Avoid methods longer than 10 lines of code.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#short-methods' + Enabled: false + +Metrics/ParameterLists: + Description: 'Avoid parameter lists longer than three or four parameters.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#too-many-params' + Enabled: false + +Metrics/PerceivedComplexity: + Description: >- + A complexity metric geared towards measuring complexity for a + human reader. + Enabled: false + +#################### Lint ################################ +### Warnings + +Lint/AmbiguousOperator: + Description: >- + Checks for ambiguous operators in the first argument of a + method invocation without parentheses. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-as-args' + Enabled: false + +Lint/AmbiguousRegexpLiteral: + Description: >- + Checks for ambiguous regexp literals in the first argument of + a method invocation without parenthesis. + Enabled: false + +Lint/AssignmentInCondition: + Description: "Don't use assignment in conditions." + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#safe-assignment-in-condition' + Enabled: false + +Lint/BlockAlignment: + Description: 'Align block ends correctly.' + Enabled: false + +Lint/ConditionPosition: + Description: >- + Checks for condition placed in a confusing position relative to + the keyword. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#same-line-condition' + Enabled: false + +Lint/Debugger: + Description: 'Check for debugger calls.' + Enabled: false + +Lint/DefEndAlignment: + Description: 'Align ends corresponding to defs correctly.' + Enabled: false + +Lint/DeprecatedClassMethods: + Description: 'Check for deprecated class method calls.' + Enabled: false + +Lint/ElseLayout: + Description: 'Check for odd code arrangement in an else block.' + Enabled: false + +Lint/EmptyEnsure: + Description: 'Checks for empty ensure block.' + Enabled: false + +Lint/EmptyInterpolation: + Description: 'Checks for empty string interpolation.' + Enabled: false + +Lint/EndAlignment: + Description: 'Align ends correctly.' + Enabled: false + +Lint/EndInMethod: + Description: 'END blocks should not be placed inside method definitions.' + Enabled: false + +Lint/EnsureReturn: + Description: 'Do not use return in an ensure block.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-return-ensure' + Enabled: false + +Lint/Eval: + Description: 'The use of eval represents a serious security risk.' + Enabled: false + +Lint/HandleExceptions: + Description: "Don't suppress exception." + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#dont-hide-exceptions' + Enabled: false + +Lint/InvalidCharacterLiteral: + Description: >- + Checks for invalid character literals with a non-escaped + whitespace character. + Enabled: false + +Lint/LiteralInCondition: + Description: 'Checks of literals used in conditions.' + Enabled: false + +Lint/LiteralInInterpolation: + Description: 'Checks for literals used in interpolation.' + Enabled: false + +Lint/Loop: + Description: >- + Use Kernel#loop with break rather than begin/end/until or + begin/end/while for post-loop tests. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#loop-with-break' + Enabled: false + +Lint/ParenthesesAsGroupedExpression: + Description: >- + Checks for method calls with a space before the opening + parenthesis. + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#parens-no-spaces' + Enabled: true + +Lint/RequireParentheses: + Description: >- + Use parentheses in the method call to avoid confusion + about precedence. + Enabled: false + +Lint/RescueException: + Description: 'Avoid rescuing the Exception class.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-blind-rescues' + Enabled: false + +Lint/ShadowingOuterLocalVariable: + Description: >- + Do not use the same name as outer local variable + for block arguments or block local variables. + Enabled: false + +Lint/SpaceBeforeFirstArg: + Description: >- + Put a space between a method name and the first argument + in a method call without parentheses. + Enabled: false + +Lint/StringConversionInInterpolation: + Description: 'Checks for Object#to_s usage in string interpolation.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#no-to-s' + Enabled: false + +Lint/UnderscorePrefixedVariableName: + Description: 'Do not use prefix `_` for a variable that is used.' + Enabled: true + +Lint/UnusedBlockArgument: + Description: 'Checks for unused block arguments.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars' + Enabled: false + +Lint/UnusedMethodArgument: + Description: 'Checks for unused method arguments.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars' + Enabled: false + +Lint/UnreachableCode: + Description: 'Unreachable code.' + Enabled: false + +Lint/UselessAccessModifier: + Description: 'Checks for useless access modifiers.' + Enabled: false + +Lint/UselessAssignment: + Description: 'Checks for useless assignment to a local variable.' + StyleGuide: 'https://github.com/bbatsov/ruby-style-guide#underscore-unused-vars' + Enabled: false + +Lint/UselessComparison: + Description: 'Checks for comparison of something with itself.' + Enabled: false + +Lint/UselessElseWithoutRescue: + Description: 'Checks for useless `else` in `begin..end` without `rescue`.' + Enabled: false + +Lint/UselessSetterCall: + Description: 'Checks for useless setter call to a local variable.' + Enabled: false + +Lint/Void: + Description: 'Possible use of operator/literal/variable in void context.' + Enabled: false + +##################### Rails ################################## + +Rails/ActionFilter: + Description: 'Enforces consistent use of action filter methods.' + Enabled: false + +Rails/DefaultScope: + Description: 'Checks if the argument passed to default_scope is a block.' + Enabled: false + +Rails/Delegate: + Description: 'Prefer delegate method for delegations.' + Enabled: false + +Rails/HasAndBelongsToMany: + Description: 'Prefer has_many :through to has_and_belongs_to_many.' + Enabled: true + +Rails/Output: + Description: 'Checks for calls to puts, print, etc.' + Enabled: true + +Rails/ReadWriteAttribute: + Description: >- + Checks for read_attribute(:attr) and + write_attribute(:attr, val). + Enabled: false + +Rails/ScopeArgs: + Description: 'Checks the arguments of ActiveRecord scopes.' + Enabled: false + +Rails/Validation: + Description: 'Use validates :attribute, hash of validations.' + Enabled: false + + +# Exclude some of GitLab files +# +# +AllCops: + RunRailsCops: true + Exclude: + - 'spec/**/*' + - 'features/**/*' + - 'vendor/**/*' + - 'db/**/*' + - 'tmp/**/*' + - 'bin/**/*' + - 'lib/backup/**/*' + - 'lib/tasks/**/*' + - 'lib/email_validator.rb' + - 'lib/gitlab/upgrader.rb' + - 'lib/gitlab/seeder.rb' diff --git a/.ruby-version b/.ruby-version new file mode 100644 index 0000000000..399088bf46 --- /dev/null +++ b/.ruby-version @@ -0,0 +1 @@ +2.1.6 diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 9b7b2cb3c0..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,36 +0,0 @@ -language: ruby -env: - global: - - TRAVIS=true - matrix: - - TASK=spinach_project DB=mysql - - TASK=spinach_other DB=mysql - - TASK=spec:api DB=mysql - - TASK=spec:feature DB=mysql - - TASK=spec:other DB=mysql - - TASK=jasmine:ci DB=mysql - - TASK=spinach_project DB=postgresql - - TASK=spinach_other DB=postgresql - - TASK=spec:api DB=postgresql - - TASK=spec:feature DB=postgresql - - TASK=spec:other DB=postgresql - - TASK=jasmine:ci DB=postgresql -before_install: - - sudo apt-get install libicu-dev -y -install: - - "travis_retry bundle install --deployment --without production --retry 5" -branches: - only: - - 'master' -rvm: - - 2.0.0 -services: - - redis-server -before_script: - - "cp config/database.yml.$DB config/database.yml" - - "cp config/gitlab.yml.example config/gitlab.yml" - - "bundle exec rake db:setup" - - "bundle exec rake db:seed_fu" -script: "bundle exec rake $TASK --trace" -notifications: - email: false diff --git a/CHANGELOG b/CHANGELOG index bc2d34d49f..1aab904f11 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,513 @@ +Please view this file on the master branch, on stable branches it's out of date. + +v 7.11.0 (unreleased) + - Fix clone URL field and X11 Primary selection (Dmitry Medvinsky) + - Ignore invalid lines in .gitmodules + - + - + - + - + - + - + - + +v 7.10.0 (unreleased) + - Ignore submodules that are defined in .gitmodules but are checked in as directories. + - Allow projects to be imported from Google Code. + - Remove access control for uploaded images to fix broken images in emails (Hannes Rosenögger) + - Allow users to be invited by email to join a group or project. + - Don't crash when project repository doesn't exist. + - Add config var to block auto-created LDAP users. + - Don't use HTML ellipsis in EmailsOnPush subject truncated commit message. + - Set EmailsOnPush reply-to address to committer email when enabled. + - Fix broken file browsing with a submodule that contains a relative link (Stan Hu) + - Fix persistent XSS vulnerability around profile website URLs. + - Fix project import URL regex to prevent arbitary local repos from being imported. + - Fix directory traversal vulnerability around uploads routes. + - Fix directory traversal vulnerability around help pages. + - Don't leak existence of project via search autocomplete. + - Don't leak existence of group or project via search. + - Fix bug where Wiki pages that included a '/' were no longer accessible (Stan Hu) + - Fix bug where error messages from Dropzone would not be displayed on the issues page (Stan Hu) + - Add a rake task to check repository integrity with `git fsck` + - Add ability to configure Reply-To address in gitlab.yml (Stan Hu) + - Move current user to the top of the list in assignee/author filters (Stan Hu) + - Fix broken side-by-side diff view on merge request page (Stan Hu) + - Set Application controller default URL options to ensure all url_for calls are consistent (Stan Hu) + - Allow HTML tags in Markdown input + - Fix code unfold not working on Compare commits page (Stan Hu) + - Fix generating SSH key fingerprints with OpenSSH 6.8. (Sašo Stanovnik) + - Include missing events and fix save functionality in admin service template settings form (Stan Hu) + - Fix "Import projects from" button to show the correct instructions (Stan Hu) + - Fix dots in Wiki slugs causing errors (Stan Hu) + - Make maximum attachment size configurable via Application Settings (Stan Hu) + - Update poltergeist to version 1.6.0 to support PhantomJS 2.0 (Zeger-Jan van de Weg) + - Fix cross references when usernames, milestones, or project names contain underscores (Stan Hu) + - Disable reference creation for comments surrounded by code/preformatted blocks (Stan Hu) + - Reduce Rack Attack false positives causing 403 errors during HTTP authentication (Stan Hu) + - enable line wrapping per default and remove the checkbox to toggle it (Hannes Rosenögger) + - Fix a link in the patch update guide + - Add a service to support external wikis (Hannes Rosenögger) + - Omit the "email patches" link and fix plain diff view for merge commits + - List new commits for newly pushed branch in activity view. + - Add sidetiq gem dependency to match EE + - Add changelog, license and contribution guide links to project tab bar. + - Improve diff UI + - Fix alignment of navbar toggle button (Cody Mize) + - Fix checkbox rendering for nested task lists + - Identical look of selectboxes in UI + - Upgrade the gitlab_git gem to version 7.1.3 + - Move "Import existing repository by URL" option to button. + - Improve error message when save profile has error. + - Passing the name of pushed ref to CI service (requires GitLab CI 7.9+) + - Add location field to user profile + - Fix print view for markdown files and wiki pages + - Fix errors when deleting old backups + - Improve GitLab performance when working with git repositories + - Add tag message and last commit to tag hook (Kamil Trzciński) + - Restrict permissions on backup files + - Improve oauth accounts UI in profile page + - Add ability to unlink connected accounts + - Replace commits calendar with faster contribution calendar that includes issues and merge requests + - Add inifinite scroll to user page activity + - Don't include system notes in issue/MR comment count. + - Don't mark merge request as updated when merge status relative to target branch changes. + - Link note avatar to user. + - Make Git-over-SSH errors more descriptive. + - Fix EmailsOnPush. + - Refactor issue filtering + - AJAX selectbox for issue assignee and author filters + - Fix issue with missing options in issue filtering dropdown if selected one + - Prevent holding Control-Enter or Command-Enter from posting comment multiple times. + - Prevent note form from being cleared when submitting failed. + - Improve file icons rendering on tree (Sullivan Sénéchal) + - API: Add pagination to project events + - Get issue links in notification mail to work again. + - Don't show commit comment button when user is not signed in. + - Fix admin user projects lists. + - Don't leak private group existence by redirecting from namespace controller to group controller. + - Ability to skip some items from backup (database, respositories or uploads) + - Archive repositories in background worker. + - Import GitHub, Bitbucket or GitLab.com projects owned by authenticated user into current namespace. + - Project labels are now available over the API under the "tag_list" field (Cristian Medina) + - Fixed link paths for HTTP and SSH on the admin project view (Jeremy Maziarz) + - Fix and improve help rendering (Sullivan Sénéchal) + - Fix final line in EmailsOnPush email diff being rendered as error. + - Authometic setup GitLab CI project for forks if origin project has GitLab CI enabled + - Prevent duplicate Buildkite service creation. + - Fix git over ssh errors 'fatal: protocol error: bad line length character' + - Automatically setup GitLab CI project for forks if origin project has GitLab CI enabled + - Bust group page project list cache when namespace name or path changes. + - Explicitly set image alt-attribute to prevent graphical glitches if gravatars could not be loaded + - Allow user to choose a public email to show on public profile + - Remove truncation from issue titles on milestone page (Jason Blanchard) + - 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.3 + - Contains no changes + +v 7.9.2 + - Contains no changes + +v 7.9.1 + - Include missing events and fix save functionality in admin service template settings form (Stan Hu) + - Fix "Import projects from" button to show the correct instructions (Stan Hu) + - Fix OAuth2 issue importing a new project from GitHub and GitLab (Stan Hu) + - Fix for LDAP with commas in DN + - Fix missing events and in admin Slack service template settings form (Stan Hu) + - Don't show commit comment button when user is not signed in. + - Downgrade gemnasium-gitlab-service gem + +v 7.9.0 + - Add HipChat integration documentation (Stan Hu) + - Update documentation for object_kind field in Webhook push and tag push Webhooks (Stan Hu) + - Fix broken email images (Hannes Rosenögger) + - Automatically config git if user forgot, where possible (Zeger-Jan van de Weg) + - Fix mass SQL statements on initial push (Hannes Rosenögger) + - Add tag push notifications and normalize HipChat and Slack messages to be consistent (Stan Hu) + - Add comment notification events to HipChat and Slack services (Stan Hu) + - Add issue and merge request events to HipChat and Slack services (Stan Hu) + - Fix merge request URL passed to Webhooks. (Stan Hu) + - Fix bug that caused a server error when editing a comment to "+1" or "-1" (Stan Hu) + - Fix code preview theme setting for comments, issues, merge requests, and snippets (Stan Hu) + - Move labels/milestones tabs to sidebar + - Upgrade Rails gem to version 4.1.9. + - Improve error messages for file edit failures + - Improve UI for commits, issues and merge request lists + - Fix commit comments on first line of diff not rendering in Merge Request Discussion view. + - Allow admins to override restricted project visibility settings. + - Move restricted visibility settings from gitlab.yml into the web UI. + - Improve trigger merge request hook when source project branch has been updated (Kirill Zaitsev) + - Save web edit in new branch + - Fix ordering of imported but unchanged projects (Marco Wessel) + - Mobile UI improvements: make aside content expandable + - Expose avatar_url in projects API + - Fix checkbox alignment on the application settings page. + - Generalize image upload in drag and drop in markdown to all files (Hannes Rosenögger) + - Fix mass-unassignment of issues (Robert Speicher) + - Fix hidden diff comments in merge request discussion view + - Allow user confirmation to be skipped for new users via API + - Add a service to send updates to an Irker gateway (Romain Coltel) + - Add brakeman (security scanner for Ruby on Rails) + - Slack username and channel options + - Add grouped milestones from all projects to dashboard. + - Web hook sends pusher email as well as commiter + - Add Bitbucket omniauth provider. + - Add Bitbucket importer. + - Support referencing issues to a project whose name starts with a digit + - Condense commits already in target branch when updating merge request source branch. + - Send notifications and leave system comments when bulk updating issues. + - Automatically link commit ranges to compare page: sha1...sha4 or sha1..sha4 (includes sha1 in comparison) + - Move groups page from profile to dashboard + - Starred projects page at dashboard + - Blocking user does not remove him/her from project/groups but show blocked label + - Change subject of EmailsOnPush emails to include namespace, project and branch. + - Change subject of EmailsOnPush emails to include first commit message when multiple were pushed. + - Remove confusing footer from EmailsOnPush mail body. + - Add list of changed files to EmailsOnPush emails. + - Add option to send EmailsOnPush emails from committer email if domain matches. + - Add option to disable code diffs in EmailOnPush emails. + - Wrap commit message in EmailsOnPush email. + - Send EmailsOnPush emails when deleting commits using force push. + - Fix EmailsOnPush email comparison link to include first commit. + - Fix highliht of selected lines in file + - Reject access to group/project avatar if the user doesn't have access. + - Add database migration to clean group duplicates with same path and name (Make sure you have a backup before update) + - Add GitLab active users count to rake gitlab:check + - Starred projects page at dashboard + - Make email display name configurable + - Improve json validation in hook data + - Use Emoji One + - Updated emoji help documentation to properly reference EmojiOne. + - Fix missing GitHub organisation repositories on import page. + - Added blue theme + - Remove annoying notice messages when create/update merge request + - Allow smb:// links in Markdown text. + - Filter merge request by title or description at Merge Requests page + - Block user if he/she was blocked in Active Directory + - Fix import pages not working after first load. + - Use custom LDAP label in LDAP signin form. + - Execute hooks and services when branch or tag is created or deleted through web interface. + - Block and unblock user if he/she was blocked/unblocked in Active Directory + - Raise recommended number of unicorn workers from 2 to 3 + - Use same layout and interactivity for project members as group members. + - Prevent gitlab-shell character encoding issues by receiving its changes as raw data. + - Ability to unsubscribe/subscribe to issue or merge request + - Delete deploy key when last connection to a project is destroyed. + - Fix invalid Atom feeds when using emoji, horizontal rules, or images (Christian Walther) + - Backup of repositories with tar instead of git bundle (only now are git-annex files included in the backup) + - Add canceled status for CI + - Send EmailsOnPush email when branch or tag is created or deleted. + - Faster merge request processing for large repository + - Prevent doubling AJAX request with each commit visit via Turbolink + - Prevent unnecessary doubling of js events on import pages and user calendar + +v 7.8.4 + - Fix issue_tracker_id substitution in custom issue trackers + - Fix path and name duplication in namespaces + +v 7.8.3 + - Bump version of gitlab_git fixing annotated tags without message + +v 7.8.2 + - Fix service migration issue when upgrading from versions prior to 7.3 + - Fix setting of the default use project limit via admin UI + - Fix showing of already imported projects for GitLab and Gitorious importers + - Fix response of push to repository to return "Not found" if user doesn't have access + - Fix check if user is allowed to view the file attachment + - Fix import check for case sensetive namespaces + - Increase timeout for Git-over-HTTP requests to 1 hour since large pulls/pushes can take a long time. + - Properly handle autosave local storage exceptions. + - Escape wildcards when searching LDAP by username. + +v 7.8.1 + - Fix run of custom post receive hooks + - Fix migration that caused issues when upgrading to version 7.8 from versions prior to 7.3 + - Fix the warning for LDAP users about need to set password + - Fix avatars which were not shown for non logged in users + - Fix urls for the issues when relative url was enabled + +v 7.8.0 + - Fix access control and protection against XSS for note attachments and other uploads. + - Replace highlight.js with rouge-fork rugments (Stefan Tatschner) + - Make project search case insensitive (Hannes Rosenögger) + - Include issue/mr participants in list of recipients for reassign/close/reopen emails + - Expose description in groups API + - Better UI for project services page + - Cleaner UI for web editor + - Add diff syntax highlighting in email-on-push service notifications (Hannes Rosenögger) + - Add API endpoint to fetch all changes on a MergeRequest (Jeroen van Baarsen) + - View note image attachments in new tab when clicked instead of downloading them + - Improve sorting logic in UI and API. Explicitly define what sorting method is used by default + - Fix overflow at sidebar when have several items + - Add notes for label changes in issue and merge requests + - Show tags in commit view (Hannes Rosenögger) + - Only count a user's vote once on a merge request or issue (Michael Clarke) + - Increase font size when browse source files and diffs + - Service Templates now let you set default values for all services + - Create new file in empty repository using GitLab UI + - Ability to clone project using oauth2 token + - Upgrade Sidekiq gem to version 3.3.0 + - Stop git zombie creation during force push check + - Show success/error messages for test setting button in services + - Added Rubocop for code style checks + - Fix commits pagination + - Async load a branch information at the commit page + - Disable blacklist validation for project names + - Allow configuring protection of the default branch upon first push (Marco Wessel) + - Add gitlab.com importer + - Add an ability to login with gitlab.com + - Add a commit calendar to the user profile (Hannes Rosenögger) + - Submit comment on command-enter + - Notify all members of a group when that group is mentioned in a comment, for example: `@gitlab-org` or `@sales`. + - Extend issue clossing pattern to include "Resolve", "Resolves", "Resolved", "Resolving" and "Close" (Julien Bianchi and Hannes Rosenögger) + - Fix long broadcast message cut-off on left sidebar (Visay Keo) + - Add Project Avatars (Steven Thonus and Hannes Rosenögger) + - Password reset token validity increased from 2 hours to 2 days since it is also send on account creation. + - Edit group members via API + - Enable raw image paste from clipboard, currently Chrome only (Marco Cyriacks) + - Add action property to merge request hook (Julien Bianchi) + - Remove duplicates from group milestone participants list. + - Add a new API function that retrieves all issues assigned to a single milestone (Justin Whear and Hannes Rosenögger) + - API: Access groups with their path (Julien Bianchi) + - Added link to milestone and keeping resource context on smaller viewports for issues and merge requests (Jason Blanchard) + - Allow notification email to be set separately from primary email. + - API: Add support for editing an existing project (Mika Mäenpää and Hannes Rosenögger) + - Don't have Markdown preview fail for long comments/wiki pages. + - When test web hook - show error message instead of 500 error page if connection to hook url was reset + - Added support for firing system hooks on group create/destroy and adding/removing users to group (Boyan Tabakov) + - Added persistent collapse button for left side nav bar (Jason Blanchard) + - Prevent losing unsaved comments by automatically restoring them when comment page is loaded again. + - Don't allow page to be scaled on mobile. + - Clean the username acquired from OAuth/LDAP so it doesn't fail username validation and block signing up. + - Show assignees in merge request index page (Kelvin Mutuma) + - Link head panel titles to relevant root page. + - Allow users that signed up via OAuth to set their password in order to use Git over HTTP(S). + - Show users button to share their newly created public or internal projects on twitter + - Add quick help links to the GitLab pricing and feature comparison pages. + - Fix duplicate authorized applications in user profile and incorrect application client count in admin area. + - Make sure Markdown previews always use the same styling as the eventual destination. + - Remove deprecated Group#owner_id from API + - Show projects user contributed to on user page. Show stars near project on user page. + - Improve database performance for GitLab + - Add Asana service (Jeremy Benoist) + - Improve project web hooks with extra data + +v 7.7.2 + - Update GitLab Shell to version 2.4.2 that fixes a bug when developers can push to protected branch + - Fix issue when LDAP user can't login with existing GitLab account + +v 7.7.1 + - Improve mention autocomplete performance + - Show setup instructions for GitHub import if disabled + - Allow use http for OAuth applications + +v 7.7.0 + - Import from GitHub.com feature + - Add Jetbrains Teamcity CI service (Jason Lippert) + - Mention notification level + - Markdown preview in wiki (Yuriy Glukhov) + - Raise group avatar filesize limit to 200kb + - OAuth applications feature + - Show user SSH keys in admin area + - Developer can push to protected branches option + - Set project path instead of project name in create form + - Block Git HTTP access after 10 failed authentication attempts + - Updates to the messages returned by API (sponsored by O'Reilly Media) + - New UI layout with side navigation + - Add alert message in case of outdated browser (IE < 10) + - Added API support for sorting projects + - Update gitlab_git to version 7.0.0.rc14 + - Add API project search filter option for authorized projects + - Fix File blame not respecting branch selection + - Change some of application settings on fly in admin area UI + - Redesign signin/signup pages + - Close standard input in Gitlab::Popen.popen + - Trigger GitLab CI when push tags + - When accept merge request - do merge using sidaekiq job + - Enable web signups by default + - Fixes for diff comments: drag-n-drop images, selecting images + - Fixes for edit comments: drag-n-drop images, preview mode, selecting images, save & update + - Remove password strength indicator + + + +v 7.6.0 + - Fork repository to groups + - New rugged version + - Add CRON=1 backup setting for quiet backups + - Fix failing wiki restore + - Add optional Sidekiq MemoryKiller middleware (enabled via SIDEKIQ_MAX_RSS env variable) + - Monokai highlighting style now more faithful to original design (Mark Riedesel) + - Create project with repository in synchrony + - Added ability to create empty repo or import existing one if project does not have repository + - Reactivate highlight.js language autodetection + - Mobile UI improvements + - Change maximum avatar file size from 100KB to 200KB + - Strict validation for snippet file names + - Enable Markdown preview for issues, merge requests, milestones, and notes (Vinnie Okada) + - In the docker directory is a container template based on the Omnibus packages. + - Update Sidekiq to version 2.17.8 + - Add author filter to project issues and merge requests pages + - Atom feed for user activity + - Support multiple omniauth providers for the same user + - Rendering cross reference in issue title and tooltip for merge request + - Show username in comments + - Possibility to create Milestones or Labels when Issues are disabled + - Fix bug with showing gpg signature in tag + +v 7.5.3 + - Bump gitlab_git to 7.0.0.rc12 (includes Rugged 0.21.2) + +v 7.5.2 + - Don't log Sidekiq arguments by default + - Fix restore of wiki repositories from backups + +v 7.5.1 + - Add missing timestamps to 'members' table + +v 7.5.0 + - API: Add support for Hipchat (Kevin Houdebert) + - Add time zone configuration in gitlab.yml (Sullivan Senechal) + - Fix LDAP authentication for Git HTTP access + - Run 'GC.start' after every EmailsOnPushWorker job + - Fix LDAP config lookup for provider 'ldap' + - Drop all sequences during Postgres database restore + - Project title links to project homepage (Ben Bodenmiller) + - Add Atlassian Bamboo CI service (Drew Blessing) + - Mentioned @user will receive email even if he is not participating in issue or commit + - Session API: Use case-insensitive authentication like in UI (Andrey Krivko) + - Tie up loose ends with annotated tags: API & UI (Sean Edge) + - Return valid json for deleting branch via API (sponsored by O'Reilly Media) + - Expose username in project events API (sponsored by O'Reilly Media) + - Adds comments to commits in the API + - Performance improvements + - Fix post-receive issue for projects with deleted forks + - New gitlab-shell version with custom hooks support + - Improve code + - GitLab CI 5.2+ support (does not support older versions) + - Fixed bug when you can not push commits starting with 000000 to protected branches + - Added a password strength indicator + - Change project name and path in one form + - Display renamed files in diff views (Vinnie Okada) + - Fix raw view for public snippets + - Use secret token with GitLab internal API. + - Add missing timestamps to 'members' table + +v 7.4.3 + - Fix raw snippets view + - Fix security issue for member api + - Fix buildbox integration + +v 7.4.2 + - Fix internal snippet exposing for unauthenticated users + +v 7.4.1 + - Fix LDAP authentication for Git HTTP access + - Fix LDAP config lookup for provider 'ldap' + - Fix public snippets + - Fix 500 error on projects with nested submodules + +v 7.4.0 + - Refactored membership logic + - Improve error reporting on users API (Julien Bianchi) + - Refactor test coverage tools usage. Use SIMPLECOV=true to generate it locally + - Default branch is protected by default + - Increase unicorn timeout to 60 seconds + - Sort search autocomplete projects by stars count so most popular go first + - Add README to tab on project show page + - Do not delete tmp/repositories itself during clean-up, only its contents + - Support for backup uploads to remote storage + - Prevent notes polling when there are not notes + - Internal ForkService: Prepare support for fork to a given namespace + - API: Add support for forking a project via the API (Bernhard Kaindl) + - API: filter project issues by milestone (Julien Bianchi) + - Fail harder in the backup script + - Changes to Slack service structure, only webhook url needed + - Zen mode for wiki and milestones (Robert Schilling) + - Move Emoji parsing to html-pipeline-gitlab (Robert Schilling) + - Font Awesome 4.2 integration (Sullivan Senechal) + - Add Pushover service integration (Sullivan Senechal) + - Add select field type for services options (Sullivan Senechal) + - Add cross-project references to the Markdown parser (Vinnie Okada) + - Add task lists to issue and merge request descriptions (Vinnie Okada) + - Snippets can be public, internal or private + - Improve danger zone: ask project path to confirm data-loss action + - Raise exception on forgery + - Show build coverage in Merge Requests (requires GitLab CI v5.1) + - New milestone and label links on issue edit form + - Improved repository graphs + - Improve event note display in dashboard and project activity views (Vinnie Okada) + - Add users sorting to admin area + - UI improvements + - Fix ambiguous sha problem with mentioned commit + - Fixed bug with apostrophe when at mentioning users + - Add active directory ldap option + - Developers can push to wiki repo. Protected branches does not affect wiki repo any more + - Faster rev list + - Fix branch removal + +v 7.3.2 + - Fix creating new file via web editor + - Use gitlab-shell v2.0.1 + +v 7.3.1 + - Fix ref parsing in Gitlab::GitAccess + - Fix error 500 when viewing diff on a file with changed permissions + - Fix adding comments to MR when source branch is master + - Fix error 500 when searching description contains relative link + +v 7.3.0 + - Always set the 'origin' remote in satellite actions + - Write authorized_keys in tmp/ during tests + - Use sockets to connect to Redis + - Add dormant New Relic gem (can be enabled via environment variables) + - Expire Rack sessions after 1 week + - Cleaner signin/signup pages + - Improved comments UI + - Better search with filtering, pagination etc + - Added a checkbox to toggle line wrapping in diff (Yuriy Glukhov) + - Prevent project stars duplication when fork project + - Use the default Unicorn socket backlog value of 1024 + - Support Unix domain sockets for Redis + - Store session Redis keys in 'session:gitlab:' namespace + - Deprecate LDAP account takeover based on partial LDAP email / GitLab username match + - Use /bin/sh instead of Bash in bin/web, bin/background_jobs (Pavel Novitskiy) + - Keyboard shortcuts for productivity (Robert Schilling) + - API: filter issues by state (Julien Bianchi) + - API: filter issues by labels (Julien Bianchi) + - Add system hook for ssh key changes + - Add blob permalink link (Ciro Santilli) + - Create annotated tags through UI and API (Sean Edge) + - Snippets search (Charles Bushong) + - Comment new push to existing MR + - Add 'ci' to the blacklist of forbidden names + - Improve text filtering on issues page + - Comment & Close button + - Process git push --all much faster + - Don't allow edit of system notes + - Project wiki search (Ralf Seidler) + - Enabled Shibboleth authentication support (Matus Banas) + - Zen mode (fullscreen) for issues/MR/notes (Robert Schilling) + - Add ability to configure webhook timeout via gitlab.yml (Wes Gurney) + - Sort project merge requests in asc or desc order for updated_at or created_at field (sponsored by O'Reilly Media) + - Add Redis socket support to 'rake gitlab:shell:install' + v 7.2.1 - Delete orphaned labels during label migration (James Brooks) - Security: prevent XSS with stricter MIME types for raw repo files diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f02ba2216d..3165b7379d 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,7 @@ By submitting code as an individual you agree to the [individual contributor lic ## Security vulnerability disclosure -Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](http://www.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. +Please report suspected security vulnerabilities in private to support@gitlab.com, also see the [disclosure section on the GitLab.com website](http://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. ## Closing policy for issues and merge requests @@ -20,9 +20,16 @@ Please treat our volunteers with courtesy and respect, it will go a long way tow Issues and merge requests should be in English and contain appropriate language for audiences of all ages. +## Helping others + +Please help other GitLab users when you can. +The channnels people will reach out on can be found on the [getting help page](https://about.gitlab.com/getting-help/). +Sign up for the mailinglist, answer GitLab questions on StackOverflow or respond in the irc channel. +You can also sign up on [CodeTriage](http://www.codetriage.com/gitlabhq/gitlabhq) to help with one issue every day. + ## Issue tracker -To get support for your particular problem please use the channels as detailed in the [getting help section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#getting-help). Professional [support subscriptions](http://www.gitlab.com/subscription/) and [consulting services](http://www.gitlab.com/consultancy/) are available from [GitLab.com](http://www.gitlab.com/). +To get support for your particular problem please use the channels as detailed in the [getting help section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#getting-help). Professional [support subscriptions](http://about.gitlab.com/subscription/) and [consulting services](http://about.gitlab.com/consultancy/) are available from [GitLab.com](http://about.gitlab.com/). The [issue tracker](https://gitlab.com/gitlab-org/gitlab-ce/issues) is only for obvious errors in the latest [stable or development release of GitLab](MAINTENANCE.md). If something is wrong but it is not a regression compared to older versions of GitLab please do not open an issue but a feature request. When submitting an issue please conform to the issue submission guidelines listed below. Not all issues will be addressed and your issue is more likely to be addressed if you submit a merge request which partially or fully addresses the issue. @@ -37,14 +44,14 @@ Please send a merge request with a tested solution or a merge request with a fai **[Search the issues](https://gitlab.com/gitlab-org/gitlab-ce/issues)** for similar entries before submitting your own, there's a good chance somebody else had the same issue. Show your support with `:+1:` and/or join the discussion. Please submit issues in the following format (as the first post): 1. **Summary:** Summarize your issue in one sentence (what goes wrong, what did you expect to happen) -1. **Steps to reproduce:** How can we reproduce the issue, preferably on the [GitLab development virtual machine with vagrant](https://gitlab.com/gitlab-org/cookbook-gitlab/blob/master/doc/development.md) (start your issue with: `vagrant destroy && vagrant up && vagrant ssh`) +1. **Steps to reproduce:** How can we reproduce the issue 1. **Expected behavior:** Describe your issue in detail 1. **Observed behavior** 1. **Relevant logs and/or screenshots:** Please use code blocks (\`\`\`) to format console output, logs, and code as it's very hard to read otherwise. 1. **Output of checks** * Results of GitLab [Application Check](doc/install/installation.md#check-application-status) (`sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production SANITIZE=true`); we will only investigate if the tests are passing * Version of GitLab you are running; we will only investigate issues in the latest stable and development releases as per the [maintenance policy](MAINTENANCE.md) - * Add the last commit sha1 of the GitLab version you used to replicate the issue (obtainable from the help page) + * Add the last commit SHA-1 of the GitLab version you used to replicate the issue (obtainable from the help page) * Describe your setup (use relevant parts from `sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production`) 1. **Possible fixes**: If you can, link to the line of code that might be responsible for the problem @@ -54,14 +61,19 @@ We welcome merge requests with fixes and improvements to GitLab code, tests, and Merge requests can be filed either at [gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests) or [github.com](https://github.com/gitlabhq/gitlabhq/pulls). +If you are new to GitLab development (or web development in general), search for the label `easyfix` ([gitlab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues?label_name=easyfix), [github](https://github.com/gitlabhq/gitlabhq/labels/easyfix)). Those are issues easy to fix, marked by the GitLab core-team. If you are unsure how to proceed but want to help, mention one of the core-team members to give you a hint. + +To start with GitLab download the [GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit) and see [Development section](doc/development/README.md) in the help file. + ### Merge request guidelines If you can, please submit a merge request with the fix or improvements including tests. If you don't know how to fix the issue but can write a test that exposes the issue we will accept that as well. In general bug fixes that include a regression test are merged quickly while new features without proper tests are least likely to receive timely feedback. The workflow to make a merge request is as follows: 1. Fork the project on GitLab Cloud 1. Create a feature branch -1. Write [tests](README.md#run-the-tests) and code +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 1. If you have multiple commits please combine them into one commit by [squashing them](http://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits) 1. Push the commit to your fork 1. Submit a merge request (MR) to the master branch @@ -72,34 +84,69 @@ If you can, please submit a merge request with the fix or improvements including 1. Link relevant [issues](https://gitlab.com/gitlab-org/gitlab-ce/issues) and/or [feature requests](http://feedback.gitlab.com/) from the merge request description and leave a comment on them with a link back to the MR 1. Be prepared to answer questions and incorporate feedback even if requests for this arrive weeks or months after your MR submission 1. If your MR touches code that executes shell commands, make sure it adheres to the [shell command guidelines]( doc/development/shell_commands.md). +1. Also have a look at the [shell command guidelines](doc/development/shell_commands.md) if your code reads or opens files, or handles paths to files on disk. The **official merge window** is in the beginning of the month from the 1st to the 7th day of the month. The best time to submit a MR and get feedback fast. Before this time the GitLab B.V. team is still dealing with work that is created by the monthly release such as assisting subscribers with upgrade issues, the release of Enterprise Edition and the upgrade of GitLab Cloud. After the 7th it is already getting closer to the release date of the next version. This means there is less time to fix the issues created by merging large new features. -Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? Can you do part of the refactor? The increased reviewability of small MR's that leads to higher code quality is more important to us than having a mimimal commit log. The smaller a MR is the more likely it is it will be merged (quickly), after that you can send more MR's to enhance it. +Please keep the change in a single MR **as small as possible**. If you want to contribute a large feature think very hard what the minimum viable change is. Can you split functionality? Can you only submit the backend/API code? Can you start with a very simple UI? Can you do part of the refactor? The increased reviewability of small MR's that leads to higher code quality is more important to us than having a minimal commit log. The smaller a MR is the more likely it is it will be merged (quickly), after that you can send more MR's to enhance it. -For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). If you would like quick feedback on your merge request feel free to mention one of the Merge Marshalls of [the core-team](https://about.gitlab.com/core-team/). Please ensure that your merge request meets the following contribution acceptance criteria. +For examples of feedback on merge requests please look at already [closed merge requests](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests?assignee_id=&label_name=&milestone_id=&scope=&sort=&state=closed). If you would like quick feedback on your merge request feel free to mention one of the Merge Marshalls of [the core-team](https://about.gitlab.com/core-team/). Please ensure that your merge request meets the contribution acceptance criteria. -**Please format your merge request description as follows:** +## Definition of done + +If you contribute to GitLab please know that changes involve more than just code. +We have the following [definition of done](http://guide.agilealliance.org/guide/definition-of-done.html). +Please ensure you support the feature you contribute through all of these steps. + +1. Description explaning the relevancy (see following item) +1. Working and clean code that is commented where needed +1. Unit and integration tests that pass on the CI server +1. Documented in the /doc directory +1. Changelog entry added +1. Reviewed and any concerns are addressed +1. Merged by the project lead +1. Added to the release blog article +1. Added to [the website](https://gitlab.com/gitlab-com/www-gitlab-com/) if relevant +1. Community questions answered +1. Answers to questions radiated (in docs/wiki/etc.) + +If you add a dependency in GitLab (such as an operating system package) please consider updating the following and note the applicability of each in your merge request: + +1. Note the addition in the release blog post (create one if it doesn't exist yet) https://gitlab.com/gitlab-com/www-gitlab-com/merge_requests/ +1. Upgrade guide, for example https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/7.5-to-7.6.md +1. Upgrader https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/upgrader.md#2-run-gitlab-upgrade-tool +1. Installation guide https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies +1. GitLab Development Kit https://gitlab.com/gitlab-org/gitlab-development-kit +1. Test suite https://gitlab.com/gitlab-org/gitlab-ci/blob/master/doc/examples/configure_a_runner_to_run_the_gitlab_ce_test_suite.md +1. Omnibus package creator https://gitlab.com/gitlab-org/omnibus-gitlab + +## Merge request description format 1. What does this MR do? 1. Are there points in the code the reviewer needs to double check? 1. Why was this MR needed? 1. What are the relevant issue numbers / [Feature requests](http://feedback.gitlab.com/)? -1. Screenshots (If appropiate) +1. Screenshots (if relevant) ## Contribution acceptance criteria 1. The change is as small as possible (see the above paragraph for details) 1. Include proper tests and make all tests pass (unless it contains a test exposing a bug in existing code) -1. Can merge without problems (if not please use: `git rebase master`) +1. All tests have to pass, if you suspect a failing CI build is unrelated to your contribution ask for tests to be restarted. See [the CI setup document](http://doc.gitlab.com/ce/development/ci_setup.html) on who you can ask for test restart. +1. Initially contains a single commit (please use `git rebase -i` to squash commits) +1. Can merge without problems (if not please merge `master`, never rebase commits pushed to the remote server) 1. Does not break any existing functionality 1. Fixes one specific issue or implements one specific feature (do not combine things, send separate merge requests if needed) +1. Migrations should do only one thing (eg: either create a table, move data to a new table or remove an old table) to aid retrying on failure 1. Keeps the GitLab code base clean and well structured 1. Contains functionality we think other users will benefit from too 1. Doesn't add configuration options since they complicate future changes -1. Initially contains a single commit (please use `git rebase -i` to squash commits) 1. Changes after submitting the merge request should be in separate commits (no squashing). You will be asked to squash when the review is over, before merging. -1. It conforms to the following style guides +1. It conforms to the following style guides. + If your change touches a line that does not follow the style, + modify the entire line to follow it. This prevents linting tools from generating warnings. + Don't touch neighbouring lines. As an exception, automatic mass refactoring modifications + may leave style non-compliant. ## Style guides @@ -113,5 +160,20 @@ For examples of feedback on merge requests please look at already [closed merge 1. [CoffeeScript](https://github.com/thoughtbot/guides/tree/master/style#coffeescript) 1. [Shell commands](doc/development/shell_commands.md) created by GitLab contributors to enhance security 1. [Markdown](http://www.cirosantilli.com/markdown-styleguide) +1. Interface text should be written subjectively instead of objectively. It should be the gitlab core team addressing a person. It should be written in present time and never use past tense (has been/was). For example instead of "prohibited this user from being saved due to the following errors:" the text should be "sorry, we could not create your account because:". Also these [excellent writing guidelines](https://github.com/NARKOZ/guides#writing). -This is also the style used by linting tools such as [Rubocop](https://github.com/bbatsov/rubocop), [PullReview](https://www.pullreview.com/) and [Hound CI](https://houndci.com). +This is also the style used by linting tools such as [RuboCop](https://github.com/bbatsov/rubocop), [PullReview](https://www.pullreview.com/) and [Hound CI](https://houndci.com). + +## Code of conduct +As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities. + +We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion. + +Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct. + +Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team. + +Instances of abusive, harassing, or otherwise unacceptable behavior can be +reported by emailing contact@gitlab.com + +This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/) diff --git a/GITLAB_SHELL_VERSION b/GITLAB_SHELL_VERSION index fee0a2788b..097a15a2af 100644 --- a/GITLAB_SHELL_VERSION +++ b/GITLAB_SHELL_VERSION @@ -1 +1 @@ -1.9.7 +2.6.2 diff --git a/Gemfile b/Gemfile index e28ffcfc2d..56fcd8d35f 100644 --- a/Gemfile +++ b/Gemfile @@ -27,22 +27,31 @@ gem 'omniauth', "~> 1.1.3" gem 'omniauth-google-oauth2' gem 'omniauth-twitter' gem 'omniauth-github' +gem 'omniauth-shibboleth' +gem 'omniauth-kerberos' +gem 'omniauth-gitlab' +gem 'omniauth-bitbucket' +gem 'doorkeeper', '2.1.3' +gem "rack-oauth2", "~> 1.0.5" + +# Browser detection +gem "browser" # Extracting information from a git repository # Provide access to Gitlab::Git library -gem "gitlab_git", '~> 6.0' +gem "gitlab_git", '~> 7.1.10' # Ruby/Rack Git Smart-HTTP Server Handler -gem 'gitlab-grack', '~> 2.0.0.pre', require: 'grack' +gem 'gitlab-grack', '~> 2.0.2', require: 'grack' # LDAP Auth -gem 'gitlab_omniauth-ldap', '1.0.4', require: "omniauth-ldap" +gem 'gitlab_omniauth-ldap', '1.2.1', require: "omniauth-ldap" # Git Wiki -gem 'gollum-lib', '~> 3.0.0' +gem 'gollum-lib', '~> 4.0.2' # Language detection -gem "gitlab-linguist", "~> 3.0.0", require: "linguist" +gem "gitlab-linguist", "~> 3.0.1", require: "linguist" # API gem "grape", "~> 0.6.1" @@ -69,8 +78,8 @@ gem "carrierwave" gem 'dropzonejs-rails' # for aws storage -gem "fog", "~> 1.14", group: :aws -gem "unf", group: :aws +gem "fog", "~> 1.14" +gem "unf" # Authorization gem "six" @@ -78,14 +87,17 @@ gem "six" # Seed data gem "seed-fu" +# Markup pipeline for GitLab +gem 'html-pipeline-gitlab', '~> 0.1' + # Markdown to HTML gem "github-markup" # Required markup gems by github-markdown -gem 'redcarpet', '~> 2.2.2' +gem 'redcarpet', '~> 3.2.3' gem 'RedCloth' gem 'rdoc', '~>3.6' -gem 'org-ruby' +gem 'org-ruby', '= 0.9.12' gem 'creole', '~>0.3.6' gem 'wikicloth', '=0.8.1' gem 'asciidoctor', '= 0.1.4' @@ -103,12 +115,13 @@ end gem "state_machine" # Issue tags -gem "acts-as-taggable-on" +gem 'acts-as-taggable-on', '~> 3.4' # Background jobs gem 'slim' gem 'sinatra', require: nil -gem 'sidekiq', '2.17.0' +gem 'sidekiq', '~> 3.3' +gem 'sidetiq', '0.6.3' # HTTP requests gem "httparty" @@ -130,7 +143,7 @@ gem "redis-rails" gem 'tinder', '~> 1.9.2' # HipChat integration -gem "hipchat", "~> 0.14.0" +gem "hipchat", "~> 1.4.0" # Flowdock integration gem "gitlab-flowdock-git-hook", "~> 0.4.2" @@ -139,11 +152,17 @@ gem "gitlab-flowdock-git-hook", "~> 0.4.2" gem "gemnasium-gitlab-service", "~> 0.2" # Slack integration -gem "slack-notifier", "~> 0.3.2" +gem "slack-notifier", "~> 1.0.0" + +# Asana integration +gem 'asana', '~> 0.0.6' # d3 gem "d3_rails", "~> 3.1.4" +#cal-heatmap +gem "cal-heatmap-rails", "~> 0.0.1" + # underscore-rails gem "underscore-rails", "~> 1.4.4" @@ -156,13 +175,15 @@ gem "rack-attack" # Ace editor gem 'ace-rails-ap' -# Semantic UI Sass for Sidebar -gem 'semantic-ui-sass', '~> 0.16.1.0' +# Keyboard shortcuts +gem 'mousetrap-rails' + +# Detect and convert string character encoding +gem 'charlock_holmes' gem "sass-rails", '~> 4.0.2' gem "coffee-rails" gem "uglifier" -gem "therubyracer" gem 'turbolinks' gem 'jquery-turbolinks' @@ -173,14 +194,16 @@ gem "jquery-ui-rails" gem "jquery-scrollto-rails" gem "raphael-rails", "~> 2.1.2" gem 'bootstrap-sass', '~> 3.0' -gem "font-awesome-rails", '~> 3.2' -gem "gitlab_emoji", "~> 0.0.1.1" +gem "font-awesome-rails", '~> 4.2' +gem "gitlab_emoji", "~> 0.1" gem "gon", '~> 5.0.0' gem 'nprogress-rails' gem 'request_store' gem "virtus" +gem 'addressable' group :development do + gem 'brakeman', require: false gem "annotate", "~> 2.6.0.beta2" gem "letter_opener" gem 'quiet_assets', '~> 1.0.1' @@ -190,8 +213,6 @@ group :development do gem 'better_errors' gem 'binding_of_caller' - gem 'rails_best_practices' - # Docs generator gem "sdoc" @@ -201,11 +222,12 @@ end group :development, :test do gem 'coveralls', require: false + gem 'rubocop', '0.28.0', require: false # gem 'rails-dev-tweaks' gem 'spinach-rails' - gem "rspec-rails" + gem "rspec-rails", '2.99' gem "capybara", '~> 2.2.1' - gem "pry" + gem "pry-rails" gem "awesome_print" gem "database_cleaner" gem "launchy" @@ -231,14 +253,16 @@ group :development, :test do gem 'jasmine', '2.0.2' - gem "spring", '1.1.1' - gem "spring-commands-rspec", '1.0.1' + gem "spring", '~> 1.3.1' + gem "spring-commands-rspec", '1.0.4' gem "spring-commands-spinach", '1.0.0' + + gem "byebug" end group :test do gem "simplecov", require: false - gem "shoulda-matchers", "~> 2.1.0" + gem "shoulda-matchers", "~> 2.7.0" gem 'email_spec' gem "webmock" gem 'test_after_commit' @@ -247,3 +271,8 @@ end group :production do gem "gitlab_meta", '7.0' end + +gem "newrelic_rpm" + +gem 'octokit', '3.7.0' +gem "rugments" diff --git a/Gemfile.lock b/Gemfile.lock index 205599e898..2fd59857be 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -3,40 +3,53 @@ GEM specs: RedCloth (4.2.9) ace-rails-ap (2.0.1) - actionmailer (4.1.1) - actionpack (= 4.1.1) - actionview (= 4.1.1) - mail (~> 2.5.4) - actionpack (4.1.1) - actionview (= 4.1.1) - activesupport (= 4.1.1) + actionmailer (4.1.9) + actionpack (= 4.1.9) + actionview (= 4.1.9) + mail (~> 2.5, >= 2.5.4) + actionpack (4.1.9) + actionview (= 4.1.9) + activesupport (= 4.1.9) rack (~> 1.5.2) rack-test (~> 0.6.2) - actionview (4.1.1) - activesupport (= 4.1.1) + actionview (4.1.9) + activesupport (= 4.1.9) builder (~> 3.1) erubis (~> 2.7.0) - activemodel (4.1.1) - activesupport (= 4.1.1) + activemodel (4.1.9) + activesupport (= 4.1.9) builder (~> 3.1) - activerecord (4.1.1) - activemodel (= 4.1.1) - activesupport (= 4.1.1) + activerecord (4.1.9) + activemodel (= 4.1.9) + activesupport (= 4.1.9) arel (~> 5.0.0) - activesupport (4.1.1) + activeresource (4.0.0) + activemodel (~> 4.0) + activesupport (~> 4.0) + rails-observers (~> 0.1.1) + activesupport (4.1.9) i18n (~> 0.6, >= 0.6.9) json (~> 1.7, >= 1.7.7) minitest (~> 5.1) thread_safe (~> 0.1) tzinfo (~> 1.1) - acts-as-taggable-on (2.4.1) - rails (>= 3, < 5) + acts-as-taggable-on (3.5.0) + activerecord (>= 3.2, < 5) addressable (2.3.5) annotate (2.6.0) activerecord (>= 2.3.0) rake (>= 0.8.7) arel (5.0.1.20140414130214) + asana (0.0.6) + activeresource (>= 3.2.3) asciidoctor (0.1.4) + ast (2.0.0) + astrolabe (1.3.0) + parser (>= 2.2.0.pre.3, < 3.0) + attr_required (1.0.0) + autoprefixer-rails (5.1.6) + execjs + json awesome_print (1.2.0) axiom-types (0.0.5) descendants_tracker (~> 0.0.1) @@ -47,9 +60,25 @@ GEM erubis (>= 2.6.6) binding_of_caller (0.7.2) debug_inspector (>= 0.0.1) - bootstrap-sass (3.0.3.0) - sass (~> 3.2) + bootstrap-sass (3.3.3) + autoprefixer-rails (>= 5.0.0.1) + sass (>= 3.2.19) + brakeman (3.0.1) + erubis (~> 2.6) + fastercsv (~> 1.5) + haml (>= 3.0, < 5.0) + highline (~> 1.6.20) + multi_json (~> 1.2) + ruby2ruby (~> 2.1.1) + ruby_parser (~> 3.5.0) + sass (~> 3.0) + terminal-table (~> 1.4) + browser (0.7.2) builder (3.2.2) + byebug (3.2.0) + columnize (~> 0.8) + debugger-linecache (~> 1.2) + cal-heatmap-rails (0.0.1) capybara (2.2.1) mime-types (>= 1.16) nokogiri (>= 1.3.3) @@ -60,12 +89,10 @@ GEM activemodel (>= 3.2.0) activesupport (>= 3.2.0) json (>= 1.7) - celluloid (0.15.2) - timers (~> 1.1.0) + celluloid (0.16.0) + timers (~> 4.0.0) charlock_holmes (0.6.9.4) cliver (0.3.2) - code_analyzer (0.4.3) - sexp_processor coderay (1.1.0) coercible (1.0.0) descendants_tracker (~> 0.0.1) @@ -78,7 +105,8 @@ GEM coffee-script-source (1.6.3) colored (1.2) colorize (0.5.8) - connection_pool (1.2.0) + columnize (0.9.0) + connection_pool (2.1.0) coveralls (0.7.0) multi_json (~> 1.3) rest-client @@ -93,6 +121,7 @@ GEM daemons (1.1.9) database_cleaner (1.3.0) debug_inspector (0.0.2) + debugger-linecache (1.2.0) default_value_for (3.0.0) activerecord (>= 3.2.0, < 5.0) descendants_tracker (0.0.3) @@ -106,21 +135,21 @@ GEM devise (~> 3.2) diff-lcs (1.2.5) diffy (3.0.3) - docile (1.1.1) + docile (1.1.5) + doorkeeper (2.1.3) + railties (>= 3.2) dotenv (0.9.0) dropzonejs-rails (0.4.14) rails (> 3.1) email_spec (1.5.0) launchy (~> 2.1) mail (~> 2.2) - emoji (1.0.1) - json enumerize (0.7.0) activesupport (>= 3.2) equalizer (0.0.8) erubis (2.7.0) escape_utils (0.2.4) - eventmachine (1.0.3) + eventmachine (1.0.4) excon (0.32.1) execjs (2.0.2) expression_parser (0.9.0) @@ -133,6 +162,7 @@ GEM multipart-post (~> 1.2.0) faraday_middleware (0.9.0) faraday (>= 0.7.4, < 0.9) + fastercsv (1.5.5) ffaker (1.22.1) ffi (1.9.3) fog (1.21.0) @@ -152,50 +182,54 @@ GEM net-ssh (>= 2.1.3) fog-json (1.0.0) multi_json (~> 1.0) - font-awesome-rails (3.2.1.3) + font-awesome-rails (4.2.0.0) railties (>= 3.2, < 5.0) foreman (0.63.0) dotenv (>= 0.7) thor (>= 0.13.6) formatador (0.2.4) - gemnasium-gitlab-service (0.2.2) - rugged (~> 0.19) + gemnasium-gitlab-service (0.2.6) + rugged (~> 0.21) + gemojione (2.0.0) + json gherkin-ruby (0.3.1) racc - github-markup (1.1.0) + github-markup (1.3.1) + posix-spawn (~> 0.3.8) gitlab-flowdock-git-hook (0.4.2.2) gitlab-grit (>= 2.4.1) multi_json - gitlab-grack (2.0.0.pre) + gitlab-grack (2.0.2) rack (~> 1.5.1) - gitlab-grit (2.6.10) + gitlab-grit (2.7.2) charlock_holmes (~> 0.6) diff-lcs (~> 1.1) mime-types (~> 1.15) posix-spawn (~> 0.3) - gitlab-linguist (3.0.0) + gitlab-linguist (3.0.1) charlock_holmes (~> 0.6.6) escape_utils (~> 0.2.4) mime-types (~> 1.19) - gitlab_emoji (0.0.1.1) - emoji (~> 1.0.1) - gitlab_git (6.2.1) + gitlab_emoji (0.1.0) + gemojione (~> 2.0) + gitlab_git (7.1.10) activesupport (~> 4.0) charlock_holmes (~> 0.6) - gitlab-grit (~> 2.6) gitlab-linguist (~> 3.0) - rugged (~> 0.21.0) + rugged (~> 0.21.2) gitlab_meta (7.0) - gitlab_omniauth-ldap (1.0.4) - net-ldap (~> 0.3.1) + gitlab_omniauth-ldap (1.2.1) + net-ldap (~> 0.9) omniauth (~> 1.0) pyu-ruby-sasl (~> 0.0.3.1) - rubyntlm (~> 0.1.1) - gollum-lib (3.0.0) - github-markup (~> 1.1.0) - gitlab-grit (~> 2.6.5) - nokogiri (~> 1.6.1) - rouge (~> 1.3.3) + rubyntlm (~> 0.3) + gollum-grit_adapter (0.1.3) + gitlab-grit (~> 2.7, >= 2.7.1) + gollum-lib (4.0.2) + github-markup (~> 1.3.1) + gollum-grit_adapter (~> 0.1, >= 0.1.1) + nokogiri (~> 1.6.4) + rouge (~> 1.7.4) sanitize (~> 2.1.0) stringex (~> 2.5.1) gon (5.0.1) @@ -235,16 +269,28 @@ GEM haml (>= 3.1, < 5.0) railties (>= 4.0.1) hashie (2.1.2) + highline (1.6.21) hike (1.2.3) - hipchat (0.14.0) - httparty + hipchat (1.4.0) httparty + hitimes (1.2.2) + html-pipeline (1.11.0) + activesupport (>= 2) + nokogiri (~> 1.4) + html-pipeline-gitlab (0.2.0) + actionpack (~> 4) + gitlab_emoji (~> 0.1) + html-pipeline (~> 1.11.0) + mime-types + sanitize (~> 2.1) http_parser.rb (0.5.3) httparty (0.13.0) json (~> 1.8) multi_xml (>= 0.5.2) httpauth (0.2.1) - i18n (0.6.11) + httpclient (2.5.3.3) + i18n (0.7.0) + ice_cube (0.11.1) ice_nine (0.10.0) jasmine (2.0.2) jasmine-core (~> 2.0.0) @@ -263,40 +309,40 @@ GEM turbolinks jquery-ui-rails (4.2.1) railties (>= 3.2.16) - json (1.8.1) + json (1.8.2) jwt (0.1.13) multi_json (>= 1.5) kaminari (0.15.1) actionpack (>= 3.0.0) activesupport (>= 3.0.0) - kgio (2.8.1) + kgio (2.9.2) launchy (2.4.2) addressable (~> 2.3) letter_opener (1.1.2) launchy (~> 2.2) - libv8 (3.16.14.3) listen (2.3.1) celluloid (>= 0.15.2) rb-fsevent (>= 0.9.3) rb-inotify (>= 0.9) lumberjack (1.0.4) - mail (2.5.4) - mime-types (~> 1.16) - treetop (~> 1.4.8) + mail (2.6.3) + mime-types (>= 1.16, < 3) method_source (0.8.2) mime-types (1.25.1) - mini_portile (0.6.0) + mini_portile (0.6.1) minitest (5.3.5) + mousetrap-rails (1.4.6) multi_json (1.10.1) multi_xml (0.5.5) multipart-post (1.2.0) mysql2 (0.3.16) - net-ldap (0.3.1) + net-ldap (0.11) net-scp (1.1.2) net-ssh (>= 2.6.5) net-ssh (2.8.0) - nokogiri (1.6.2.1) - mini_portile (= 0.6.0) + newrelic_rpm (3.9.4.245) + nokogiri (1.6.5) + mini_portile (~> 0.6.0) nprogress-rails (0.1.2.3) oauth (0.4.7) oauth2 (0.8.1) @@ -305,27 +351,45 @@ GEM jwt (~> 0.1.4) multi_json (~> 1.0) rack (~> 1.2) + octokit (3.7.0) + sawyer (~> 0.6.0, >= 0.5.3) omniauth (1.1.4) hashie (>= 1.2, < 3) rack + omniauth-bitbucket (0.0.2) + multi_json (~> 1.7) + omniauth (~> 1.1) + omniauth-oauth (~> 1.0) omniauth-github (1.1.1) omniauth (~> 1.0) omniauth-oauth2 (~> 1.1) + omniauth-gitlab (1.0.0) + omniauth (~> 1.0) + omniauth-oauth2 (~> 1.0) omniauth-google-oauth2 (0.2.5) omniauth (> 1.0) omniauth-oauth2 (~> 1.1) + omniauth-kerberos (0.2.0) + omniauth-multipassword + timfel-krb5-auth (~> 0.8) + omniauth-multipassword (0.4.1) + omniauth (~> 1.0) omniauth-oauth (1.0.1) oauth omniauth (~> 1.0) omniauth-oauth2 (1.1.1) oauth2 (~> 0.8.0) omniauth (~> 1.0) + omniauth-shibboleth (1.1.1) + omniauth (>= 1.0.0) omniauth-twitter (1.0.1) multi_json (~> 1.3) omniauth-oauth (~> 1.0) - org-ruby (0.9.8) + org-ruby (0.9.12) rubypants (~> 0.2) orm_adapter (0.5.0) + parser (2.2.0.2) + ast (>= 1.1, < 3.0) pg (0.15.1) phantomjs (1.9.2.0) poltergeist (1.5.1) @@ -333,12 +397,14 @@ GEM cliver (~> 0.3.1) multi_json (~> 1.0) websocket-driver (>= 0.2.0) - polyglot (0.3.4) posix-spawn (0.3.9) + powerpack (0.0.9) pry (0.9.12.4) coderay (~> 1.0) method_source (~> 0.8) slop (~> 3.4) + pry-rails (0.3.2) + pry (>= 0.9.10) pyu-ruby-sasl (0.0.3.3) quiet_assets (1.0.2) railties (>= 3.1, < 5.0) @@ -346,53 +412,53 @@ GEM rack (1.5.2) rack-accept (0.4.5) rack (>= 0.4) - rack-attack (2.3.0) + rack-attack (4.2.0) rack rack-cors (0.2.9) rack-mini-profiler (0.9.0) rack (>= 1.1.3) rack-mount (0.8.3) rack (>= 1.0.0) + rack-oauth2 (1.0.8) + activesupport (>= 2.3) + attr_required (>= 0.0.5) + httpclient (>= 2.2.0.2) + multi_json (>= 1.3.6) + rack (>= 1.1) rack-protection (1.5.1) rack - rack-test (0.6.2) + rack-test (0.6.3) rack (>= 1.0) - rails (4.1.1) - actionmailer (= 4.1.1) - actionpack (= 4.1.1) - actionview (= 4.1.1) - activemodel (= 4.1.1) - activerecord (= 4.1.1) - activesupport (= 4.1.1) + rails (4.1.9) + actionmailer (= 4.1.9) + actionpack (= 4.1.9) + actionview (= 4.1.9) + activemodel (= 4.1.9) + activerecord (= 4.1.9) + activesupport (= 4.1.9) bundler (>= 1.3.0, < 2.0) - railties (= 4.1.1) + railties (= 4.1.9) sprockets-rails (~> 2.0) + rails-observers (0.1.2) + activemodel (~> 4.0) rails_autolink (1.1.6) rails (> 3.1) - rails_best_practices (1.14.4) - activesupport - awesome_print - code_analyzer (>= 0.4.3) - colored - erubis - i18n - require_all - ruby-progressbar - railties (4.1.1) - actionpack (= 4.1.1) - activesupport (= 4.1.1) + railties (4.1.9) + actionpack (= 4.1.9) + activesupport (= 4.1.9) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - raindrops (0.12.0) - rake (10.3.2) + rainbow (2.0.0) + raindrops (0.13.0) + rake (10.4.2) raphael-rails (2.1.2) rb-fsevent (0.9.3) rb-inotify (0.9.2) ffi (>= 0.5.0) rdoc (3.12.2) json (~> 1.4) - redcarpet (2.2.2) - redis (3.0.6) + redcarpet (3.2.3) + redis (3.1.0) redis-actionpack (4.0.0) actionpack (~> 4) redis-rack (~> 1.5.0) @@ -400,8 +466,8 @@ GEM redis-activesupport (4.0.0) activesupport (~> 4) redis-store (~> 1.1.0) - redis-namespace (1.4.1) - redis (~> 3.0.4) + redis-namespace (1.5.1) + redis (~> 3.0, >= 3.0.4) redis-rack (1.5.0) rack (~> 1.5) redis-store (~> 1.1.0) @@ -411,32 +477,46 @@ GEM redis-store (~> 1.1.0) redis-store (1.1.4) redis (>= 2.2) - ref (1.0.5) request_store (1.0.5) - require_all (1.3.2) rest-client (1.6.7) mime-types (>= 1.16) rinku (1.7.3) - rouge (1.3.3) - rspec (2.14.1) - rspec-core (~> 2.14.0) - rspec-expectations (~> 2.14.0) - rspec-mocks (~> 2.14.0) - rspec-core (2.14.7) - rspec-expectations (2.14.4) + rouge (1.7.7) + rspec (2.99.0) + rspec-core (~> 2.99.0) + rspec-expectations (~> 2.99.0) + rspec-mocks (~> 2.99.0) + rspec-collection_matchers (1.1.2) + rspec-expectations (>= 2.99.0.beta1) + rspec-core (2.99.2) + rspec-expectations (2.99.2) diff-lcs (>= 1.1.3, < 2.0) - rspec-mocks (2.14.4) - rspec-rails (2.14.0) + rspec-mocks (2.99.3) + rspec-rails (2.99.0) actionpack (>= 3.0) + activemodel (>= 3.0) activesupport (>= 3.0) railties (>= 3.0) - rspec-core (~> 2.14.0) - rspec-expectations (~> 2.14.0) - rspec-mocks (~> 2.14.0) - ruby-progressbar (1.2.0) - rubyntlm (0.1.1) + rspec-collection_matchers + rspec-core (~> 2.99.0) + rspec-expectations (~> 2.99.0) + rspec-mocks (~> 2.99.0) + rubocop (0.28.0) + astrolabe (~> 1.3) + parser (>= 2.2.0.pre.7, < 3.0) + powerpack (~> 0.0.6) + rainbow (>= 1.99.1, < 3.0) + ruby-progressbar (~> 1.4) + ruby-progressbar (1.7.1) + ruby2ruby (2.1.3) + ruby_parser (~> 3.1) + sexp_processor (~> 4.0) + ruby_parser (3.5.0) + sexp_processor (~> 4.1) + rubyntlm (0.5.0) rubypants (0.2.0) - rugged (0.21.0) + rugged (0.21.4) + rugments (1.0.0.beta6) safe_yaml (0.9.7) sanitize (2.1.0) nokogiri (>= 1.4.4) @@ -446,6 +526,9 @@ GEM sass (~> 3.2.0) sprockets (~> 2.8, <= 2.11.0) sprockets-rails (~> 2.0) + sawyer (0.6.0) + addressable (~> 2.3.5) + faraday (~> 0.8, < 0.10) sdoc (0.3.20) json (>= 1.1.3) rdoc (~> 3.10) @@ -454,20 +537,22 @@ GEM activesupport (>= 3.1, < 4.2) select2-rails (3.5.2) thor (~> 0.14) - semantic-ui-sass (0.16.1.0) - sass (~> 3.2) settingslogic (2.0.9) - sexp_processor (4.4.0) - shoulda-matchers (2.1.0) + sexp_processor (4.4.5) + shoulda-matchers (2.7.0) activesupport (>= 3.0.0) - sidekiq (2.17.0) - celluloid (>= 0.15.2) - connection_pool (>= 1.0.0) + sidekiq (3.3.0) + celluloid (>= 0.16.0) + connection_pool (>= 2.0.0) json - redis (>= 3.0.4) + redis (>= 3.0.6) redis-namespace (>= 1.3.1) + sidetiq (0.6.3) + celluloid (>= 0.14.1) + ice_cube (= 0.11.1) + sidekiq (>= 3.0.0) simple_oauth (0.1.9) - simplecov (0.8.2) + simplecov (0.9.0) docile (~> 1.1.0) multi_json simplecov-html (~> 0.8.0) @@ -477,11 +562,11 @@ GEM rack-protection (~> 1.4) tilt (~> 1.3, >= 1.3.4) six (0.2.0) - slack-notifier (0.3.2) + slack-notifier (1.0.0) slim (2.0.2) temple (~> 0.6.6) tilt (>= 1.3.3, < 2.1) - slop (3.4.7) + slop (3.6.0) spinach (0.8.7) colorize (= 0.5.8) gherkin-ruby (>= 0.3.1) @@ -489,8 +574,8 @@ GEM capybara (>= 2.0.0) railties (>= 3) spinach (>= 0.4) - spring (1.1.1) - spring-commands-rspec (1.0.1) + spring (1.3.3) + spring-commands-rspec (1.0.4) spring (>= 0.9.1) spring-commands-spinach (1.0.0) spring (>= 0.9.1) @@ -499,28 +584,28 @@ GEM multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) - sprockets-rails (2.1.3) + sprockets-rails (2.2.4) actionpack (>= 3.0) activesupport (>= 3.0) - sprockets (~> 2.8) + sprockets (>= 2.8, < 4.0) stamp (0.5.0) state_machine (1.2.0) - stringex (2.5.1) + stringex (2.5.2) temple (0.6.7) term-ansicolor (1.2.2) tins (~> 0.8) + terminal-table (1.4.5) test_after_commit (0.2.2) - therubyracer (0.12.0) - libv8 (~> 3.16.14.0) - ref thin (1.6.1) daemons (>= 1.0.9) eventmachine (>= 1.0.0) rack (>= 1.0.0) thor (0.19.1) - thread_safe (0.3.4) + thread_safe (0.3.5) tilt (1.4.1) - timers (1.1.0) + timers (4.0.1) + hitimes + timfel-krb5-auth (0.8.3) tinder (1.9.3) eventmachine (~> 1.0) faraday (~> 0.8) @@ -531,9 +616,6 @@ GEM multi_json (~> 1.7) twitter-stream (~> 0.1) tins (0.13.1) - treetop (1.4.15) - polyglot - polyglot (>= 0.3.1) turbolinks (2.0.0) coffee-rails twitter-stream (0.1.16) @@ -555,7 +637,7 @@ GEM raindrops (~> 0.7) unicorn-worker-killer (0.4.2) unicorn (~> 4) - version_sorter (1.1.0) + version_sorter (2.0.0) virtus (1.0.1) axiom-types (~> 0.0.5) coercible (~> 1.0) @@ -580,15 +662,22 @@ PLATFORMS DEPENDENCIES RedCloth ace-rails-ap - acts-as-taggable-on + acts-as-taggable-on (~> 3.4) + addressable annotate (~> 2.6.0.beta2) + asana (~> 0.0.6) asciidoctor (= 0.1.4) awesome_print better_errors binding_of_caller bootstrap-sass (~> 3.0) + brakeman + browser + byebug + cal-heatmap-rails (~> 0.0.1) capybara (~> 2.2.1) carrierwave + charlock_holmes coffee-rails colored coveralls @@ -599,24 +688,25 @@ DEPENDENCIES devise (= 3.2.4) devise-async (= 0.9.0) diffy (~> 3.0.3) + doorkeeper (= 2.1.3) dropzonejs-rails email_spec enumerize factory_girl_rails ffaker fog (~> 1.14) - font-awesome-rails (~> 3.2) + font-awesome-rails (~> 4.2) foreman gemnasium-gitlab-service (~> 0.2) github-markup gitlab-flowdock-git-hook (~> 0.4.2) - gitlab-grack (~> 2.0.0.pre) - gitlab-linguist (~> 3.0.0) - gitlab_emoji (~> 0.0.1.1) - gitlab_git (~> 6.0) + gitlab-grack (~> 2.0.2) + gitlab-linguist (~> 3.0.1) + gitlab_emoji (~> 0.1) + gitlab_git (~> 7.1.10) gitlab_meta (= 7.0) - gitlab_omniauth-ldap (= 1.0.4) - gollum-lib (~> 3.0.0) + gitlab_omniauth-ldap (= 1.2.1) + gollum-lib (~> 4.0.2) gon (~> 5.0.0) grape (~> 0.6.1) grape-entity (~> 0.4.2) @@ -624,7 +714,8 @@ DEPENDENCIES guard-rspec guard-spinach haml-rails - hipchat (~> 0.14.0) + hipchat (~> 1.4.0) + html-pipeline-gitlab (~> 0.1) httparty jasmine (= 2.0.2) jquery-atwho-rails (~> 0.3.3) @@ -636,53 +727,61 @@ DEPENDENCIES launchy letter_opener minitest (~> 5.3.0) + mousetrap-rails mysql2 + newrelic_rpm nprogress-rails + octokit (= 3.7.0) omniauth (~> 1.1.3) + omniauth-bitbucket omniauth-github + omniauth-gitlab omniauth-google-oauth2 + omniauth-kerberos + omniauth-shibboleth omniauth-twitter - org-ruby + org-ruby (= 0.9.12) pg poltergeist (~> 1.5.1) - pry + pry-rails quiet_assets (~> 1.0.1) rack-attack rack-cors rack-mini-profiler + rack-oauth2 (~> 1.0.5) rails (~> 4.1.0) rails_autolink (~> 1.1) - rails_best_practices raphael-rails (~> 2.1.2) rb-fsevent rb-inotify rdoc (~> 3.6) - redcarpet (~> 2.2.2) + redcarpet (~> 3.2.3) redis-rails request_store - rspec-rails + rspec-rails (= 2.99) + rubocop (= 0.28.0) + rugments sanitize (~> 2.0) sass-rails (~> 4.0.2) sdoc seed-fu select2-rails - semantic-ui-sass (~> 0.16.1.0) settingslogic - shoulda-matchers (~> 2.1.0) - sidekiq (= 2.17.0) + shoulda-matchers (~> 2.7.0) + sidekiq (~> 3.3) + sidetiq (= 0.6.3) simplecov sinatra six - slack-notifier (~> 0.3.2) + slack-notifier (~> 1.0.0) slim spinach-rails - spring (= 1.1.1) - spring-commands-rspec (= 1.0.1) + spring (~> 1.3.1) + spring-commands-rspec (= 1.0.4) spring-commands-spinach (= 1.0.0) stamp state_machine test_after_commit - therubyracer thin tinder (~> 1.9.2) turbolinks diff --git a/Guardfile b/Guardfile index e19a312377..68ac3232b0 100644 --- a/Guardfile +++ b/Guardfile @@ -1,7 +1,7 @@ # A sample Guardfile # More info at https://github.com/guard/guard#readme -guard 'rspec', cmd: "spring rspec", version: 2, all_on_start: false, all_after_pass: false do +guard 'rspec', cmd: "spring rspec", all_on_start: false, all_after_pass: false do watch(%r{^spec/.+_spec\.rb$}) watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } watch(%r{^lib/api/(.+)\.rb$}) { |m| "spec/requests/api/#{m[1]}_spec.rb" } @@ -19,7 +19,7 @@ guard 'rspec', cmd: "spring rspec", version: 2, all_on_start: false, all_after_p watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" } end -guard 'spinach' do +guard 'spinach', command_prefix: 'spring' do watch(%r|^features/(.*)\.feature|) watch(%r|^features/steps/(.*)([^/]+)\.rb|) do |m| "features/#{m[1]}#{m[2]}.feature" diff --git a/LICENSE b/LICENSE index d11b8730bf..d8cb29f363 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2011-2014 GitLab B.V. +Copyright (c) 2011-2015 GitLab B.V. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/MAINTENANCE.md b/MAINTENANCE.md index 19200fef38..d3d3667069 100644 --- a/MAINTENANCE.md +++ b/MAINTENANCE.md @@ -2,7 +2,7 @@ GitLab is a fast moving and evolving project. We currently don't have the resources to support many releases concurrently. We support exactly one stable release at any given time. -GitLab follows the [Semantic Versioning](http://semver.org/) for its releases: `(Major).(Minor).(Patch)`. +GitLab follows the [Semantic Versioning](http://semver.org/) for its releases: `(Major).(Minor).(Patch)` in a [pragmatic way](https://gist.github.com/jashkenas/cbd2b088e20279ae2c8e). - **Major version**: Whenever there is something significant or any backwards incompatible changes are introduced to the public API. - **Minor version**: When new, backwards compatible functionality is introduced to the public API or a minor feature is introduced, or when a set of smaller features is rolled out. diff --git a/PROCESS.md b/PROCESS.md index c986013e2f..1b6b3e7d32 100644 --- a/PROCESS.md +++ b/PROCESS.md @@ -18,7 +18,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co - Responds to merge requests the issue team mentions them in and monitors for new merge requests - Provides feedback to the merge request submitter to improve the merge request (style, tests, etc.) - Mark merge requests 'ready-for-merge' when they meet the contribution guidelines -- Mention developer(s) based on the [list of members and their specialities](https://www.gitlab.com/core-team/) +- Mention developer(s) based on the [list of members and their specialities](https://about.gitlab.com/core-team/) - Closes merge requests with no feedback from the reporter for two weeks ## Priorities of the issue team @@ -30,7 +30,7 @@ Below we describe the contributing process to GitLab for two reasons. So that co ## Mentioning people -The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help on those issue. Please select someone with relevant experience from [GitLab core team](https://www.gitlab.com/core-team/). If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. Avoid mentioning the lead developer, this is the person that is least likely to give a timely response. If the involvement of the lead developer is needed the other core team members will mention this person. +The most important thing is making sure valid issues receive feedback from the development team. Therefore the priority is mentioning developers that can help on those issue. Please select someone with relevant experience from [GitLab core team](https://about.gitlab.com/core-team/). If there is nobody mentioned with that expertise look in the commit history for the affected files to find someone. Avoid mentioning the lead developer, this is the person that is least likely to give a timely response. If the involvement of the lead developer is needed the other core team members will mention this person. ## Workflow labels @@ -71,7 +71,7 @@ Thanks for the issue report. Please reformat your issue to conform to the issue ### Feature requests -Thank you for your interest in improving GitLab. We don't use the issue tracker for feature requests. Things that are wrong but are not a regression compared to older versions of GitLab are considered feature requests and not issues. Please use the [feature request forum](http://feedback.gitlab.com/) for this purpose or create a merge request implementing this feature. Have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) for more information. +Thank you for your interest in improving GitLab. We don't use the issue tracker for feature requests. Things that are wrong but are not a regression compared to older versions of GitLab are considered feature requests and not issues. Please use the \[feature request forum\]\(http://feedback.gitlab.com/) for this purpose or create a merge request implementing this feature. Have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) for more information. ### Issue report for old version @@ -79,7 +79,7 @@ Thanks for the issue report but we only support issues for the latest stable ver ### Support requests and configuration questions -Thanks for your interest in GitLab. We don't use the issue tracker for support requests and configuration questions. Please use the \[support forum\]\(https://groups.google.com/forum/#!forum/gitlabhq), \[Stack Overflow\]\(http://stackoverflow.com/questions/tagged/gitlab), the #gitlab IRC channel on Freenode or the http://www.gitlab.com paid services for this purpose. Have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) for more information. +Thanks for your interest in GitLab. We don't use the issue tracker for support requests and configuration questions. Please use the \[support forum\]\(https://groups.google.com/forum/#!forum/gitlabhq), \[Stack Overflow\]\(http://stackoverflow.com/questions/tagged/gitlab), the #gitlab IRC channel on Freenode or the http://about.gitlab.com paid services for this purpose. Have a look at the \[contribution guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md) for more information. ### Code format @@ -87,7 +87,7 @@ Please use ``` to format console output, logs, and code as it's very hard to rea ### Issue fixed in newer version -Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please \[upgrade\]\(https://github.com/gitlabhq/gitlabhq/tree/master/doc/update). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). +Thanks for the issue report. This issue has already been fixed in newer versions of GitLab. Due to the size of this project and our limited resources we are only able to support the latest stable release as outlined in our \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker). In order to get this bug fix and enjoy many new features please \[upgrade\]\(https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update). If you still experience issues at that time please open a new issue following our issue tracker guidelines found in the \[contributing guidelines\]\(https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#issue-tracker-guidelines). ### Improperly formatted merge request @@ -104,3 +104,10 @@ This merge request has been closed because a request for more information has no ### Accepting merge requests Is there a request on [the feature request forum](http://feedback.gitlab.com/forums/176466-general) that is similar to this? If so, can you make a comment with a link to it? Please be aware that new functionality that is not marked [accepting merge/pull requests](http://feedback.gitlab.com/forums/176466-general/status/796455) on the forum might not make it into GitLab. You might be asked to make changes and even after implementing them your feature might still be declined. If you want to reduce the chance of this happening please have a discussion in the forum first. + +### Only accepting merge requests with green tests + +We can only accept a merge request if all the tests are green. I've just +restarted the build. When the tests are still not passing after this restart and +you're sure that is does not have anything to do with your code changes, please +rebase with master to see if that solves the issue. diff --git a/Procfile b/Procfile index a5693f8dbc..799b92729f 100644 --- a/Procfile +++ b/Procfile @@ -1,2 +1,2 @@ web: bundle exec unicorn_rails -p ${PORT:="3000"} -E ${RAILS_ENV:="development"} -c ${UNICORN_CONFIG:="config/unicorn.rb"} -worker: bundle exec sidekiq -q post_receive,mailer,system_hook,project_web_hook,common,default,gitlab_shell +worker: bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q common -q default diff --git a/README.md b/README.md index f5f7a8aad4..0563ceca40 100644 --- a/README.md +++ b/README.md @@ -1,124 +1,90 @@ -# GitLab +# ![logo](https://about.gitlab.com/images/gitlab_logo.png) GitLab ## Open source software to collaborate on code -![logo](https://gitlab.com/gitlab-org/gitlab-ce/raw/master/public/gitlab_logo.png) - -![animated-screenshots](https://gist.github.com/fnkr/2f9badd56bfe0ed04ee7/raw/4f48806fbae97f556c2f78d8c2d299c04500cb0d/compiled.gif) +![Animated screenshots](https://about.gitlab.com/images/animated/compiled.gif) - Manage Git repositories with fine grained access controls that keep your code secure - Perform code reviews and enhance collaboration with merge requests - Each project can also have an issue tracker and a wiki - Used by more than 100,000 organizations, GitLab is the most popular solution to manage Git repositories on-premises - Completely free and open source (MIT Expat license) -- Powered by Ruby on Rails +- Powered by [Ruby on Rails](https://github.com/rails/rails) + +## Editions + +There are two editions of GitLab. +*GitLab [Community Edition](https://about.gitlab.com/features/) (CE)* is available without any costs under an MIT license. + +*GitLab Enterprise Edition (EE)* includes [extra features](https://about.gitlab.com/features/#compare) that are most useful for organizations with more than 100 users. +To get access to the EE and support please [become a subscriber](https://about.gitlab.com/pricing/). ## Canonical source -- The source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/) and there are mirrors to make [contributing](CONTRIBUTING.md) as easy as possible. +The source of GitLab Community Edition is [hosted on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/) and there are mirrors to make [contributing](CONTRIBUTING.md) as easy as possible. ## Code status - [![build status](https://ci.gitlab.org/projects/1/status.png?ref=master)](https://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch) -- [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.png)](https://codeclimate.com/github/gitlabhq/gitlabhq) +- [![Build Status](https://semaphoreapp.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/243338/badge.png)](https://semaphoreapp.com/gitlabhq/gitlabhq) -- [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq) +- [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq) -- [![PullReview stats](https://www.pullreview.com/gitlab/gitlab-org/gitlab-ce/badges/master.svg?)](https://www.pullreview.com/gitlab.gitlab.com/gitlab-org/gitlab-ce/reviews/master) +- [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master) ## Website -On [www.gitlab.com](https://www.gitlab.com/) you can find more information about: +On [about.gitlab.com](https://about.gitlab.com/) you can find more information about: -- [Subscriptions](https://www.gitlab.com/subscription/) -- [Consultancy](https://www.gitlab.com/consultancy/) -- [Community](https://www.gitlab.com/community/) -- [Hosted GitLab.com](https://www.gitlab.com/gitlab-com/) use GitLab as a free service -- [GitLab Enterprise Edition](https://www.gitlab.com/gitlab-ee/) with additional features aimed at larger organizations. -- [GitLab CI](https://www.gitlab.com/gitlab-ci/) a continuous integration (CI) server that is easy to integrate with GitLab. - -## Third-party applications - -Access GitLab from multiple platforms with applications below. -These applications are maintained by contributors, GitLab B.V. does not offer support for them. - -- [iPhone app](http://gitlabcontrol.com/) -- [Android app](https://play.google.com/store/apps/details?id=com.bd.gitlab&hl=en) -- [Chrome app](https://chrome.google.com/webstore/detail/chrome-gitlab-notifier/eageapgbnjicdjjihgclpclilenjbobi) -- [Command line client](https://github.com/drewblessing/gitlab-cli) -- [Ruby API wrapper](https://github.com/NARKOZ/gitlab) +- [Subscriptions](https://about.gitlab.com/subscription/) +- [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 CI](https://about.gitlab.com/gitlab-ci/) a continuous integration (CI) server that is easy to integrate with GitLab. ## Requirements -- Ubuntu/Debian/CentOS/RHEL** -- ruby 2.0+ -- git 1.7.10+ -- redis 2.0+ +GitLab requires the following software: + +- Ubuntu/Debian/CentOS/RHEL +- Ruby (MRI) 2.0 or 2.1 +- Git 1.7.10+ +- Redis 2.0+ - MySQL or PostgreSQL -** More details are in the [requirements doc](doc/install/requirements.md). +Please see the [requirements documentation](doc/install/requirements.md) for system requirements and more information about the supported operating systems. ## Installation -Please see [the installation page on the GitLab website](https://www.gitlab.com/installation/). +The recommended way to install GitLab is using the provided [Omnibus packages](https://about.gitlab.com/downloads/). Compared to an installation from source, this is faster and less error prone. Just select your operating system, download the respective package (Debian or RPM) and install it using the system's package manager. -### New versions +There are various other options to install GitLab, please refer to the [installation page on the GitLab website](https://about.gitlab.com/installation/) for more information. -Since 2011 a minor or major version of GitLab is released on the 22nd of every month. Patch and security releases come out when needed. New features are detailed on the [blog](https://www.gitlab.com/blog/) and in the [changelog](CHANGELOG). For more information about the release process see the release [documentation](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/release). Features that will likely be in the next releases can be found on the [feature request forum](http://feedback.gitlab.com/forums/176466-general) with the status [started](http://feedback.gitlab.com/forums/176466-general/status/796456) and [completed](http://feedback.gitlab.com/forums/176466-general/status/796457). +You can access a new installation with the login **`root`** and password **`5iveL!fe`**, after login you are required to set a unique password. -### Upgrading +## Third-party applications -For updating the the Omnibus installation please see the [update documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md). For manual installations there is an [upgrader script](doc/update/upgrader.md) and there are [upgrade guides](doc/update). +There are a lot of [third-party applications integrating with GitLab](https://about.gitlab.com/applications/). These include GUI Git clients, mobile applications and API wrappers for various languages. -## Run in production mode +## GitLab release cycle -The Installation guide contains instructions on how to download an init script and run it automatically on boot. You can also start the init script manually: +Since 2011 a minor or major version of GitLab is released on the 22nd of every month. Patch and security releases are published when needed. New features are detailed on the [blog](https://about.gitlab.com/blog/) and in the [changelog](CHANGELOG). For more information about the release process see the [release documentation](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/release). Features that will likely be in the next releases can be found on the [feature request forum](http://feedback.gitlab.com/forums/176466-general) with the status [started](http://feedback.gitlab.com/forums/176466-general/status/796456) and [completed](http://feedback.gitlab.com/forums/176466-general/status/796457). - sudo service gitlab start +## Upgrading -or by directly calling the script: - - sudo /etc/init.d/gitlab start - -Please login with `root` / `5iveL!fe` +For updating the Omnibus installation please see the [update documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md). For installations from source there is an [upgrader script](doc/update/upgrader.md) and there are [upgrade guides](doc/update) detailing all necessary commands to migrate to the next version. ## Install a development environment -We recommend setting up your development environment with [the cookbook](https://gitlab.com/gitlab-org/cookbook-gitlab/blob/master/README.md#installation). If you do not use the cookbook you might need to copy the example development unicorn configuration file +To work on GitLab itself, we recommend setting up your development environment with [the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit). +If you do not use the GitLab Development Kit you need to install and setup all the dependencies yourself, this is a lot of work and error prone. +One small thing you also have to do when installing it yourself is to copy the example development unicorn configuration file: cp config/unicorn.rb.example.development config/unicorn.rb -## Run in development mode - -Start it with [Foreman](https://github.com/ddollar/foreman) - - bundle exec foreman start -p 3000 - -or start each component separately: - - bundle exec rails s - bin/background_jobs start - -And surf to [localhost:3000](http://localhost:3000/) and login with `root` / `5iveL!fe`. - -## Run the tests - -- Run all tests: - - bundle exec rake test - -- [RSpec](http://rspec.info/) unit and functional tests. - - All RSpec tests: `bundle exec rake spec` - - Single RSpec file: `bundle exec rspec spec/controllers/commit_controller_spec.rb` - -- [Spinach](https://github.com/codegram/spinach) integration tests. - - All Spinach tests: `bundle exec rake spinach` - - Single Spinach test: `bundle exec spinach features/project/issues/milestones.feature` +Instructions on how to start GitLab and how to run the tests can be found in the [development section of the GitLab Development Kit](https://gitlab.com/gitlab-org/gitlab-development-kit#development). ## Documentation @@ -126,7 +92,7 @@ All documentation can be found on [doc.gitlab.com/ce/](http://doc.gitlab.com/ce/ ## Getting help -Please see [Getting help for GitLab](https://www.gitlab.com/getting-help/) on our website for the many options to get help. +Please see [Getting help for GitLab](https://about.gitlab.com/getting-help/) on our website for the many options to get help. ## Is it any good? @@ -135,4 +101,4 @@ Please see [Getting help for GitLab](https://www.gitlab.com/getting-help/) on ou ## Is it awesome? Thanks for [asking this question](https://twitter.com/supersloth/status/489462789384056832) Joshua. -[These people](https://twitter.com/gitlabhq/favorites) seem to like it. +[These people](https://twitter.com/gitlab/favorites) seem to like it. diff --git a/VERSION b/VERSION index b26a34e470..9c51f30944 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -7.2.1 +7.10.0 \ No newline at end of file diff --git a/app/assets/images/authbuttons/bitbucket_64.png b/app/assets/images/authbuttons/bitbucket_64.png new file mode 100644 index 0000000000000000000000000000000000000000..4b90a57bc7de93163275f99ea2971e2740af98af GIT binary patch literal 2163 zcmV-(2#oiMP)2uhW|vhI6clA;HOc~sCx{2egDXMtKom7mKv^T8ct;fPlX#%Ui}gx8f}-Mq z8i53&iTVRjz=KFcSXNM@D}srF8g{ySd}H3pR^Ig1Oi%ahB0rw&W_G&jtEa!JuIj3F z^7%Yyqwm?bInLn?j(6--#~D1u#044R2)luLa0a**G=ha-71#u}0UzW`Amn$q@!C}^ zYc{xsb@XMOj*ua){w;gl<&IN7-g#rmivQY~^Mn6>mLkB7X!IUn2)G{11FIQuzJvIa zb-uwEhJdaSGTbBrwBcRBncxMm-V$5@a_!(71cG0{a(!T_cO7^RoCbDR5pe~0Mh4V@ zyI4U~s%itn3f}pU^%SdfEw}^J8X4e~D!^j~RN=xIV6)s^pFcxOgSS)Ir(iyK7R&@U zf@xqfm;lCui@+sd3b+c~0%n1izyh!gYy>+ZvyCxGYI5QLI4}i z!iiM)3K(9bu^z}qkP)(ij^;{tj!EySgorMahk!K@(2D}RNCE5uFG4^b0$MHoFaVe7 zsA~l70hc7s0OA>Sc=Da;Ahcv@D+J^r;AwUN&o%}$LqGrlL3;wo$cVd~u{1nQ5t<>V zNERpo1OyQ9GX!*J7jWAXu$~!U6Hs^--wgT`NqtRs&xUpjx#P;doCjT&b2WoT{#MvMFE*80a*%|Dgt65laicFd&5$%C%(@3tYs9>fCdW4+A?4y1+b!u z5egNphXRJ$GQgvNdIO%xF90s;aG_!a`HD8MbY z7FbLH{w^TEr+`J)wE)RbKqCd@b^!r73V56XvNi!(3YgXb84wDU&Vb1xz?uQ0DmV!G z#Qu5ugWw1X$k+sU6mSRx{9_i70zpKB{~*531JZ0f*bV`8G6q-&!5RoyD}g6YL$a7l z>A|3m4CZGaim2l3c#`nWA1l(7SfD8pRBoL4T1En!H^Y6YT;A`ZXqk*45zyuKxs|yTa zr6hL$8x36cYnCTlZMw%tX~tM^wv-w7)!g7D0&2mQIQFHFKL8xrEqc+i0D4NE0uFdo z%8YXZeg*qcfE!1EO95H%wG^?3$+f{h94Uj@9_0{9p# z+<|%0r{V^2lccmm>cKd0E;yv{xpbM^<2GJmeU;^Kd%QdW_sGZ{w-5dWPBbKV#1TAF zX#6nnx4cd-l^n0RBMku;h}ClD11(^L>?Bu#YEmV~aYliyQbsu$FgA$*m+9C8d|xn| zU`?=tgW>^}#ZVvc5NIu@@e<>D(2Z?y%RMFL8Qag>l1d<(**C8S=YX;NeJ=P-v!_&~ zoBnBZJ1^NWITpYI*V#5D<7}oX3CqpN0jU!t_9OxT`zO0YBU4n*4~4Nr06iiXvkW~4 zw1d0Rdr8d61*lDM4Ac0pi2mo>grv0LswdK3VN4QhiURbF4H*D#3l(IC-CV^El=B%ZZR5Cy%EHMV5Ku&7k3kJ2*&5D@|U@co?6_fFw(WGy(AWqzjh5i_QTB<55nLX%`_t8aAj z!fy^Hoh=4n zkPLvSjsZPEOR>d@3K4>04Hd!|i>;r)Ua5}(#HH1xZE4u~CqyKcPvo$6DgtyMd|m}~ zA}V0EblG$SNTJhqVgXY+Lcqx~2J8%kzT(Ii9Kvd)ZB3oHV}T0(4N^hf~*P zThOszblPQZ;9c(_v2>^at++clAIxprM@Wn|dJgY+H;th)1laz&d~Yxu%m8nKFTs`= puf?};oj;Fd45Q%4{|-+g;9pZ(FBz?c<5B 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 iA}>IXTxnNYoc13tyzJ2T9nO{j0000}gZ87xUe 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

X%~0la9wB4oXwju zWm>O7AtSrGx?Jpb0$5Bs!C)}RhQ=msdiu(q=H`|-Hj8EEayWgJMl2SaauZ8PfCdN- z3hY|Dc8wt+F`kVmKsGx;r_q2Rc9+n-y}g+%HaiLc*mvns`qA+i=z4o=3i7ir3>lg@ z#7!(2j3^4#)KoWY-?sVip~FXWfwI)}eUP0LA3f`*gZnZK2E7I$gjA}owv#80>}_jn zDZUREa6gEv`R=<-1pv@6bLNao8jZTG&oh+OYVBUNGW`Mo+*-c;?HYox_xz{*uwJKC zWSu&eQC(G0fDocclBCK@Z&baqXu&D~2psVAHZdVO?eyu)EUVS3LkJ;RXGg<_2MfR2$CczlA@|^R^EK&g;CC-RfRM{oowAO0Q*v^0h`ZRshsWm&SuB>l zurRMmBJrg2`2z97_WKASV6#}7g!rMYQ>RUj=5l%7aQixrBU&*`EEeOju|rgYV}=Ob z-Nh2@5qf9<0IS8)ed%IO9*fCljTo6chQVO4+S^;}zWnm@1KYOmJ_rEP2N@@L!^i7B zF_}z?+vjq2OG!!b#a%mo*wNjs>U@;*K8hkO1VN|}MOht>Qg@2IXMU+0wIJf7K`$1 z*2$yCj~%+8)Abr3!3osEk^v7hEY~a)alWF zegWYCKx=7flz;Z=KM$6c*PL*GtcRAckJtZgN4H98cDh@oQZ|3E`n~VYW}P}nlBBjT z+`3;P>{2S4Uw?gZ<*=lr?$M)1Ym$?b)yc`p>X9QyX-1D4)v|5tmLi+YrhU*=cLa;Q zrKPEC#XEofy13})EGG-4QYlUA|MB5=00?^|_VMMHr`_hp1H8xV+_5#wXfQa#p6uw5 zRxf&K-j|mP^DiNU?0tKL`|MHOkt2tSA|k>o9YiYOcq!(lr4=+cHPsBn>-e!F=k0d8#i>AB zYwNAI-dvLT^OeFngpmCqct1^bbye%MY15hjphqlr@A>e<52|~6b)64r{GDku8jXC- z2k)IbnR)z*qlrT{o6U0c=%KR-af4^wgYomR@zU;xaLQD+ik?ztkbn?*Q`9%-P7Iq7>>{#&LRza_w2g-#b=*f z?d|RDb)FsEtSrx;oHBM90Qfwq!&hHEm+@?I(a)I_McHp7E|YcV_N|vb|JR27dc96| zpOB^yqDYFO2#TTzM~^|>D_BU9H0GQ?m%rxymB-|Ane(i{UQ~4DB$tgp`>4j_kLUos z%}&V=?8`7DB*ck=fK-Mt3}Itsb0Zqb7IU!BZ6ewrjndsS8Ct(ED^KlgAK4jHJq`ELpa0DkkHO?~Im z#he<1C_*liUrC)kX~X4$ON9@5p~7jsR;!gQ{>zL1K7Q;JLeZZO^gW34^?|qvE}H-rQYNQ`K@mCpe8a8V&l5UwnG*?3t4n zEEbb;Z^n+?sHl)M01!AQd3NUDKlUXh8o=n)E8p2Im9}Z6()QZVKilwQds|!m{c63% zV(HnlXJ=N<`Ll(sEzOFxAFSMmW03r~^u!;>pq>H%gB7HguM|}@HP&i}3>g|fC_F-R z{rc64fPg@Ti;IhUzw0HOCR~O{vUM1{yjKkr$ezM{3yMEZZ>stg+>Fo|+F#j)c zfbo}`INJJKO(o@}MRFf6ZwZ&faaVT9Y6Aj-M07esvdH(%M0@Ja@7ijqIcWY zANFToDA?urh00?w{;@gWH$PNz_?bP^#4{$C2?zw}e~{{cDhNt3<1 Rz)t`G002ovPDHLkV1k8$(}Ms2 literal 0 HcmV?d00001 diff --git a/app/assets/images/authbuttons/google_32.png b/app/assets/images/authbuttons/google_32.png deleted file mode 100644 index 6225cc9c2d77b8be6b060d212e1624b86935a46c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1611 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 Dtzq$= delta 3435 zcmV-x4V3cX80{L6BYyx1a7bBm000XU000XU0RWnu7ytkO8FWQhbW?9;ba!ELWdK2B zZ(?O2No`?gWm08fWO;GPWjp`?4Ejk#K~#9!?OS_{71bI4eP?FwzW463xU$RQE>#`^ zkrhD^jf=JgLq$QNv5Izmq}KESZTd*s#6qJIO{+*^+M*O}YJV!IDVkO)(zqZ{%X6j5 zLaDGUu**xhkA=I>`TEDa=bo8+@9s8jA}9Cc%$z%OW`5uK-scQ(saz_T%BAuJE|lZ7 zl;b`Bi^8rtBm@uu6abppivM%)I)GjP{buEX5`X}x03vJGt{pRH&YT|?6&1}PqUf>H zop|!qUD&Xr4SxWE%uy!R4Z|-Qr((v0%cTSMC*o*-a2~o_KC$nyUYj}nJ>6X-MZ%uO zm@bNU>-?A9{k*L4==nn?G4z2DKm$;)e*O9z=FXk_h7ckO0G}Q`k9(IjA(83>2?B^f zCK_f2nDMK}rs1BOYNdnfi8$Kt{VMv}kDD*f=8uinVSl#!*=A_=5yZO3O$T=VX9g4E zJ1+L#^I*ZNa%N9co?#YD9w)QH@qrplB+D zrr~d&oJLJWA+D||O{aKXKJur{K z(FYiPPJf$Utt#`cEQ!0}x&Qdn&UR=K1(Aq~yoiEG zo^foPtBB+oRnr7g{W^ZT=}4e_l{~fx6p27YG?1o&G!>+&5Sj|1DIiUOP!*6WAXLR5 zkfJyoV{(t1D{LZJgaM&gh*>5c5ya{;0w@a>EU0j^J^<`Ka^9G6bCxyLn0@11K+}ZD z6@LbIyu0rdlBqs9i&fPizhRaQn*3EYs!$Ek7H~^k$i*dY@Fsf0UK1dgOI>Y}~j}OQVdb@r6}wd4Dy48#CnXfA{0lfdq(LGtE$FmapqS@Y zYtAsN>ny9|sHJ_-N5}uaueBqk)d##+u|AAECT6{dL|IqH2OpUv!(19 zY=LGrh`<2IG=H{_N&s<|eqgv)ch282zN$p(hakk=VgP$uV*~d?DgBsaovwc*!+)L} z2myNh9pkD?qzE~HC{{Ui>O#2J9yplq8r$&;clS95rq0Q)zirZG_V6rw40{|iyL5KR zEkWPmgIT!tvNjm5%%vVW0!*w#alYz#9hSZaO9_!ifFlRJBZc7Pp!v-W?;w>RfCp~6 z0ieSfYUA6)o28#32-Qm=xcc7nYx9{6tRfO$TMc6i?5T6dqLqZeg z;5wk|=773=KH>C5+<3eb(~oyz*5O2ObU4%mC@IkJ^&4xjYD=4Ay8O>|fqw^pDRpJu zh_Ew>&XvE!wyGkmy{a4=YDr{w0V4OZ4_fi6npMpx$Y@Eb4Lx16>Bb#es#|Q)_~Kr zZ6c!J&2dq@Ii>^)T4MO#&VRF66CzXzZu_Jo@W1Eon}jhViX5?H=vOn;1XT{5EJ>FLAq zz}Kn`(aJF;rT{1gaCzn3s}ELGl;VXarepTC)q#T~yZW%FHHJecFQ7PI!IZi(58KCj zFJk70&*I~FTRJG2+{4{xDM9mGXXo5H*8Rs$9U1SziD#OtP0zN+FI#FRKQwW%2amtB z1Gi7B!6RS(60W~$n17TT8paPt!}#ISQI@p49Rl9P`f2^{>wPmFq$GWM<3zDK2qnmt z)5&;$-$^vJoJDPA5pEb$fsqvj7+GF`s-07 z6kPr6Bbqc3&N;;znXQ2NK+CS5IG4iPyV{|uWXZn;|#)Z2&wuJ6O46_RJpaQhCPNVodX=nu_ALu#EnYC1T7q+bWsxE@HT(>V#?1Db zJmkXuzI*}8VsFklG72StkzO}g++uZRhVEm3of*H`bR?7u0AP9jLs)(N6Ip=QB{6~S zyDyVv$c_^-a)8Bt2CMsZfZ6K6iE|h6!TvKDAJ{m$0e^dEKZ~eV-~*}410g>^J%lSH z;rO7e0J-CNW|ZWsm~nL_CXR~Y>Y7rN7N{6kU4qgAEvJXpm)D``wk4Rk=}E-9JAGW> zBZ4l|`ZC4nb8{_jpE?S&r&JHwI(B{eXsnt39n5c9W~^A{Yy#m6q5?P| z12IAgaDU&eWALN@glR;^&|dOF|z$Z`2KVVd^9J z{?3=vLFyr{ahR#hv4jRg%21K2Q%%HieG8c>JAS zU^)k!L*gQHKn4juw|fv-Vf|nu^tPpPkU>7URr}-ML$`uIUlS|}SKOEmJd^xq^!Irv zVgW!uwj6x&RYNe`52XzLhF=%8K5XcQ;f?7cP;$Ac{gK)C&q+xR!VkFf7L;&r418pX zJbzus8AL$B72NtTI3X8@h7s4~20j;*10leQpI{-MSr7Eavjdt*ADH{M_()^0*QL8t z;F*FaNuFQ;iv;01psO`FHz@&^RbT2T_(F4V;1Q5;Jm2bNtt&i zv3&0v-ZXcRF3=?bkB0+t4~8;2LSn)iK#is+YxDp)_f^)tkuIthfmA1ZynOGQh<|q_ zvU7D2<{0G;K9m4m-ADoe71TjYX#$j}JYy9U#_JMsDVz+dbpoX3Gl#JBqg5`j*_c2{ zHYf-*4?HKxhuUy}`(m~zi0hkyXhUWe57GsNlOZNI*|Xn#`VeNW`>q4t?F#FebOGck zG8teY5(DCZxr?!*=lqrvwmvARgMTum38MZ3pyIA{GZ2XI+-^vug1L}^VQ>8UEA#Jublr1qnYG97 zjRCPHaAz~n7c*M(nN&U#P|HANGYv)R&I|5-9ZR;aLcA-PnOzy-8y_;UK7XSV8#)%! z_KtrN(Rlz}VD4j}pV?TfF!vUdLS*c|B_b6-gqceieA(|_d}z-7kFWfprfJ24@wuDY zc4Nu5SFrim9{IL1&Xkqkol(qC~O<9qUTs_5B;Mc+>M(y7grojkRYNx%r)*-1Pbd zeBQlPyw~xR*m0(Xgoq2G6oGnWf=Z4A*hEkWWPT#Uh0G{ra2WxGAkqjBAX3cM*sV)Y zV6?(&2_!&d?5s7ewKlj5$xcVd%V4&aA$9Xa2Us^*R%d42?gMl%>wnDLZyy_<$#elo z5)cPbCjs3I^cweaA2aJP?qSImTOtA!kVOO(m^B}ON=CoIDK-&|a~BcFXbr-eAc>4^ z+~n3j3*pu0Dz#Z_0FX|_P#USo$g>_1VRX{!x_d{H4vmIpezv-v4FTCmP!=K2`aD# zfSG}}YZ+|J-K(a%9NK#=uBpUuQmMIP4!@jR=I9T9Koyfi?(G~+1|UHi!}UINVYs$V zcQ`aZ$G@$qvf$mX7Rc2$F$|*|$a>@M$@5&}m$UBLGnZ2^jHZvl5Y}F-l`DzN*Z^#3 z9Y4K~h(>p%M|y!qt<8e{WYKF)pCk#MNp zTFb1hzjUg&n=pV|2hhwOUnFjjB@qSiHbHCr-1` zbidaagAtAeAGM(=DEYC@v!Y z=fd}&oc!?BSC&uxbR1#H%~s8Q(1b$LD&2iXU1usNzRQ$jT@m>&wfXT2dDLn;_AVg&Z^hEhu3b#1fXzVgKjZ=U?)ul>~@yp&?Z^$0Kq z)<617zxnvxAAkD9-;PG3V_nxV8g;a;(bVQ5NU1>zkmN8p>lQG1j|(BsebzgPQ+*2Q z6bV!AbNQU|Jh6G^)$jbmb6@zQXMsyVT#Ep0M*io2?IU+T`qTe19*>WW$0H`5(YT}Q zrq*Z@2!zT@L-L3Ea!Y}-RFe z9dP*04?X_*Y0Hn-#;mQ4Cbl-qfKk`zTB9`pk%W_|P2mjAPfY@ZoTi3-vNFd*tuE(l<9wDbZGrEQ^Mo%oc!9vk49}NKuLcDM&P9o zNtK9_?tkGV+-m9;(xgx+Qfb|YNN*e>Y!pdYQ|}ZS2onlMYeznld>X*j3eW*-QajeN z-_L}Yk?&Ff(Q*th2IeuMkWVs^=BZUuU`jj?Awt=zB=JT>&{_~76h;axp$LM=E+Zr( zL73YLc>&&AfE)uPW-Z+%;b}bTM3z$k5Dl8}Vt;mMSO&i`|m1u&d6rjuobn_TB9z1>v z02`YZxNv!g)_U9rp}?W^n{X?@97YeENfT^VyHwi$)Dv3f2yA(Jqlz zZqA}1TmPB`xPp^#brlj$^6J{G<#)rU9)CQ+?MDw~`U((242hXt;Od7kn6xYdF7fpU zuu#O%xYUl3)`VNY0s z)^hNgmxaL^ETa5Cb$db$x`u3;AVMrZw1|*(Ei){_vk|W@>=Yxhr>Za zfcGPSSEPPXp=K<5<5xd^Ki7-@dVk|A|M>jty!zHA3ZrNc*#&U=Ye&1f!PV)!sIO7F z8;Xzq$nE^@&pgJ5@3{kY4YgKRM$3jN@3?OPRLR=q(4#1B$ToOi@GGBs7+T92q;w%u z^R~9$J~II0K?YQ@{eEOX!JS7BasTmKavDub(t~vn-ggER5rS}}```vaM3Nk&SrH@r{${{8Uq8d?^B0Le z@}Q-*e#z4P3y|bVKeJ&MsDhXqjn~e;%U^!=1!8*#_dz(4tZ_|Qsv!GZ3a1E_e2q(n z?2YG6o#V`fD`h5O?xD^o>3>UX^VZfSo_TSD{>nDq>2U<)DHHW9kn|aApPQj{9<=;G z1d`Amc=mf|dHMADdHPN*f~cQcvMkCea3Ayg`< z1zZLHIpIEt?Vi}#AtF|O7f?i)3(y-!3N|Ia6Xhs8a803W%*xSKEq{b!`9PY%^S+;%Q-w1_(gyEy45&NQ`f@E5h}@>iCd{6= z{qPzNlc}Eq)Dz26)Vy#ALKM>@?TBQ@X9YN_QE~K*0CV!YR$q7vtai! z=`$=DP+HsvBS1cIb${yw8OL0ZH_l#QFFg7~$MR6L;i*Tskw;$*3rI6yrIlmu#j+gg z6gVL7gAqVzAp)gE(w6BQ=H1KNT-@$?FWhltjbD81KIj^ZIy^D84!RCTZSK>UUYqzf zF=qGViE<}IJ&|$AfU-~ZL1jStG+3lw5H@{|%u{=Q;~ab8iGLrxi(mf9hd8o+2#rTe z8%=C&ZY=7@Gi!OBQHs$TrW~9m?{rCizq3HX4Ekgaf)N5tlOgBn=ifNPq~Kex zo#FeBUFP`FLw{Tie(c1pTpf<_QxD(GlRt4UzyA-XYTs_<*zz zWELo@W`FA=FTbS`fg}KsQ1swHA?O2*dcN?r7kTET4L*KX=WpSi z9h_TY*X6KF`uiXR7=8yB#+qA%X@KRy0Sc%Ino-}N^}Moijz2$re%5zC@xYyYc(NpZ z;Er23am!(@7TqUoJu#5CW;(8(2aH z`dCa=YCF&x8<(%}#kbG%PXZ7r?usKgh^%)6m_?U!l8=e68>7k?ALxO|U^N&)Wcs5b zK#@MDCe+Vk31|%vXw}*MU`tzlFK5q7Zi$sALBE zxws@9Stmmb9=D2o;_}wHr-3d_gUfUzB!4wYU2APDIE1uLf92HihaUSZXpIW3_|Bl7 z5!5yL;QFF2f-Gd&;Hc%A_9ZWOCq5*Pt+j0_n{4VOOQO@fF!iIR zHnp)PUTD0z@k-xsU-|YO4?gk)Xw6Xb7pET_R~3r|ang1bsMIm!>(yk3B?jdG=YQ$H z|E<@)@ulymwF@bLb3mGzZFjZx(@#x^26X4&czNr}=9^zXdg9*O*4B^S6=tmxjYu;y zpQKk9E9*-`5AC|pNf(8-ffznU%%LJeC)5FB@DZEa zj6Wbaf;1L1q%M6E6B8YC-_pcmS66P7P4eadIqqNUo>}IPj*lFgIJSs7;BK3#x r!W(KP;9(3UL(KJT25;gfZsPv{r$~SZ`|)F900000NkvXXu0mjf+(71| delta 3325 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 Hu0mjfIxQtz diff --git a/app/assets/images/bg-header.png b/app/assets/images/bg-header.png index 9ecdaf4e2d50de747f5249860f10d6debc33cf4e..639271c6fafc58a2007f22478ba2d52e57c2a2ee 100644 GIT binary patch delta 72 zcmcb_7&Sq{iGzuOfx%60!(kw$=;`7ZQo)$~k-smw^2-lKZgF${)9>%^kEr|qi*s3I bgTe~DWM4fhjJS) delta 193 zcma#L#5h5so`so#fx$Y-YB`W%E_U(^;o#u7{m}mbkjo$76XNP09B1w5ZD!+YXlAEl zXmM-qL<^u4XMsm#F#`j)5C}6~x?A@LC@53n8c`CQpH@?HG;qKe-bC?;RGZWBNMX23PM}(3rkh^g=?BA7^mX=;wk83`_j6LTK zP2}QjK6!AiDYM^#8Fyr)dsKOG`_?gVBRz;}1%{CIA2c07*qoM6N<$ Ef{v*}AOHXW literal 2976 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μtWws*I&mrRkIh6Jxc;FFc44v>F zZ>Bg7k=lAIz*{nNapTo`PR6&YOlfJIE8)*^b;$`<_kncphh0602@U4?W}SABMHAWQDJthOV#C*-8h?2Wt1m&uF=#q1~kHVDH5CEZXrKvr@ zKL+qXJ<0sIyEj-To8sIPM#%+--ugB96s?k7(`#Cf6*p?ak*eJe&ka*&68d*eU2y^{ zM+cm3Qe5O9kUkrKsc*Cbo{ehUgjHZE)?_t1YG-^|dAle%At!P&07mg>V38bj*9I2E zU;VWo9@jSKCIIWbYc?yW?p4iH?I3kfk3Wzn-3Nhu0>|Ad5sjbNL>>v%n@*v>4I


+y?CxqDj`Rq1V9&GB8+rQm+$$A%0Ag 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 diff --git a/app/assets/images/diff_note_add.png b/app/assets/images/diff_note_add.png index 8ec15b701fc0e806e45dbc10cda9ce068f606a0f..0084422e3303593e2f067c7de013413f393b0f88 100644 GIT binary patch delta 403 zcmV;E0c`%W1)>9x8Gi%-003^iq_6-00clA@K~#7FeUiOPTTvK?UovD6%_eTKYfI_U zmX;DkoOE)R_xS_j$0%mX;2E`S5S~Q2@C)L002ovPDHLkV1f!@#EJj_ delta 678 zcmV;X0$Kf{1G5E?8Gi-<006|aY&!q|00eVFNmK|32nc)#WQYI&010qNS#tmY3ljhU z3ljkVnw%H_00LD>L_t(Ijir^nYZgHeho9feOB7?_M-eTA_z$Q^ASo0gB5IXF5Yd}h zXc16Egos+$Ngxmq8x=&5IV`MY!A>6>;UFg32p%2fk*CsQ(2aEOBw|_RtW?k zVo{-&o09q?qC?W6I9k<2M0^G&B|QhGft|p9U~5FQO9}wB0OfasO<-3|c1fB6MkHN# z_n9=t%nnJq27m012#N$He3Q)Vbwu;^EvLi3h_BVr;+G+Y@2Tz@Q@bq*-OKMLRclGG{QmLwXI zG#wF_B&`MRN%{i3t%S8F+jS`z=&MfhJih{9W&^;+h`5vI`FN7NF9k1YUI95<>+6u=yLbTy2KR$ZNS5bI9xd(^NRm0$|NZw)~pz$-Hw2d>iE0Re0PP68LIpvqXlL{rjjpcmMe2r3nH0gvkh zCvilCfM1f%m-q2{T6gjBB#>&aig^<_&BE~qm;vqr?|}#I{|}fNzQN{{t8Bm%x#Tn5E`FxgTn9372#b_?y1_2bq5CC(Jb_Q2+n{ M07*qoM6N<$f(}3-_y7O^ diff --git a/app/assets/images/gitorious-logo-black.png b/app/assets/images/gitorious-logo-black.png new file mode 100644 index 0000000000000000000000000000000000000000..78f17a9af79dc2395acd9bcc2a391589dd6b6ed8 GIT binary patch literal 809 zcmV+^1J?YBP)G*X{kliLPwpCH_Ux^-m~D{fnn~wbKd#g`|kapbKi6DEOLf+ zN&$y=oMu=FqBx%JuhX%pIz@#hg8*wUQgI3eX`+i+7xFy6Z;2lf|+O3eT=?{Zlr#Q=P_NZ+T60H7bTbD9FnDV;VH zA`f-gCtb7}g3r*Sy@opADbZ*TJ;hVP&=#Ra2}uMH0sJEX7DqETm;o1l*~3KvX!H+o zQ0i^HOB?%bV!@DH<~e@foyBO$F^fs>*LGt_2#fNb_bvDo%C3kzo=3CYJPiS$A6~ShAh|z})|$Rug%3K+rg~Bm!|iNR1|hf}VBDE?U{j nDy!$>sq%+$UhqH%QaR#Z&H$a*+H(Ht00000NkvXXu0mjfir-pq literal 0 HcmV?d00001 diff --git a/app/assets/images/gitorious-logo-blue.png b/app/assets/images/gitorious-logo-blue.png new file mode 100644 index 0000000000000000000000000000000000000000..4962cffba3153927ddd8cd5ff295fe4ca142bfa8 GIT binary patch literal 495 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7TyC=Y)RhkE)4%caKYZ?lYt_f1s;*b z3=De8Ak0{?)V>TT$X?><>&kwgNu1AE_|Z?p9}EnPHJ&bxAr-gY&e|w=$biT7sZ8;T z6d|?~LR)eirt)S)HZdA`dg%&j74B4Skjc^Z`dKOmc^xlq#JR8X;C&kdvr$f8mXRJ2aS*_MNDR*_yoTOyvF(6<;Njo*gv=n{#vX#_O67_!?$*J9p?F z<+tp;ShYZ<FVdQ I&MBb@0GSiJ)c^nh literal 0 HcmV?d00001 diff --git a/app/assets/images/icon-link.png b/app/assets/images/icon-link.png index 32ade0fe9a37d114ff86789f34242a845f0c5294..60021d5ac47686cce70570d22bad6670082caf56 100644 GIT binary patch delta 703 zcmV;w0zm!y2i66UBYy&&NklgV}u2A9V`( z|CkM$_qD?LO$zV1pndso;XM_!cl{936T$T9T?p@ES0>-yuPhd9pU4A3`}i{e*r?YF z`PKBtrU3};$FaZ7dcDx5&q4v#t3Y}P?^R&E0;E@g_J0uYUWN0thtPiUvVO(?(jG$l zDwk1#_7L7X6?hNfy$YmPfb}ZS9un67)DLoI69DEzdIdJprU&}m!K$Br`!aYuq*tKD z_A2OD55XJL6M^&)WK2&4(nA(Z&jiv#hMnJ7_|vaTOd|~F@W-tPY&==qSARq((Cp7A z5Zw10Wq;1^#NA&A2b%mZW)QZuCs)r_ozC2Ru>&$`^FSYtHtF#|>Icl0w;};K=T}#- z-@lW0mLmXrseqvOFIL|@`u_9Wp#XZF5y<_ez;=ZZNKXOe;zl4n1@PFZu*TmH4q!-y z%8}Vm14+^oKm{G?8K461IY0!`6Tk|*=KvnY$vFEF6+kvs+^6#>6+odY;rz<`cM%=X lZH=}!)YLY1PX0mE{Rcx3aB6uz8Rq~1002ovPDHLkV1l{5TIv7* delta 998 zcmcb{`kQ@%ay>+sSM@po%-5E{-7) zhv#0i&kzojIq-3}jHr=|;dT|ajSFR)Lk#LgmNb`Wc(5&WWo3_+SyHlb@!A%)rY07t z=sPV}l30CJFFA>o3acn>5mLHzi7$yWGlwH%D&M1Qy~^_U_uoH_`*iI4?)U92cXoHT zO$pDeOG>H8QL8`lCTmrt-m1r`=dM|^uQHq))mXFqKv_ikw2c=eH;7*PIrS9_LzR*S zL&AHG`gaG+?%ZzD-cWr`(`K>u>%z&xcNkW^uguGR7;Kei zO#Q}i!}w50^@GE&mpOGLg22c-|v02eyYHBY-+#p_Dd2H zQ=f&+4nH~3+*U#MGf;w}U{*C*W%FE?vh=153&-QJ7{RQ*A|EE^CGM#U8NSIz- zS0VQyT%@+bgp={g-{kcw)vlTe{F`P4o#$Ng{r|-)Rc;D5^i}5bBBie~7Afp6`rB!Ggt=t@om*BLTCX)O`YrQ_aX~qY=Xr*N ze%45T=1KcBJ*v4TeB-TuxM+)d!-f3VwbqC1Gv6~L$zNJr`YZXsUS5a2=Qt+GF&FrA zZ`#LtqDsl*x0=Ei`;*+3RSCHwOZT#d96t13T-|-e_m|uCPfosck15INJjWTKUu+-P z_S8S>s%7qQvejX*z2vp&xB0p&%DMOC^gk|Uxn6x;fW_y*#}yXQ-!9g^cdut*Qm)v? z#IyhYtBs$7zt<~DNE{1a)BO0;TOzHM5k~}l)e_ZtQzI8DjPVDCrGhr#bZI@eDXx^rHh#e=2wUggFmyWX5!t$Q`re{YPu zbPm70{VfKK^h>on7F;o|H_!@>v;KZwcGcHA^-H4CYBsE|&oKHOZs`6(!dajoYu5KE zGQec7TH+c}l9E`GYL#4+3Zxi}3=BNstx6`DrEPiAAXljw$&`sS2LCiRr09sfj6-g(p)%8I!@& L)z4*}Q$iB}08gz9 diff --git a/app/assets/images/icon-search.png b/app/assets/images/icon-search.png index 084b89e3a7cee5787bc3fae15596dab09c547cd7..3c1c146541d456a042db5768154a307b9b535e9d 100644 GIT binary patch delta 137 zcmV;40CxY&0^R|TB$1Ijf8w_fWWJaDu%YkOxpw}tV`T2p#MDu0n%=zVXlNT)=%(V> zLMOj8j9sSU*0c3285pJF%!gK%+`CT2wz;8ZV$F%7pPEBo#%??)iiuuox-LAqa_U_% rv!7KCOcgV`S*7RGovr+5&t~!sY?VjFX3{3I00000NkvXXu0mjfq+>_7 delta 244 zcmcb|c$#T~N)}VGlV=DAN9Y?j76t|e&H|6fVg?3oArNM~bhqvgP*A4CHKHUqKdq!Z zu_%?nF(p4KRlzN@D78GlD7#p}IoPyt>ti#ZIx&zs=c3falFa-(g^ePaWC zL&M*v=lqGEb@_F7#(7hl$M#&*^WD=**6v&Ch3bD6pChR8&}crv|h zmdKI;Vst0GmE!SO5S3 diff --git a/app/assets/images/icon_sprite.png b/app/assets/images/icon_sprite.png index 9ad65fc443bdbbc50fa25797bced0680c8135b5c..2e7a5023398e7aa1d2794755af4f90d59b431919 100644 GIT binary patch literal 2636 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 GHSWLDaEBQH diff --git a/app/assets/images/images.png b/app/assets/images/images.png index da91f6b1f4c31422a3890c3ad8a38bb418a1b81c..ad146246caf907144b468a121ef9524ba8ec3c74 100644 GIT binary patch literal 5849 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 literal 6644 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 diff --git a/app/assets/images/logo-black.png b/app/assets/images/logo-black.png deleted file mode 100644 index 4a96572d570108366da2cf5aa8213f69b591a2a3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2797 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 diff --git a/app/assets/images/logo-white.png b/app/assets/images/logo-white.png index bc2ef601a538d69ef99d5bdafa605e63f902e8e4..917bcfcb7e750f8426dd79912ca525dc65183a56 100644 GIT binary patch literal 7699 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 F|35^Y{uBTJ literal 7501 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 XBYOdNmDsD+00000NkvXXu0mjf1LwCf diff --git a/app/assets/images/monokai-scheme-preview.png b/app/assets/images/monokai-scheme-preview.png index 3aeed886a02bcdc0d38467be744887a437fb99c1..fbb339c6a9170de79c5fee8fc2a5abd6a785f4e7 100644 GIT binary patch literal 3711 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 literal 4332 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 IJQjTUf1kY-`Tzg` diff --git a/app/assets/images/move.png b/app/assets/images/move.png index 9d2d55ddf0b460d8effa80bca82340f5a6ca3341..6a0567f8f2534837e7280dd41e4bf4b98725a3bf 100644 GIT binary patch delta 180 zcmV;l089Ub0>uH48Gi%-007x@vVQ;o0E|gQK~#7F)s?XgLm>=A*?^H4^+sZ(#Dz~N zvZ7$UCf~23cqf4Yj~}G~l*TI%&=UbY5zrI0brP`>t6sOu0j|Ff&~<>mo&0S;>4+QG z*_x`}3vh(>LX|4A-VZRM0h?`MkNJ5D>rmgv;pn7|p_7XgIxV@%)=SN&KW7#IK2`9o i^TEPL8y~KCyuSf?G5cR1sCvx+0000eSaefwW^{L9a%BKPWN%_+AW3auXJt}l zVPtu6$z?nM005>*L_t(o!|j*R3BVu>MBTuVI%-DhNEX5NBh*CE%Ry-%Uq#*n&C#T^ zR??bM6m3}O_NsTzgh!5H7akErE*u%f!|=QEW&voj t{Q@BZ^qY@-HxdHA$3Duf?u~e`(M2f_0#|W002ovPDHLkV1hDPWy}Bo diff --git a/app/assets/images/no_avatar.png b/app/assets/images/no_avatar.png index dac3ab1bb890ee2b583ac5eaa060d972b6e35867..8287acbce13e32d0823c8f5fd449099c1c61d6cd 100644 GIT binary patch delta 597 zcmV-b0;>JM1?>coBYy%kNklnYL|XX%)w zah8f%3TK^T)){A=j*(6%%O0ayjAahd3~HB%Q3Yxj$fyM<>-Zk&fU~rWSQ=*;A%h8n zvmj;x(t4NSu$C%np7?7eM) zU>$ef3W`}0 zs1~Ss_PdnL&p@`|@W!&C^z8s7t9dM&&eMl9pQ_=6v1-98xOXk4-zKx!>(@7F;9kYV z<>Qv#Gsd!dm{>`v+u<`AV{ETKWLdps_x!)Sju?*jkW9HfF*K}*$J8xWlC$|E}O6gYWT6|58yH-KB3fC&>R_R*xaz$)eyOv#0 zt%Y?hSWYd8tzl#_s>R0Ej%w}3mRt~%W2>3csA)5{AeoY=1U!~mzD#CwRI|y;(m2*5 jmCRmI%@XzR|G)YJi=(k0Tjf%>00000NkvXXu0mjfTK_cd delta 681 zcmV;a0#^O)1i%H5BYyw^b5ch_0Itp)=>Px#32;bRa{vGf6951U69E94oEQKA00(qQ zO+^RX1{xA73;x$u5dZ)H0%A)?L;(MXkIcUS00KlwL_t(o!`0XAisCR7fZ@6Ss~FgY zu~txYZBtUw&`tmn-9+4pbcX&ahDeSa}u*USHFsiAEywS)Sk zr8`;Qlf!4Mt`-l;4r}R?7j<;TL!c65=|eCLvD7{o=sao`q6*M`86LD? zdw+NjvQARJ7huU#Jk_(P)iivN~m?-`6$+cXY|HrZQ%3X`-lu~ zRm{^KZuPmF+DW)&JXWp{7TY=VZGF%>Y@HPttI3^Kg|SSwaYTERwRKtsWfi%jEzYX5 zF-1$9W!7VBrH~dU)QT{JwA9?T<_c>WG0Xmzvf0s~Eq}A0lI&hO-5Zp38=fMXr8j97 ziHD_Ut%i}?m*lyKzE9%#?c0>!_NpfA^t>f^gpln1iQCqkfBbuCvdf}th&e1RH{@6D zlh@PVt9h;eWobdvKjpn^+TNGxQH3m3eMpzG(UZvRVZBsdOVY%OKipc+?R8RVr!}x- zT*~U(wSQI3QpXSS&SACL+7r!-BB#F9RF15UtdS+svJ_pTBkIu_R(NYQSkJipfo^H< zSBke%+oCIU)V2z|RoGUAw<_Ce7E5G{#|C>eH1PAnhlqzkJ0G&cq>~LP_)g(SrT*EO zf`dADaUtTMZGSsz|7Ta;p)7DA5xQJ#{lAmbLse!%>X7Y(tfDv}Lc*9;t0X8&JV+h3 zWNltQBm_vnbDMNcvf;7Bf>b;gyCS8;vmjCB7$_+mh^QPRB|8U_@zmD8RdaYqD3ERF z%&*#r$qdLvSPyNuFESu`+!3vw2R#E4^#_(dkNYkH#DC+y#MrJZ;~^JeK%Rd!jfWJ% zfIOe>`*=tq49N4=QWy`}aa-i<>HG1JfZJOCSrzwO zJJ)A}D7tD*DTbh!?iD3EL{Y0Hr9F7y?4(DLjsqd&%`b?Y8h#1nu)k@fB+mXeo>BsT zVKv2o_n|78-GX!-JyrrICAGh=5`oBnu&uCX^#Ho zo(V*Z9Bhbe{BJ@{AW`j*J~QN`Up`DA`V&3%xgi@=x!mW5oF>y1A0p1?G^ax%G)3u? zLqxv~H#a0fQSS3YoY8c}h7_ymiVcaj4j#nWI%trJr&?bElI-RyE+p`86Kx>+2hVdu z8h>tc*D8==d|=Ta33oYJ2C|bEwq}rkyUIEckGo_Y2$-H8(0W}$o&S_1=2!d2Lb|;ARyyFKtS#U0s;a80s;bJG=hMDfPjF2 zfPjF2fPjF2fPjF2fPjF2fOxDxKtMo1Kpa3oKtMo1o>YwrKWlr&Sr7mK002ovPDHLk FV1m3;qptt} literal 4884 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 diff --git a/app/assets/images/slider_handles.png b/app/assets/images/slider_handles.png index a6d477033fa5436fbad5409adcb549ac8d8f01e0..884378ec96a20c1cb2a419e1dc1aa68ab9d85c15 100644 GIT binary patch delta 1359 zcmV-V1+e;>AmIv-BYy=bNkl&c3y}rI<`ge0*CJL* z!&_jXq_d3;P2^&T;>IPKQYmnBf-;rc5o}y61Yv=M*8u{DOCd)tf)bYVHZK*mJ$;|E z=P=>d4>rp6~Ddy}$Q;fA18vR?u?W3fO`fz<&Waf!Sgmq~BKZkgr71 zA<5H3;AP+imVxEKpRM>_5_gb%tMSBbfV~uXgS8+6d<~9)JhtLTNZecU?WJBRE`b+- zJMaTLfgZGjKLME$zg^0vE8s1V0IugY^wZwri&S#8n+(7GL6-)e(z(7TbhhU)TlTr11QYIwS2r)4<})DVbIm z)@5-!un_D4{YPp$D6^`GN;~gSNypuB;k=_&jdbMvH5NC@2c8eKEba(=Wv*!-UNh&) zb^5mL*I|zS*iJs{L+K^2C2ZLr5kFbi&2{F{>%cp#%i_*p703s)JFA?s>-+Jcei`QN zpipx24Syt$m77iW&2=Fo;@Ne56q|k4EUpLstjpqaz=xm+P-I3iWkLK|JpF=c!NzD> z91>@;uPqN65kCR(s3Rq2@j}^LFOD8Ll8Pz)d?#gI?jB~v_9T<=($FtV_PuS1BjV|` z*C^^}iDKl6Ip338Ny(-4bhx&281`4i9%Ny*|9=`sonp>Mua=vs6-^XYM?8n<2LBb3ez;53xj8 z7&wI~9f=>b^fj zjkkZN+M7@5Z0}>X?3?;I&Ux<#hkB0pfT_TCrjxUCY*yJ%l`Xw=>E`1xuNE9HYk%yb zotY(7sC5K3YTv*b*ngU!i_ESuP?>RJR}0R|s%fUp32EC=Z>KhTTQEEP;K_^nx-JW@ z763OduH$;qQMBp6B`Tx%!DOx$sJqMcqN8Y9;5ON-MMQehU#OoeI*Mio+_$FYJ)F$d zVoOTyebmom9o3P460d8R%+-SH)PF}VbMCt_EO|0l3!e>Jlho&2R*TIv&5{jqx*L{U zEdrwV)uYy1eYMCs2X3zOmu^}gf9SzPt`?j(XRb#e>Ri>=x$IEj1m2kSiffR6SbXDH zR|}3K?<3@UDt4&a*&?s^I(T=1-@23!!s1#3V^bdS)q*Yi+|O|lo@L45rAU#HxPpZu z;ty7{6@Q_`9nHd)p67NlDi`1e+{L;`znw+rxwO%LqUk^@Vp{P<6nXze`v;0qoMi-I RvvL3c002ovPDHLkV1i6$w%z~$ literal 4122 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 Y50zi;R|_Mig#Z8m07*qoM6N<$g8P}p^#A|> diff --git a/app/assets/images/solarized-dark-scheme-preview.png b/app/assets/images/solarized-dark-scheme-preview.png index ae092ab52139e10183efd5c175768a8e355f6678..7ed7336896b1daa3d06b3f304931bc2006a3b145 100644 GIT binary patch literal 3195 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 literal 9902 zcmaKS2Ut^0w=VjLG!c~m0xHr8U3y1>&|3(-_uf09iBcpeAf3=d@4ba4T}r^vq@#2Y z0!Wv3ga3c-J@@?gxlf)XJ3BLLX04fd*E{d*NOe^?VnQlH92^{C1$h|_9Gp7>*y{=W zyV%cE#*}XC*BuWHIVqfqk!M@j9|W%QdLB49M5MRxJ2>$4r`Vf#o(d`;yoG;Ah>0JU zxZM@S-nwtDBqxJ&bNk9}D~!j%Vd7Mfd86q&yBlZ(A)T2O2zd*8Nk~udNSYz_(HlCM z58=-rn3{G)WG`2!D-XQCGcE)(e8L_*YMTk|U2CrFJt}OUeS5I}@}ZRwx2y zsA=Nbz~$T1AAvVt=SoGFnsqiG{@8cZF~V!wsh?WN@LDr=urdlMD za~BAE{Sx#T|DFaQE0!x2Jx6AzA}XUP5qLd~xxb+$&Kqc>Lmq@Ba0~ zkx<xeSLH(DxsPiT>E>k8iyU1hnX_%uH}W6ZIOtd6*wSW`!J34%8zqgB6`QkW7^i zBzld)uI>jA)I{!O!BlJMHSe`gNon+RLc26I0`_@ImfeDIOy7gt%K(E(YoAH$@69j{ zxIJ{bJml<~271EQ@hhFckf2csDjI4bt(4k(RMI%+Qw_2vs*BBAS$0jYmH?4T8?Uo zhB3J(PBgJyWio>pwM>It^MRx1n(L%|(;tsehhD~}901B=G^6&8uGQ`@(QKzlo4**^ zeQ1{?xfPgoG*qcpFG_ddt6=ov{tffh(P=Z~tm8%X*x{SKi)@o?BYvSr#Md$7-8aSh zmn&s^-KnBd>E%I2FQGoi<%5}WVddpKB@n5>j#|HymK$K}VN2%a_jRCtnmfy&jt*CQ zDeYvi=mPyBa2|Xg;Fb0KYy(UJ8Jm+HjoqN8XfMI9o4A_wus)OGs}z_zN3xzjIBb_A zZJO77X*^XK(e6vn7fb8=OlomrpAqdOB_~r~0ciSdMaXd0|wI@|NZ zmL~c-3%(Xi^J*x^_E$$JSF-#AhPlszqU+dTvoPBX@6g{*|KX14w@xLk(4)=OIwb}#`V!Khq+ZY!L`UgHKsnb zq3Y-Ks}GPv3)>jqbPSz0|Mg}2fDOXq0Zpr^vF$RXhsaTqBXo}2B*~#E=BGsmd8raZ zj6@AW#zx4LbdNGC3OMV?o;Ue3Q15$rFk4ucgO`Sq~ z-UeNERTjd_sLi5{TK%-zX7p?vU(N|Th$n`uTMJMAj2#0KHm0JYD^}P$dQ8j@lg<>j zI6_Kww8qQKBQH7OD@2FEt13&SI&fKTuCh-%#vc%M(7{8bK>qUmqy3i$3#tnl zW+1lFFL_}ky$n`6RxVAJ%*dcCH8(rmY^2IT zF4C9L@-|Y=+GMy#3cgiK}E;Q;1KbsL4eTOg@ zcJI)*v2ROBa0<;xdDn-0?bCZVRnel#abJDAzS7RQUI8n|8FFfUwzB<`Wm_O6T6_ zI4H84&x7`E2p-O7#4VA*QW_ZDBm6v;yI4v?_edUm?{4)Ci+y{L;H7)E%I}!(BmN}1 zn6H>bVy!WuBf^VJnq0zTCEIMb45NfkCz14apJ$7jBi6 zVy_quUuy}ePc`H^$?9WRNy@k8Pcj@2xfUH-bJp(qbC!rJfaiqwkS>_1YNHMXeYu9^ zXot0=gs#$HizZ8$Hwib_$x~3v;L{sb2wypB6EgT~ldWpUF)jENh<`3n`yw4-0m~neD|y{HBMsUvRT2Et@%>!rA@lra6~V)}$AdJ5>k*bPcxAKn!8V)mX*E=T&oy0h zy6=d(6dq*o&egF5Zd6WOIaRaBQz)M3d%0A$-`K21re8YulH0Ace$k*A&Bn&O;pO

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 diff --git a/app/assets/images/solarized-light-scheme-preview.png b/app/assets/images/solarized-light-scheme-preview.png new file mode 100644 index 0000000000000000000000000000000000000000..c50db75449b974294a612f3215a673c7c2217f80 GIT binary patch literal 3095 zcmV+y4CwQTP)003kN0ssI2j?}E!000Z#Nkl$l~z?1DS2|#7r(JD_@Q#OibyNbJQPJuEz5S+QA1IwY%540 zh@IY8P#jXzAd{5XU3*JHi*45T7rNIME)EHCMSN3hhD5BeIM}g z<<4k6_sq0c{6d_b8=9SInPfKdn-0V7}pjDQg^3IRV`>2~|y`O1I% zrlOd;z4T;E@)rx#V*YwI2b^19j(MF!UraCUymb#wuSUDxF5stYHqYu`zFB?W!&}4I zXir{FHgoF)7(?9&CCxN81MC%}PuE{o^2r6U$1BC3 z8m*~b;`q~~H3Q-mKEJPtYeuiwpQ#xuxhny9J`9}>uT`t-rHs@QR$l=qC<}rmo~u^0 z{z;Kb%Y9z*FbR&--aU0l03YDQG54VX-kw{$)fHNqeL?s=9Jw*oeg82NFkE^6R@Y6D&{7JiQ&*#7 z&%n-0v91sp*LTU+Z(S&CvyIIG8_#$+$lKC+qcv^G9BIw6lC_cxO3+4rj<=<1MqMG_ z;ta2?XqP4%JQ!M&Sm=KPf?qGd2k&Z6bd7*7YuP@p7=CrnK3Y&fnlD3N8V?Kh4A82Q zg19VSAs@09uavAbR-Rc;qo9bYRKRF49@)lvwioc@k?Wz}-1h5r(YyM&!1RSj@GT{G zx66Cim3vGtV;WllT;St1Z!0R+0JJ8h?jUYdRQjlqiV3({C?rKoTmx_BrJkT#ct!e> zR;b^T+ka15kkGG?3kvBdZ;2|&ed9${lFkHxbVJdsdKv{?(F@d@;X(WZ0q;H}sLnlV z2{2z+>AfC;j`u77JMJ1_us@$&`N?v9>iF1HH|^mW#+Ct>iMveF*3{?2;MP@YJQy(C z)-fr?OTf&|>#tmyUZ%%MSCP@-p5xGGQV}CuP|k(plGO`i!6k5IHsC6&o={K&RQPOq z52ja6bua3?;lW-Cn2yjo>ETb^-(VVB>!LYmcj_69-bRfF1BPcMAy}i5B_OAbuTaM_ z<~!t`WW+5gee?pB%35tKDAkUm1^gsFGH|iav8VGB$(_TSMMHPGI=5Tvx4jeOC79}%cX4*N`t!=& z_u#VW-j#>nC#H{&MO|c`wWVBuskI*_;WI1bGPozx*z7fFoVNt!sLq(jdMG?H~VTz#V#mic+C>R_zAG>YfMpCnRg*EPC&rfqHLDRFbl`0RR3s@J7_1G75sK zdcw8WB#&TA0BLqTnSdMXi7Q!eE`2^GkU4XiT4=lDN><0KqB%%!9Ly%6=SRRO1dKw! zrV#>0Az%~&-v0h}h`oG4wflH%W*XKt-4iE_zynisX~{0N6f2}_yy4PO1YF@GZs*9^ z&j&skc6*b8NdeP%!%yG8IK)DaKH#m?HK%Jlnb{$v<;dXhXuQ}?z_31bW9qp3+pZh; zE`%uH4d3lswS}-y&!WcH7d0Ce!cNR+jIa_7FuV^7{nUfmAyuNXl(p3?wpi8%fY||E zh&0ewUib`)n&t=0gzf$z-$uaOvx$(=*(#6H-DBSZ;QdS4FSHARP_Od5?!n4z*NxS5 zs|!zI3YZyEHDcy`EljKPN%6F&NkErG31v%ADO6xR%a+V{~Xcsu^n*nBrR7*5=3r1s|nHkWfOO<^Q(Mm9P;3P|4njRhW#k8%q zPO^3%4g?^AR3C$9po@myxG^ko{Dzo*L%{6ydOd4}&|bqB%ns<%VC^^$!Qc)9ytNef zxty_NgtkHyg4k`6S8$uPL?|fu&_wz-eXLj z+cO5oLnms#$>Ixi46gl9t3C!lbltqwD{a0Vz?IykePi$$&;JhSLSRm`z|XG=%7SQ# zY2}UqoSzx|e1AN6$i19v?=1S<^{HcI79Cm~*$d;-aB`?S4)|XOa8a=~_OU%Nt;zyy zglS=34dGbDVZ`B?0o{^f3rIE6#uFg)OFAxepb#($0izJ`+Z6&vAz%~&Mj_z*d_uH9 z_J`nrj4^h+&t$29pex7!>r{JC7Zcrtr?`Q`I}7Ct+f{q9Hd zmH+%QbO7KTdE&g|;@N>wj#o`N2E$0nDXN{5ljQ=R5bc>hQ8qb>%a6BT%dD)iCLklJ(PR|h!<~4Re*Ho#%hYxuC z1K#`NuHfK5byVtr)fQ*bxh3Do$bxKAz!|A8k*k(e`=m6lDfXCFWd?LjmkNBiUGxL! zqVF#MrhF~6!3XCgRRlWXp+r!CWO*elkFaky)U=#vI znFufn0izIb^F&hw%vLes9v&LJlR1)r5wH;VjXK?5wihrX6`1l{Tsddt4To0dcAI1p zqgnu*TfcL4d?6zTUF`)|V{eYRN zz~I5}B_1kuZ-%{0*_p)1wFSUW=DnVXOkCuT0L)GWrcjOa!)f@;%p^vx2Ls+rUi}1; z3aJLpbE%zn0cNKHvjDR*i4iRuFk9{SV0J1n8!$7I7}1&?Ff$dH37DNpj55(e2MPhB z5HQLFfKdn-g@92A7=?flurN0^u(wn>>|fnFN`MjYRwm+hN7Nj8FxAo95PWp5h+XuN z1)LlosK=4clH8Him09E z!Kl{qgD;O>`$fP>7A|xkU<8apz$gTaLcpkw2aH0%sH1;=FiYmxCdUMTg+}I?r)*SJ|2> zYj)V=va{pC>`lPgGW+?#%pIFq=LeUWJ4@@ys6dY!1?gEUmsKhq9&F%(R~@8_9&owu z9CQY$>CX>l?bysdKbW<%bY58ytqZWHZJ&qlR|-{0vP86M2Y1oM_{hMJ^UhMG#ocC2 ze||7)$7c5Fqs*P94Wdd$vd3jxT;r3XHIb`!_AfdvkIp^W7Y|N0jR%|l{9yKu%`Cvo zouvu&zF?M%Na3P(C7k7=0x@>rawQJy<;;AFnecdIEtmd6?iEF2Ymo_ lT002ovPDHLkV1gDb?(+Zu literal 0 HcmV?d00001 diff --git a/app/assets/images/switch_icon.png b/app/assets/images/switch_icon.png index 6b8bde41bc95c15df2f7ce6d18330cce1181a76c..c6b6c8d9521f64b00990ca5352c8ce269e9a3e4a 100644 GIT binary patch delta 214 zcmV;{04e{i3FiTj8Gi%-008(hmf-*Z0Io?yK~#7FV;CS{>txFSgFu7{Z8kaBIWRyV z1B_wYX|omvtv|T`cgVC6vSF}c03s;7?(*Lq4t@UnU2=AJW^iTzA_%k>^%nONgThOG zEBzS#7(ft+Frj_0Jp&9fAegox*c=R{{1|PvTHke)u$O{BJ5Lci1_%Tbw%*patbk7Q z{%-??wpUS1un}@h^ZMua#%~@RR`@ZXInvq6ZwWRBBQYIZ92hVm3Wqid0J3*sEW}74 QDgXcg07*qoM6N<$g5^(Cz5oCK literal 1197 zcmaJ=PiPcp6d#E#MVew0wb6rqoy1bX-PzgAj_$a#o86hsW|N&|ySw1hlarY*$q;A0 zapp^Qlax|SL;^~BXuufoq=;fY2;zT)wugomii${uf)qR{q(=`$eY45N9=bke-oJV8 z_ulutH}l0pwtdU4Eeyl7=LVD#$gS{peAEUUKfhW9v6E)1v`j{5O>;4mGRQDSIZGSG zC9D}!$5wEhVcL4kN|jdCyr>f^sQEF$y5&GN!^D$yN7KhKMZoJ2(CQ!UC0)U07hU(T)+Y{>&+rSk;_uU+e8X4}OY7!gyh}tljpb`t6 zt{uU0HehXU{hHvyxVq+Wd@$r6sTruM?+>-CjcAXS@VkEh7uc&zIhZS9k4(5a3~r>` zFXf1`i#1Bz3L!_Ew^$q_lz3yrL2_9@U#gmJ+P-67BSTfiob6G~)^SdeSilIHrXe0o zDRL&0iS~wNh37NjbUMPPQ_-j(A3PxNe5{$P5PibJHf`n_?{bBfx&AU(4pdgKYffUL z-z64m99lG6_Y!G|x1MXX?xnY7E(gtU{%HR*>ZS|!$5%HN7mSV7$2RP{3yZzkHot(M z;@e`rk^#B2v{Wb*;_-MQkw_+!pbrfVrBW%-Wm)d)>yso2IOw2&6F7VY4@80ve2oBM zjlcvy=!Y5vCdk3TK~WUJsVGV|n}rhje12wp_eJQShn5OuhS}7yZTpVTx^{_)w2~bv zS4PbG{JHa&e!O=5;o~Py|9bh)>(lIuC5VT)rz%~Gi+^N4`7ZRa^3|i6)q9;c-+Yjp zOU-TnW@Y(C*9y9OoMXN4ox%bMw$xpYb7argGmAqPb@a(6~UoJms zKYQxgMC8vMU(ccwkG{Y5_sq|~ziMYr&fZ_$^V`247I(`(EJHyimnkS$(?^cK1rl3* ArvLx| diff --git a/app/assets/images/trans_bg.gif b/app/assets/images/trans_bg.gif index 5f6ed04a43c97debddc5c6be5697ba74d0504adf..1a1c9c15ec71a58db869578399068cf313c51599 100644 GIT binary patch delta 26 hcmXpqoFF6Wz$DNjyRy1gTBgNsliB;r9nDM()&Obx2($nI delta 27 jcmXpsnjj;_z$DlrJF{Ji=T5TFDX+O#BXiG-FjxZsY}g3f diff --git a/app/assets/images/white-scheme-preview.png b/app/assets/images/white-scheme-preview.png index d1866e0015803d0180553d350dffa322b5d9f958..fc4c40b9227cced4692d9c3f0b3e09d62894fe3e 100644 GIT binary patch literal 3751 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 literal 10022 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 WJ?Y!kfO-Dyo(kv{uu}eQ@c#k0;6(WV diff --git a/app/assets/javascripts/activities.js.coffee b/app/assets/javascripts/activities.js.coffee index fdefbfb92b..777c62dc1b 100644 --- a/app/assets/javascripts/activities.js.coffee +++ b/app/assets/javascripts/activities.js.coffee @@ -1,4 +1,4 @@ -class Activities +class @Activities constructor: -> Pager.init 20, true $(".event_filter_link").bind "click", (event) => @@ -12,7 +12,7 @@ class Activities toggleFilter: (sender) -> - sender.parent().toggleClass "inactive" + sender.parent().toggleClass "active" event_filters = $.cookie("event_filter") filter = sender.attr("id").split("_")[0] if event_filters @@ -27,5 +27,3 @@ class Activities event_filters.splice index, 1 $.cookie "event_filter", event_filters.join(","), { path: '/' } - -@Activities = Activities diff --git a/app/assets/javascripts/admin.js.coffee b/app/assets/javascripts/admin.js.coffee index 6634bb6cc3..bcb2e6df7c 100644 --- a/app/assets/javascripts/admin.js.coffee +++ b/app/assets/javascripts/admin.js.coffee @@ -1,4 +1,4 @@ -class Admin +class @Admin constructor: -> $('input#user_force_random_password').on 'change', (elem) -> elems = $('#user_password, #user_password_confirmation') @@ -46,10 +46,8 @@ class Admin modal.hide() $('.change-owner-link').show() - $('li.users_project').bind 'ajax:success', -> + $('li.project_member').bind 'ajax:success', -> Turbolinks.visit(location.href) - $('li.users_group').bind 'ajax:success', -> + $('li.group_member').bind 'ajax:success', -> Turbolinks.visit(location.href) - -@Admin = Admin diff --git a/app/assets/javascripts/api.js.coffee b/app/assets/javascripts/api.js.coffee index fafa5cdfaa..9e5d594c86 100644 --- a/app/assets/javascripts/api.js.coffee +++ b/app/assets/javascripts/api.js.coffee @@ -1,44 +1,24 @@ @Api = - users_path: "/api/:version/users.json" - user_path: "/api/:version/users/:id.json" - notes_path: "/api/:version/projects/:id/notes.json" + groups_path: "/api/:version/groups.json" + group_path: "/api/:version/groups/:id.json" namespaces_path: "/api/:version/namespaces.json" - project_users_path: "/api/:version/projects/:id/users.json" - # Get 20 (depends on api) recent notes - # and sort the ascending from oldest to newest - notes: (project_id, callback) -> - url = Api.buildUrl(Api.notes_path) - url = url.replace(':id', project_id) - - $.ajax( - url: url, - data: - private_token: gon.api_token - gfm: true - recent: true - dataType: "json" - ).done (notes) -> - notes.sort (a, b) -> - return a.id - b.id - callback(notes) - - user: (user_id, callback) -> - url = Api.buildUrl(Api.user_path) - url = url.replace(':id', user_id) + group: (group_id, callback) -> + url = Api.buildUrl(Api.group_path) + url = url.replace(':id', group_id) $.ajax( url: url data: private_token: gon.api_token dataType: "json" - ).done (user) -> - callback(user) + ).done (group) -> + callback(group) - # Return users list. Filtered by query - # Only active users retrieved - users: (query, callback) -> - url = Api.buildUrl(Api.users_path) + # Return groups list. Filtered by query + # Only active groups retrieved + groups: (query, skip_ldap, callback) -> + url = Api.buildUrl(Api.groups_path) $.ajax( url: url @@ -46,27 +26,9 @@ private_token: gon.api_token search: query per_page: 20 - active: true dataType: "json" - ).done (users) -> - callback(users) - - # Return project users list. Filtered by query - # Only active users retrieved - projectUsers: (project_id, query, callback) -> - url = Api.buildUrl(Api.project_users_path) - url = url.replace(':id', project_id) - - $.ajax( - url: url - data: - private_token: gon.api_token - search: query - per_page: 20 - active: true - dataType: "json" - ).done (users) -> - callback(users) + ).done (groups) -> + callback(groups) # Return namespaces list. Filtered by query namespaces: (query, callback) -> diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee index 1960479321..fda142293b 100644 --- a/app/assets/javascripts/application.js.coffee +++ b/app/assets/javascripts/application.js.coffee @@ -15,22 +15,32 @@ #= require jquery.atwho #= require jquery.scrollTo #= require jquery.blockUI -#= require turbolinks #= require jquery.turbolinks +#= require jquery.sticky-kit.min +#= require turbolinks +#= require autosave #= require bootstrap #= require select2 #= require raphael #= require g.raphael-min #= require g.bar-min +#= require chart-lib.min #= require branch-graph -#= require highlight.pack #= require ace/ace +#= require ace/ext-searchbox #= require d3 #= require underscore #= require nprogress #= require nprogress-turbolinks #= require dropzone -#= require semantic-ui/sidebar +#= require mousetrap +#= require mousetrap/pause +#= require shortcuts +#= require shortcuts_navigation +#= require shortcuts_dashboard_navigation +#= require shortcuts_issueable +#= require shortcuts_network +#= require cal-heatmap #= require_tree . window.slugify = (text) -> @@ -41,12 +51,6 @@ window.ajaxGet = (url) -> window.showAndHide = (selector) -> -window.errorMessage = (message) -> - ehtml = $("

") - ehtml.addClass("error_message") - ehtml.html(message) - ehtml - window.split = (val) -> return val.split( /,\s*/ ) @@ -54,7 +58,7 @@ window.extractLast = (term) -> return split( term ).pop() window.rstrip = (val) -> - return val.replace(/\s+$/, '') + return if val then val.replace(/\s+$/, '') else val # Disable button if text field is empty window.disableButtonIfEmptyField = (field_selector, button_selector) -> @@ -72,24 +76,18 @@ window.disableButtonIfEmptyField = (field_selector, button_selector) -> # Disable button if any input field with given selector is empty window.disableButtonIfAnyEmptyField = (form, form_selector, button_selector) -> closest_submit = form.find(button_selector) - empty = false - form.find('input').filter(form_selector).each -> - empty = true if rstrip($(this).val()) is "" - - if empty - closest_submit.disable() - else - closest_submit.enable() - - form.keyup -> - empty = false + updateButtons = -> + filled = true form.find('input').filter(form_selector).each -> - empty = true if rstrip($(this).val()) is "" + filled = rstrip($(this).val()) != "" || !$(this).attr('required') - if empty - closest_submit.disable() - else + if filled closest_submit.enable() + else + closest_submit.disable() + + updateButtons() + form.keyup(updateButtons) window.sanitize = (str) -> return str.replace(/<(?:.|\n)*?>/gm, '') @@ -105,8 +103,17 @@ window.unbindEvents = -> $(document).unbind('scroll') $(document).off('scroll') +window.shiftWindow = -> + scrollBy 0, -50 + document.addEventListener("page:fetch", unbindEvents) +# Scroll the window to avoid the topnav bar +# https://github.com/twitter/bootstrap/issues/1768 +if location.hash + setTimeout shiftWindow, 1 +window.addEventListener "hashchange", shiftWindow + $ -> # Click a .one_click_select field, select the contents $(".one_click_select").on 'click', -> $(@).select() @@ -117,6 +124,13 @@ $ -> # Initialize select2 selects $('select.select2').select2(width: 'resolve', dropdownAutoWidth: true) + # Close select2 on escape + $('.js-select2').bind 'select2-close', -> + setTimeout ( -> + $('.select2-container-active').removeClass('select2-container-active') + $(':focus').blur() + ), 1 + # Initialize tooltips $('.has_tooltip').tooltip() @@ -134,7 +148,6 @@ $ -> if (flash = $(".flash-container")).length > 0 flash.click -> $(@).fadeOut() flash.show() - setTimeout (-> flash.fadeOut()), 5000 # Disable form buttons while a form is submitting $('body').on 'ajax:complete, ajax:beforeSend, submit', 'form', (e) -> @@ -149,20 +162,6 @@ $ -> # Show/Hide the profile menu when hovering the account box $('.account-box').hover -> $(@).toggleClass('hover') - # Focus search field by pressing 's' key - $(document).keypress (e) -> - # Don't do anything if typing in an input - return if $(e.target).is(":input") - - switch e.which - when 115 - $("#search").focus() - e.preventDefault() - when 63 - new Shortcuts() - e.preventDefault() - - # Commit show suppressed diff $(".diff-content").on "click", ".supp_diff_link", -> $(@).next('table').show() @@ -170,12 +169,19 @@ $ -> # Show/hide comments on diff $("body").on "click", ".js-toggle-diff-comments", (e) -> - $(@).find('i'). - toggleClass('icon-chevron-down'). - toggleClass('icon-chevron-up') + $(@).toggleClass('active') $(@).closest(".diff-file").find(".notes_holder").toggle() e.preventDefault() + $(document).on "click", '.js-confirm-danger', (e) -> + e.preventDefault() + btn = $(e.target) + text = btn.data("confirm-danger-message") + form = btn.closest("form") + new ConfirmDangerModal(form, text) + + new Aside() + (($) -> # Disable an element and add the 'disabled' Bootstrap class $.fn.extend disable: -> diff --git a/app/assets/javascripts/aside.js.coffee b/app/assets/javascripts/aside.js.coffee new file mode 100644 index 0000000000..8547310194 --- /dev/null +++ b/app/assets/javascripts/aside.js.coffee @@ -0,0 +1,17 @@ +class @Aside + constructor: -> + $(document).off "click", "a.show-aside" + $(document).on "click", 'a.show-aside', (e) -> + e.preventDefault() + btn = $(e.currentTarget) + icon = btn.find('i') + console.log('1') + + if icon.hasClass('fa-angle-left') + btn.parent().find('section').hide() + btn.parent().find('aside').fadeIn() + icon.removeClass('fa-angle-left').addClass('fa-angle-right') + else + btn.parent().find('aside').hide() + btn.parent().find('section').fadeIn() + icon.removeClass('fa-angle-right').addClass('fa-angle-left') diff --git a/app/assets/javascripts/autosave.js.coffee b/app/assets/javascripts/autosave.js.coffee new file mode 100644 index 0000000000..5d3fe81da7 --- /dev/null +++ b/app/assets/javascripts/autosave.js.coffee @@ -0,0 +1,39 @@ +class @Autosave + constructor: (field, key) -> + @field = field + + key = key.join("/") if key.join? + @key = "autosave/#{key}" + + @field.data "autosave", this + + @restore() + + @field.on "input", => @save() + + restore: -> + return unless window.localStorage? + + try + text = window.localStorage.getItem @key + catch + return + + @field.val text if text?.length > 0 + @field.trigger "input" + + save: -> + return unless window.localStorage? + + text = @field.val() + if text?.length > 0 + try + window.localStorage.setItem @key, text + else + @reset() + + reset: -> + return unless window.localStorage? + + try + window.localStorage.removeItem @key diff --git a/app/assets/javascripts/behaviors/taskable.js.coffee b/app/assets/javascripts/behaviors/taskable.js.coffee new file mode 100644 index 0000000000..ddce71c188 --- /dev/null +++ b/app/assets/javascripts/behaviors/taskable.js.coffee @@ -0,0 +1,21 @@ +window.updateTaskState = (taskableType) -> + objType = taskableType.data + isChecked = $(this).prop("checked") + if $(this).is(":checked") + stateEvent = "task_check" + else + stateEvent = "task_uncheck" + + taskableUrl = $("form.edit-" + objType).first().attr("action") + taskableNum = taskableUrl.match(/\d+$/) + taskNum = 0 + $("li.task-list-item input:checkbox").each( (index, e) => + if e == this + taskNum = index + 1 + ) + + $.ajax + type: "PATCH" + url: taskableUrl + data: objType + "[state_event]=" + stateEvent + + "&" + objType + "[task_num]=" + taskNum diff --git a/app/assets/javascripts/behaviors/toggler_behavior.coffee b/app/assets/javascripts/behaviors/toggler_behavior.coffee index 1b2ed9efc2..177b691827 100644 --- a/app/assets/javascripts/behaviors/toggler_behavior.coffee +++ b/app/assets/javascripts/behaviors/toggler_behavior.coffee @@ -8,7 +8,7 @@ $ -> # $("body").on "click", ".js-toggle-button", (e) -> $(@).find('i'). - toggleClass('icon-chevron-down'). - toggleClass('icon-chevron-up') + toggleClass('fa fa-chevron-down'). + toggleClass('fa fa-chevron-up') $(@).closest(".js-toggle-container").find(".js-toggle-content").toggle() e.preventDefault() diff --git a/app/assets/javascripts/blob.js.coffee b/app/assets/javascripts/blob/blob.js.coffee similarity index 96% rename from app/assets/javascripts/blob.js.coffee rename to app/assets/javascripts/blob/blob.js.coffee index 9db919e5a6..37a175fdbc 100644 --- a/app/assets/javascripts/blob.js.coffee +++ b/app/assets/javascripts/blob/blob.js.coffee @@ -1,4 +1,4 @@ -class BlobView +class @BlobView constructor: -> # handle multi-line select handleMultiSelect = (e) -> @@ -26,7 +26,7 @@ class BlobView unless isNaN first_line $("#tree-content-holder .highlight .line").removeClass("hll") $("#LC#{line}").addClass("hll") for line in [first_line..last_line] - $.scrollTo("#L#{first_line}") unless e? + $.scrollTo("#L#{first_line}", offset: -50) unless e? # parse selected lines from hash # always return first and last line (initialized to NaN) @@ -71,6 +71,3 @@ class BlobView # Highlight the correct lines when the hash part of the URL changes $(window).on("hashchange", highlightBlobLines) - - -@BlobView = BlobView diff --git a/app/assets/javascripts/blob/edit_blob.js.coffee b/app/assets/javascripts/blob/edit_blob.js.coffee new file mode 100644 index 0000000000..2e91a06daa --- /dev/null +++ b/app/assets/javascripts/blob/edit_blob.js.coffee @@ -0,0 +1,44 @@ +class @EditBlob + constructor: (assets_path, mode)-> + ace.config.set "modePath", assets_path + '/ace' + ace.config.loadModule "ace/ext/searchbox" + if mode + ace_mode = mode + editor = ace.edit("editor") + editor.focus() + @editor = editor + + if ace_mode + editor.getSession().setMode "ace/mode/" + ace_mode + + disableButtonIfEmptyField "#commit_message", ".js-commit-button" + $(".js-commit-button").click -> + $("#file-content").val editor.getValue() + $(".file-editor form").submit() + return false + + editModePanes = $(".js-edit-mode-pane") + editModeLinks = $(".js-edit-mode a") + editModeLinks.click (event) -> + event.preventDefault() + currentLink = $(this) + paneId = currentLink.attr("href") + currentPane = editModePanes.filter(paneId) + editModeLinks.parent().removeClass "active hover" + currentLink.parent().addClass "active hover" + editModePanes.hide() + if paneId is "#preview" + currentPane.fadeIn 200 + $.post currentLink.data("preview-url"), + content: editor.getValue() + , (response) -> + currentPane.empty().append response + return + + else + currentPane.fadeIn 200 + editor.focus() + return + + editor: -> + return @editor diff --git a/app/assets/javascripts/blob/new_blob.js.coffee b/app/assets/javascripts/blob/new_blob.js.coffee new file mode 100644 index 0000000000..ab8f98715e --- /dev/null +++ b/app/assets/javascripts/blob/new_blob.js.coffee @@ -0,0 +1,21 @@ +class @NewBlob + constructor: (assets_path, mode)-> + ace.config.set "modePath", assets_path + '/ace' + ace.config.loadModule "ace/ext/searchbox" + if mode + ace_mode = mode + editor = ace.edit("editor") + editor.focus() + @editor = editor + + if ace_mode + editor.getSession().setMode "ace/mode/" + ace_mode + + disableButtonIfEmptyField "#commit_message", ".js-commit-button" + $(".js-commit-button").click -> + $("#file-content").val editor.getValue() + $(".file-editor form").submit() + return false + + editor: -> + return @editor diff --git a/app/assets/javascripts/branch-graph.js.coffee b/app/assets/javascripts/branch-graph.js.coffee index f6d57bd55b..010a2b0e42 100644 --- a/app/assets/javascripts/branch-graph.js.coffee +++ b/app/assets/javascripts/branch-graph.js.coffee @@ -1,4 +1,4 @@ -class BranchGraph +class @BranchGraph constructor: (@element, @options) -> @preparedCommits = {} @mtime = 0 @@ -90,11 +90,15 @@ class BranchGraph renderPartialGraph: -> start = Math.floor((@element.scrollTop() - @offsetY) / @unitTime) - 10 - start = 0 if start < 0 + if start < 0 + isGraphEdge = true + start = 0 end = start + 40 - end = @commits.length if @commits.length < end + if @commits.length < end + isGraphEdge = true + end = @commits.length - if @prev_start == -1 or Math.abs(@prev_start - start) > 10 + if @prev_start == -1 or Math.abs(@prev_start - start) > 10 or isGraphEdge i = start @prev_start = start @@ -120,23 +124,32 @@ class BranchGraph @top.toFront() bindEvents: -> - drag = {} element = @element $(element).scroll (event) => @renderPartialGraph() - $(window).on - keydown: (event) => - # left - element.scrollLeft element.scrollLeft() - 50 if event.keyCode is 37 - # top - element.scrollTop element.scrollTop() - 50 if event.keyCode is 38 - # right - element.scrollLeft element.scrollLeft() + 50 if event.keyCode is 39 - # bottom - element.scrollTop element.scrollTop() + 50 if event.keyCode is 40 - @renderPartialGraph() + scrollDown: => + @element.scrollTop @element.scrollTop() + 50 + @renderPartialGraph() + + scrollUp: => + @element.scrollTop @element.scrollTop() - 50 + @renderPartialGraph() + + scrollLeft: => + @element.scrollLeft @element.scrollLeft() - 50 + @renderPartialGraph() + + scrollRight: => + @element.scrollLeft @element.scrollLeft() + 50 + @renderPartialGraph() + + scrollBottom: => + @element.scrollTop @element.find('svg').height() + + scrollTop: => + @element.scrollTop 0 appendLabel: (x, y, commit) -> return unless commit.refs @@ -325,5 +338,3 @@ Raphael::textWrap = (t, width) -> b = t.getBBox() h = Math.abs(b.y2) - Math.abs(b.y) + 1 t.attr y: b.y + h - -@BranchGraph = BranchGraph diff --git a/app/assets/javascripts/calendar.js.coffee b/app/assets/javascripts/calendar.js.coffee new file mode 100644 index 0000000000..44d75bd694 --- /dev/null +++ b/app/assets/javascripts/calendar.js.coffee @@ -0,0 +1,38 @@ +class @Calendar + options = + month: "short" + day: "numeric" + year: "numeric" + + 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 + 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/chart.js.coffee b/app/assets/javascripts/chart.js.coffee deleted file mode 100644 index 989f48e5e7..0000000000 --- a/app/assets/javascripts/chart.js.coffee +++ /dev/null @@ -1,21 +0,0 @@ -@Chart = - labels: [] - values: [] - - init: (labels, values, title) -> - r = Raphael('activity-chart') - - fin = -> - @flag = r.popup(@bar.x, @bar.y, @bar.value or "0").insertBefore(this) - - fout = -> - @flag.animate - opacity: 0, 300, -> @remove() - - r.text(160, 10, title).attr font: "13px sans-serif" - r.barchart( - 10, 20, 560, 200, - [values], - {colors:["#456"]} - ).label(labels, true) - .hover(fin, fout) diff --git a/app/assets/javascripts/commit.js.coffee b/app/assets/javascripts/commit.js.coffee index 5f53439ca4..0566e23919 100644 --- a/app/assets/javascripts/commit.js.coffee +++ b/app/assets/javascripts/commit.js.coffee @@ -1,6 +1,4 @@ -class Commit +class @Commit constructor: -> $('.files .diff-file').each -> new CommitFile(this) - -@Commit = Commit diff --git a/app/assets/javascripts/commit/file.js.coffee b/app/assets/javascripts/commit/file.js.coffee index 4db9116a9d..83e793863b 100644 --- a/app/assets/javascripts/commit/file.js.coffee +++ b/app/assets/javascripts/commit/file.js.coffee @@ -1,7 +1,5 @@ -class CommitFile +class @CommitFile constructor: (file) -> if $('.image', file).length new ImageFile(file) - -@CommitFile = CommitFile diff --git a/app/assets/javascripts/commit/image-file.js.coffee b/app/assets/javascripts/commit/image-file.js.coffee index 607b85eb45..9e5f49b1f6 100644 --- a/app/assets/javascripts/commit/image-file.js.coffee +++ b/app/assets/javascripts/commit/image-file.js.coffee @@ -1,4 +1,4 @@ -class ImageFile +class @ImageFile # Width where images must fits in, for 2-up this gets divided by 2 @availWidth = 900 @@ -124,5 +124,3 @@ class ImageFile else img.on 'load', => callback.call(this, domImg.naturalWidth, domImg.naturalHeight) - -@ImageFile = ImageFile diff --git a/app/assets/javascripts/commits.js.coffee b/app/assets/javascripts/commits.js.coffee index 784d7d20bb..c183e78e51 100644 --- a/app/assets/javascripts/commits.js.coffee +++ b/app/assets/javascripts/commits.js.coffee @@ -1,4 +1,4 @@ -class CommitsList +class @CommitsList @data = ref: null limit: 0 @@ -53,5 +53,3 @@ class CommitsList @disable callback: => this.getOld() - -this.CommitsList = CommitsList diff --git a/app/assets/javascripts/confirm_danger_modal.js.coffee b/app/assets/javascripts/confirm_danger_modal.js.coffee new file mode 100644 index 0000000000..bb99edbd09 --- /dev/null +++ b/app/assets/javascripts/confirm_danger_modal.js.coffee @@ -0,0 +1,18 @@ +class @ConfirmDangerModal + constructor: (form, text) -> + @form = form + $('.js-confirm-text').text(text || '') + $('.js-confirm-danger-input').val('') + $('#modal-confirm-danger').modal('show') + project_path = $('.js-confirm-danger-match').text() + submit = $('.js-confirm-danger-submit') + submit.disable() + + $('.js-confirm-danger-input').on 'input', -> + if rstrip($(@).val()) is project_path + submit.enable() + else + submit.disable() + + $('.js-confirm-danger-submit').on 'click', => + @form.submit() diff --git a/app/assets/javascripts/dashboard.js.coffee b/app/assets/javascripts/dashboard.js.coffee index c4a0ccd9c2..00ee503ff1 100644 --- a/app/assets/javascripts/dashboard.js.coffee +++ b/app/assets/javascripts/dashboard.js.coffee @@ -1,33 +1,3 @@ -class Dashboard +class @Dashboard constructor: -> - @initSidebarTab() - - $(".dash-filter").keyup -> - terms = $(this).val() - uiBox = $(this).parents('.panel').first() - if terms == "" || terms == undefined - uiBox.find(".dash-list li").show() - else - uiBox.find(".dash-list li").each (index) -> - name = $(this).find(".filter-title").text() - - if name.toLowerCase().search(terms.toLowerCase()) == -1 - $(this).hide() - else - $(this).show() - - - - initSidebarTab: -> - key = "dashboard_sidebar_filter" - - # store selection in cookie - $('.dash-sidebar-tabs a').on 'click', (e) -> - $.cookie(key, $(e.target).attr('id')) - - # show tab from cookie - sidebar_filter = $.cookie(key) - $("#" + sidebar_filter).tab('show') if sidebar_filter - - -@Dashboard = Dashboard + new ProjectsList() diff --git a/app/assets/javascripts/diff.js.coffee b/app/assets/javascripts/diff.js.coffee index dbe00c487d..069f91c30e 100644 --- a/app/assets/javascripts/diff.js.coffee +++ b/app/assets/javascripts/diff.js.coffee @@ -1,6 +1,7 @@ -class Diff +class @Diff UNFOLD_COUNT = 20 constructor: -> + $(document).off('click', '.js-unfold') $(document).on('click', '.js-unfold', (event) => target = $(event.target) unfoldBottom = target.hasClass('js-unfold-bottom') @@ -41,6 +42,3 @@ class Diff lines = line.children().slice(0, 2) line_numbers = ($(l).attr('data-linenumber') for l in lines) (parseInt(line_number) for line_number in line_numbers) - - -@Diff = Diff diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee index e5e62c87e4..330ebac6f7 100644 --- a/app/assets/javascripts/dispatcher.js.coffee +++ b/app/assets/javascripts/dispatcher.js.coffee @@ -4,7 +4,6 @@ $ -> class Dispatcher constructor: () -> @initSearch() - @initHighlight() @initPageScripts() initPageScripts: -> @@ -15,50 +14,137 @@ class Dispatcher return false path = page.split(':') + shortcut_handler = null switch page when 'projects:issues:index' Issues.init() + shortcut_handler = new ShortcutsNavigation() when 'projects:issues:show' new Issue() + shortcut_handler = new ShortcutsIssueable() + new ZenMode() when 'projects:milestones:show' new Milestone() - when 'projects:issues:new' + when 'projects:milestones:new', 'projects:milestones:edit' + new ZenMode() + when 'projects:compare:show' + new Diff() + when 'projects:issues:new','projects:issues:edit' GitLab.GfmAutoComplete.setup() - when 'projects:merge_requests:new' + shortcut_handler = new ShortcutsNavigation() + new ZenMode() + new DropzoneInput($('.issue-form')) + if page == 'projects:issues:new' + new IssuableForm($('.issue-form')) + when 'projects:merge_requests:new', 'projects:merge_requests:edit' GitLab.GfmAutoComplete.setup() new Diff() + shortcut_handler = new ShortcutsNavigation() + new ZenMode() + new DropzoneInput($('.merge-request-form')) + if page == 'projects:merge_requests:new' + new IssuableForm($('.merge-request-form')) when 'projects:merge_requests:show' new Diff() + shortcut_handler = new ShortcutsIssueable() + new ZenMode() when "projects:merge_requests:diffs" new Diff() + new ZenMode() + when 'projects:merge_requests:index' + shortcut_handler = new ShortcutsNavigation() + MergeRequests.init() when 'dashboard:show' new Dashboard() new Activities() + when 'dashboard:projects:starred' + new Activities() + new ProjectsList() when 'projects:commit:show' new Commit() new Diff() - when 'groups:show', 'projects:show' + new ZenMode() + shortcut_handler = new ShortcutsNavigation() + when 'projects:commits:show' + shortcut_handler = new ShortcutsNavigation() + when 'projects:show' new Activities() - when 'projects:new', 'projects:edit' - new Project() - when 'projects:teams:members:index' - new TeamMembers() - when 'groups:members' + shortcut_handler = new ShortcutsNavigation() + when 'groups:show' + new Activities() + shortcut_handler = new ShortcutsNavigation() + new ProjectsList() + when 'groups:group_members:index' new GroupMembers() + new UsersSelect() + when 'projects:project_members:index' + new ProjectMembers() + new UsersSelect() + when 'groups:new', 'groups:edit', 'admin:groups:edit' + new GroupAvatar() when 'projects:tree:show' new TreeView() + shortcut_handler = new ShortcutsNavigation() when 'projects:blob:show' new BlobView() + shortcut_handler = new ShortcutsNavigation() when 'projects:labels:new', 'projects:labels:edit' new Labels() + when 'projects:network:show' + # Ensure we don't create a particular shortcut handler here. This is + # already created, where the network graph is created. + shortcut_handler = true + when 'projects:forks:new' + new ProjectFork() + when 'users:show' + new User() + new Activities() + when 'admin:users:show' + new ProjectsList() switch path.first() - when 'admin' then new Admin() + when 'admin' + new Admin() + switch path[1] + when 'groups' + new UsersSelect() + when 'projects' + new NamespaceSelect() + when 'dashboard' + shortcut_handler = new ShortcutsDashboardNavigation() + when 'profiles' + new Profile() when 'projects' - new Wikis() if path[1] == 'wikis' + new Project() + new ProjectAvatar() + switch path[1] + when 'compare' + shortcut_handler = new ShortcutsNavigation() + when 'edit' + shortcut_handler = new ShortcutsNavigation() + new ProjectNew() + when 'new' + new ProjectNew() + when 'show' + new ProjectShow() + when 'issues', 'merge_requests' + new UsersSelect() + when 'wikis' + new Wikis() + shortcut_handler = new ShortcutsNavigation() + new ZenMode() + new DropzoneInput($('.wiki-form')) + when 'snippets', 'labels', 'graphs' + shortcut_handler = new ShortcutsNavigation() + when 'project_members', 'deploy_keys', 'hooks', 'services', 'protected_branches' + shortcut_handler = new ShortcutsNavigation() + # If we haven't installed a custom shortcut handler, install the default one + if not shortcut_handler + new Shortcuts() + initSearch: -> opts = $('.search-autocomplete-opts') path = opts.data('autocomplete-path') @@ -66,10 +152,3 @@ class Dispatcher project_ref = opts.data('autocomplete-project-ref') new SearchAutocomplete(path, project_id, project_ref) - - initHighlight: -> - $('.highlight pre code').each (i, e) -> - $(e).html($.map($(e).html().split("\n"), (line, i) -> - "" + line + "" - ).join("\n")) - hljs.highlightBlock(e) diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee new file mode 100644 index 0000000000..fca2a290e2 --- /dev/null +++ b/app/assets/javascripts/dropzone_input.js.coffee @@ -0,0 +1,243 @@ +class @DropzoneInput + constructor: (form) -> + Dropzone.autoDiscover = false + alertClass = "alert alert-danger alert-dismissable div-dropzone-alert" + alertAttr = "class=\"close\" data-dismiss=\"alert\"" + "aria-hidden=\"true\"" + divHover = "

" + divSpinner = "
" + divAlert = "
" + iconPaperclip = "" + iconSpinner = "" + btnAlert = "" + project_uploads_path = window.project_uploads_path or null + max_file_size = gon.max_file_size or 10 + + form_textarea = $(form).find("textarea.markdown-area") + form_textarea.wrap "
" + form_textarea.bind 'paste', (event) => + handlePaste(event) + + form_dropzone = $(form).find('.div-dropzone') + form_dropzone.parent().addClass "div-dropzone-wrapper" + form_dropzone.append divHover + $(".div-dropzone-hover").append iconPaperclip + form_dropzone.append divSpinner + $(".div-dropzone-spinner").append iconSpinner + $(".div-dropzone-spinner").css + "opacity": 0 + "display": "none" + + # Preview button + $(document).off "click", ".js-md-preview-button" + $(document).on "click", ".js-md-preview-button", (e) -> + ### + Shows the Markdown preview. + + Lets the server render GFM into Html and displays it. + ### + e.preventDefault() + form = $(this).closest("form") + # toggle tabs + form.find(".js-md-write-button").parent().removeClass "active" + form.find(".js-md-preview-button").parent().addClass "active" + + # toggle content + form.find(".md-write-holder").hide() + form.find(".md-preview-holder").show() + + preview = form.find(".js-md-preview") + mdText = form.find(".markdown-area").val() + if mdText.trim().length is 0 + preview.text "Nothing to preview." + else + preview.text "Loading..." + $.post($(this).data("url"), + md_text: mdText + ).success (previewData) -> + preview.html previewData + + # Write button + $(document).off "click", ".js-md-write-button" + $(document).on "click", ".js-md-write-button", (e) -> + ### + Shows the Markdown textarea. + ### + e.preventDefault() + form = $(this).closest("form") + # toggle tabs + form.find(".js-md-write-button").parent().addClass "active" + form.find(".js-md-preview-button").parent().removeClass "active" + + # toggle content + form.find(".md-write-holder").show() + form.find(".md-preview-holder").hide() + + dropzone = form_dropzone.dropzone( + url: project_uploads_path + dictDefaultMessage: "" + clickable: true + paramName: "file" + maxFilesize: max_file_size + uploadMultiple: false + headers: + "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content") + + previewContainer: false + + processing: -> + $(".div-dropzone-alert").alert "close" + + dragover: -> + form_textarea.addClass "div-dropzone-focus" + form.find(".div-dropzone-hover").css "opacity", 0.7 + return + + dragleave: -> + form_textarea.removeClass "div-dropzone-focus" + form.find(".div-dropzone-hover").css "opacity", 0 + return + + drop: -> + form_textarea.removeClass "div-dropzone-focus" + form.find(".div-dropzone-hover").css "opacity", 0 + form_textarea.focus() + return + + success: (header, response) -> + child = $(dropzone[0]).children("textarea") + $(child).val $(child).val() + formatLink(response.link) + "\n" + return + + error: (temp, errorMessage) -> + errorAlert = $(form).find('.error-alert') + checkIfMsgExists = errorAlert.children().length + if checkIfMsgExists is 0 + errorAlert.append divAlert + $(".div-dropzone-alert").append btnAlert + errorMessage + return + + sending: -> + form_dropzone.find(".div-dropzone-spinner").css + "opacity": 0.7 + "display": "inherit" + return + + complete: -> + $(".dz-preview").remove() + $(".markdown-area").trigger "input" + $(".div-dropzone-spinner").css + "opacity": 0 + "display": "none" + return + ) + + child = $(dropzone[0]).children("textarea") + + formatLink = (link) -> + text = "[#{link.alt}](#{link.url})" + text = "!#{text}" if link.is_image + text + + handlePaste = (event) -> + pasteEvent = event.originalEvent + if pasteEvent.clipboardData and pasteEvent.clipboardData.items + image = isImage(pasteEvent) + if image + event.preventDefault() + + filename = getFilename(pasteEvent) or "image.png" + text = "{{" + filename + "}}" + pasteText(text) + uploadFile image.getAsFile(), filename + + isImage = (data) -> + i = 0 + while i < data.clipboardData.items.length + item = data.clipboardData.items[i] + if item.type.indexOf("image") isnt -1 + return item + i++ + return false + + pasteText = (text) -> + caretStart = $(child)[0].selectionStart + caretEnd = $(child)[0].selectionEnd + textEnd = $(child).val().length + + beforeSelection = $(child).val().substring 0, caretStart + afterSelection = $(child).val().substring caretEnd, textEnd + $(child).val beforeSelection + text + afterSelection + form_textarea.trigger "input" + + getFilename = (e) -> + if window.clipboardData and window.clipboardData.getData + value = window.clipboardData.getData("Text") + else if e.clipboardData and e.clipboardData.getData + value = e.clipboardData.getData("text/plain") + + value = value.split("\r") + value.first() + + uploadFile = (item, filename) -> + formData = new FormData() + formData.append "file", item, filename + $.ajax + url: project_uploads_path + type: "POST" + data: formData + dataType: "json" + processData: false + contentType: false + headers: + "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content") + + beforeSend: -> + showSpinner() + closeAlertMessage() + + success: (e, textStatus, response) -> + insertToTextArea(filename, formatLink(response.responseJSON.link)) + + error: (response) -> + showError(response.responseJSON.message) + + complete: -> + closeSpinner() + + insertToTextArea = (filename, url) -> + $(child).val (index, val) -> + val.replace("{{" + filename + "}}", url + "\n") + + appendToTextArea = (url) -> + $(child).val (index, val) -> + val + url + "\n" + + showSpinner = (e) -> + form.find(".div-dropzone-spinner").css + "opacity": 0.7 + "display": "inherit" + + closeSpinner = -> + form.find(".div-dropzone-spinner").css + "opacity": 0 + "display": "none" + + showError = (message) -> + errorAlert = $(form).find('.error-alert') + checkIfMsgExists = errorAlert.children().length + if checkIfMsgExists is 0 + errorAlert.append divAlert + $(".div-dropzone-alert").append btnAlert + message + + closeAlertMessage = -> + form.find(".div-dropzone-alert").alert "close" + + form.find(".markdown-selector").click (e) -> + e.preventDefault() + $(@).closest('.gfm-form').find('.div-dropzone').click() + return + + formatLink: (link) -> + text = "[#{link.alt}](#{link.url})" + text = "!#{text}" if link.is_image + text diff --git a/app/assets/javascripts/flash.js.coffee b/app/assets/javascripts/flash.js.coffee index f8b7789884..b39ab0c447 100644 --- a/app/assets/javascripts/flash.js.coffee +++ b/app/assets/javascripts/flash.js.coffee @@ -1,4 +1,4 @@ -class Flash +class @Flash constructor: (message, type)-> flash = $(".flash-container") flash.html("") @@ -10,6 +10,3 @@ class Flash flash.click -> $(@).fadeOut() flash.show() - setTimeout (-> flash.fadeOut()), 5000 - -@Flash = Flash diff --git a/app/assets/javascripts/group_avatar.js.coffee b/app/assets/javascripts/group_avatar.js.coffee new file mode 100644 index 0000000000..0825fd3ce5 --- /dev/null +++ b/app/assets/javascripts/group_avatar.js.coffee @@ -0,0 +1,9 @@ +class @GroupAvatar + constructor: -> + $('.js-choose-group-avatar-button').bind "click", -> + form = $(this).closest("form") + form.find(".js-group-avatar-input").click() + $('.js-group-avatar-input').bind "change", -> + form = $(this).closest("form") + filename = $(this).val().replace(/^.*[\\\/]/, '') + form.find(".js-avatar-filename").text(filename) diff --git a/app/assets/javascripts/groups.js.coffee b/app/assets/javascripts/groups.js.coffee index 49d6605980..cc905e91ea 100644 --- a/app/assets/javascripts/groups.js.coffee +++ b/app/assets/javascripts/groups.js.coffee @@ -1,17 +1,4 @@ -class GroupMembers +class @GroupMembers constructor: -> - $('li.users_group').bind 'ajax:success', -> + $('li.group_member').bind 'ajax:success', -> $(this).fadeOut() - -@GroupMembers = GroupMembers - -$ -> - # avatar - $('.js-choose-group-avatar-button').bind "click", -> - form = $(this).closest("form") - form.find(".js-group-avatar-input").click() - - $('.js-group-avatar-input').bind "change", -> - form = $(this).closest("form") - filename = $(this).val().replace(/^.*[\\\/]/, '') - form.find(".js-avatar-filename").text(filename) diff --git a/app/assets/javascripts/groups_select.js.coffee b/app/assets/javascripts/groups_select.js.coffee new file mode 100644 index 0000000000..1084e2a17d --- /dev/null +++ b/app/assets/javascripts/groups_select.js.coffee @@ -0,0 +1,41 @@ +class @GroupsSelect + constructor: -> + $('.ajax-groups-select').each (i, select) => + skip_ldap = $(select).hasClass('skip_ldap') + + $(select).select2 + placeholder: "Search for a group" + multiple: $(select).hasClass('multiselect') + minimumInputLength: 0 + query: (query) -> + Api.groups query.term, skip_ldap, (groups) -> + data = { results: groups } + query.callback(data) + + initSelection: (element, callback) -> + id = $(element).val() + if id isnt "" + Api.group(id, callback) + + + formatResult: (args...) => + @formatResult(args...) + formatSelection: (args...) => + @formatSelection(args...) + dropdownCssClass: "ajax-groups-dropdown" + escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results + m + + formatResult: (group) -> + if group.avatar_url + avatar = group.avatar_url + else + avatar = gon.default_avatar_url + + "
+
#{group.name}
+
#{group.path}
+
" + + formatSelection: (group) -> + group.name diff --git a/app/assets/javascripts/importer_status.js.coffee b/app/assets/javascripts/importer_status.js.coffee new file mode 100644 index 0000000000..be8d225e73 --- /dev/null +++ b/app/assets/javascripts/importer_status.js.coffee @@ -0,0 +1,35 @@ +class @ImporterStatus + constructor: (@jobs_url, @import_url) -> + this.initStatusPage() + this.setAutoUpdate() + + initStatusPage: -> + $(".js-add-to-import").click (event) => + new_namespace = null + tr = $(event.currentTarget).closest("tr") + id = tr.attr("id").replace("repo_", "") + if tr.find(".import-target input").length > 0 + new_namespace = tr.find(".import-target input").prop("value") + tr.find(".import-target").empty().append(new_namespace + "/" + tr.find(".import-target").data("project_name")) + $.post @import_url, {repo_id: id, new_namespace: new_namespace}, dataType: 'script' + + $(".js-import-all").click (event) => + $(".js-add-to-import").each -> + $(this).click() + + setAutoUpdate: -> + setInterval (=> + $.get @jobs_url, (data) => + $.each data, (i, job) => + job_item = $("#project_" + job.id) + status_field = job_item.find(".job-status") + + if job.import_status == 'finished' + job_item.removeClass("active").addClass("success") + status_field.html(' done') + else if job.import_status == 'started' + status_field.html(" started") + else + status_field.html(job.import_status) + + ), 4000 diff --git a/app/assets/javascripts/issuable_form.js.coffee b/app/assets/javascripts/issuable_form.js.coffee new file mode 100644 index 0000000000..abd58bcf97 --- /dev/null +++ b/app/assets/javascripts/issuable_form.js.coffee @@ -0,0 +1,28 @@ +class @IssuableForm + constructor: (@form) -> + @titleField = @form.find("input[name*='[title]']") + @descriptionField = @form.find("textarea[name*='[description]']") + + return unless @titleField.length && @descriptionField.length + + @initAutosave() + + @form.on "submit", @resetAutosave + @form.on "click", ".btn-cancel", @resetAutosave + + initAutosave: -> + new Autosave @titleField, [ + document.location.pathname, + document.location.search, + "title" + ] + + new Autosave @descriptionField, [ + document.location.pathname, + document.location.search, + "description" + ] + + resetAutosave: => + @titleField.data("autosave").reset() + @descriptionField.data("autosave").reset() diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee index 36935a0a15..4e2e6550eb 100644 --- a/app/assets/javascripts/issue.js.coffee +++ b/app/assets/javascripts/issue.js.coffee @@ -1,9 +1,24 @@ -class Issue +class @Issue constructor: -> $('.edit-issue.inline-update input[type="submit"]').hide() - $(".issue-box .inline-update").on "change", "select", -> + $(".context .inline-update").on "change", "select", -> $(this).submit() - $(".issue-box .inline-update").on "change", "#issue_assignee_id", -> + $(".context .inline-update").on "change", "#issue_assignee_id", -> $(this).submit() -@Issue = Issue + if $("a.btn-close").length + $("li.task-list-item input:checkbox").prop("disabled", false) + + $('.task-list-item input:checkbox').off('change') + $('.task-list-item input:checkbox').change('issue', updateTaskState) + + $('.issue-details').waitForImages -> + $('.issuable-affix').affix offset: + top: -> + @top = ($('.issuable-affix').offset().top - 70) + bottom: -> + @bottom = $('.footer').outerHeight(true) + $('.issuable-affix').on 'affix.bs.affix', -> + $(@).width($(@).outerWidth()) + .on 'affixed-top.bs.affix affixed-bottom.bs.affix', -> + $(@).width('') diff --git a/app/assets/javascripts/issues.js.coffee b/app/assets/javascripts/issues.js.coffee index 54de93a4e0..40bb9e9cb0 100644 --- a/app/assets/javascripts/issues.js.coffee +++ b/app/assets/javascripts/issues.js.coffee @@ -15,7 +15,7 @@ $(this).html totalIssues + 1 else $(this).html totalIssues - 1 - $("body").on "click", ".issues-filters .dropdown-menu a", -> + $("body").on "click", ".issues-other-filters .dropdown-menu a", -> $('.issues-list').block( message: null, overlayCSS: @@ -43,25 +43,31 @@ $(".selected_issue").bind "change", Issues.checkChanged - + # Make sure we trigger ajax request only after user stop typing initSearch: -> - form = $("#issue_search_form") - last_terms = "" + @timer = null $("#issue_search").keyup -> - terms = $(this).val() - unless terms is last_terms - last_terms = terms - if terms.length >= 2 or terms.length is 0 - $.ajax - type: "GET" - url: location.href - data: "issue_search=" + terms - complete: -> - $(".loading").hide() - success: (data) -> - $('.issues-holder').html(data.html) - Issues.reload() - dataType: "json" + clearTimeout(@timer) + @timer = setTimeout(Issues.filterResults, 500) + + filterResults: => + form = $("#issue_search_form") + search = $("#issue_search").val() + $('.issues-holder').css("opacity", '0.5') + issues_url = form.attr('action') + '? '+ form.serialize() + + $.ajax + type: "GET" + url: form.attr('action') + data: form.serialize() + complete: -> + $('.issues-holder').css("opacity", '1.0') + success: (data) -> + $('.issues-holder').html(data.html) + # Change url so if user reload a page - search results are saved + History.replaceState {page: issues_url}, document.title, issues_url + Issues.reload() + dataType: "json" checkChanged: -> checked_issues = $(".selected_issue:checked") @@ -71,9 +77,9 @@ ids.push $(value).attr("data-id") $("#update_issues_ids").val ids - $(".issues-filters").hide() + $(".issues-other-filters").hide() $(".issues_bulk_update").show() else $("#update_issues_ids").val [] $(".issues_bulk_update").hide() - $(".issues-filters").show() + $(".issues-other-filters").show() diff --git a/app/assets/javascripts/labels.js.coffee b/app/assets/javascripts/labels.js.coffee index d306ad64f5..1bc8840f9a 100644 --- a/app/assets/javascripts/labels.js.coffee +++ b/app/assets/javascripts/labels.js.coffee @@ -1,4 +1,4 @@ -class Labels +class @Labels constructor: -> form = $('.label-form') @setupLabelForm(form) @@ -31,5 +31,3 @@ class Labels # Notify the form, that color has changed $('.label-form').trigger('keyup') e.preventDefault() - -@Labels = Labels diff --git a/app/assets/javascripts/markdown_area.js.coffee b/app/assets/javascripts/markdown_area.js.coffee deleted file mode 100644 index bee2785562..0000000000 --- a/app/assets/javascripts/markdown_area.js.coffee +++ /dev/null @@ -1,196 +0,0 @@ -formatLink = (str) -> - "![" + str.alt + "](" + str.url + ")" - -$(document).ready -> - alertClass = "alert alert-danger alert-dismissable div-dropzone-alert" - alertAttr = "class=\"close\" data-dismiss=\"alert\"" + "aria-hidden=\"true\"" - divHover = "
" - divSpinner = "
" - divAlert = "
" - iconPicture = "" - iconSpinner = "" - btnAlert = "" - project_image_path_upload = window.project_image_path_upload or null - - $("textarea.markdown-area").wrap "
" - - $(".div-dropzone").parent().addClass "div-dropzone-wrapper" - - $(".div-dropzone").append divHover - $(".div-dropzone-hover").append iconPicture - $(".div-dropzone").append divSpinner - $(".div-dropzone-spinner").append iconSpinner - $(".div-dropzone-spinner").css - "opacity": 0 - "display": "none" - - dropzone = $(".div-dropzone").dropzone( - url: project_image_path_upload - dictDefaultMessage: "" - clickable: true - paramName: "markdown_img" - maxFilesize: 10 - uploadMultiple: false - acceptedFiles: "image/jpg,image/jpeg,image/gif,image/png" - headers: - "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content") - - previewContainer: false - - processing: -> - $(".div-dropzone-alert").alert "close" - - dragover: -> - $(".div-dropzone > textarea").addClass "div-dropzone-focus" - $(".div-dropzone-hover").css "opacity", 0.7 - return - - dragleave: -> - $(".div-dropzone > textarea").removeClass "div-dropzone-focus" - $(".div-dropzone-hover").css "opacity", 0 - return - - drop: -> - $(".div-dropzone > textarea").removeClass "div-dropzone-focus" - $(".div-dropzone-hover").css "opacity", 0 - $(".div-dropzone > textarea").focus() - return - - success: (header, response) -> - child = $(dropzone[0]).children("textarea") - $(child).val $(child).val() + formatLink(response.link) + "\n" - return - - error: (temp, errorMessage) -> - checkIfMsgExists = $(".error-alert").children().length - if checkIfMsgExists is 0 - $(".error-alert").append divAlert - $(".div-dropzone-alert").append btnAlert + errorMessage - return - - sending: -> - $(".div-dropzone-spinner").css - "opacity": 0.7 - "display": "inherit" - return - - complete: -> - $(".dz-preview").remove() - $(".markdown-area").trigger "input" - $(".div-dropzone-spinner").css - "opacity": 0 - "display": "none" - return - ) - - child = $(dropzone[0]).children("textarea") - - formatLink = (str) -> - "![" + str.alt + "](" + str.url + ")" - - handlePaste = (e) -> - e.preventDefault() - my_event = e.originalEvent - - if my_event.clipboardData and my_event.clipboardData.items - processItem(my_event) - - processItem = (e) -> - image = isImage(e) - if image - filename = getFilename(e) or "image.png" - text = "{{" + filename + "}}" - pasteText(text) - uploadFile image.getAsFile(), filename - - else - text = e.clipboardData.getData("text/plain") - pasteText(text) - - isImage = (data) -> - i = 0 - while i < data.clipboardData.items.length - item = data.clipboardData.items[i] - if item.type.indexOf("image") isnt -1 - return item - i++ - return false - - pasteText = (text) -> - caretStart = $(child)[0].selectionStart - caretEnd = $(child)[0].selectionEnd - textEnd = $(child).val().length - - beforeSelection = $(child).val().substring 0, caretStart - afterSelection = $(child).val().substring caretEnd, textEnd - $(child).val beforeSelection + text + afterSelection - $(".markdown-area").trigger "input" - - getFilename = (e) -> - if window.clipboardData and window.clipboardData.getData - value = window.clipboardData.getData("Text") - else if e.clipboardData and e.clipboardData.getData - value = e.clipboardData.getData("text/plain") - - value = value.split("\r") - value.first() - - uploadFile = (item, filename) -> - formData = new FormData() - formData.append "markdown_img", item, filename - $.ajax - url: project_image_path_upload - type: "POST" - data: formData - dataType: "json" - processData: false - contentType: false - headers: - "X-CSRF-Token": $("meta[name=\"csrf-token\"]").attr("content") - - beforeSend: -> - showSpinner() - closeAlertMessage() - - success: (e, textStatus, response) -> - insertToTextArea(filename, formatLink(response.responseJSON.link)) - - error: (response) -> - showError(response.responseJSON.message) - - complete: -> - closeSpinner() - - insertToTextArea = (filename, url) -> - $(child).val (index, val) -> - val.replace("{{" + filename + "}}", url + "\n") - - appendToTextArea = (url) -> - $(child).val (index, val) -> - val + url + "\n" - - showSpinner = (e) -> - $(".div-dropzone-spinner").css - "opacity": 0.7 - "display": "inherit" - - closeSpinner = -> - $(".div-dropzone-spinner").css - "opacity": 0 - "display": "none" - - showError = (message) -> - checkIfMsgExists = $(".error-alert").children().length - if checkIfMsgExists is 0 - $(".error-alert").append divAlert - $(".div-dropzone-alert").append btnAlert + message - - closeAlertMessage = -> - $(".div-dropzone-alert").alert "close" - - $(".markdown-selector").click (e) -> - e.preventDefault() - $(@).closest(".div-dropzone-wrapper").find(".div-dropzone").click() - return - - return diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee index 59e53b69e3..fc75f14383 100644 --- a/app/assets/javascripts/merge_request.js.coffee +++ b/app/assets/javascripts/merge_request.js.coffee @@ -1,4 +1,4 @@ -class MergeRequest +class @MergeRequest constructor: (@opts) -> @initContextWidget() this.$el = $('.merge-request') @@ -15,8 +15,21 @@ class MergeRequest modal = $('#modal_merge_info').modal(show: false) - disableButtonIfEmptyField '#merge_commit_message', '.accept_merge_request' + disableButtonIfEmptyField '#commit_message', '.accept_merge_request' + if $("a.btn-close").length + $("li.task-list-item input:checkbox").prop("disabled", false) + + $('.merge-request-details').waitForImages -> + $('.issuable-affix').affix offset: + top: -> + @top = ($('.issuable-affix').offset().top - 70) + bottom: -> + @bottom = $('.footer').outerHeight(true) + $('.issuable-affix').on 'affix.bs.affix', -> + $(@).width($(@).outerWidth()) + .on 'affixed-top.bs.affix affixed-bottom.bs.affix', -> + $(@).width('') # Local jQuery finder $: (selector) -> @@ -24,9 +37,9 @@ class MergeRequest initContextWidget: -> $('.edit-merge_request.inline-update input[type="submit"]').hide() - $(".issue-box .inline-update").on "change", "select", -> + $(".context .inline-update").on "change", "select", -> $(this).submit() - $(".issue-box .inline-update").on "change", "#merge_request_assignee_id", -> + $(".context .inline-update").on "change", "#merge_request_assignee_id", -> $(this).submit() initMergeWidget: -> @@ -40,6 +53,8 @@ class MergeRequest if @opts.ci_enable $.get @opts.url_to_ci_check, (data) => this.showCiState data.status + if data.coverage + this.showCiCoverage data.coverage , 'json' bindEvents: -> @@ -70,6 +85,9 @@ class MergeRequest this.$('.remove_source_branch_in_progress').hide() this.$('.remove_source_branch_widget.failed').show() + $('.task-list-item input:checkbox').off('change') + $('.task-list-item input:checkbox').change('merge_request', updateTaskState) + activateTab: (action) -> this.$('.merge-request-tabs li').removeClass 'active' this.$('.tab-content').hide() @@ -78,6 +96,10 @@ class MergeRequest this.$('.merge-request-tabs .diffs-tab').addClass 'active' this.loadDiff() unless @diffs_loaded this.$('.diffs').show() + $(".diff-header").trigger("sticky_kit:recalc") + when 'commits' + this.$('.merge-request-tabs .commits-tab').addClass 'active' + this.$('.commits').show() else this.$('.merge-request-tabs .notes-tab').addClass 'active' this.$('.notes').show() @@ -88,26 +110,28 @@ class MergeRequest showCiState: (state) -> $('.ci_widget').hide() - allowed_states = ["failed", "running", "pending", "success"] + allowed_states = ["failed", "canceled", "running", "pending", "success"] if state in allowed_states $('.ci_widget.ci-' + state).show() + switch state + when "failed", "canceled" + @setMergeButtonClass('btn-danger') + when "running", "pending" + @setMergeButtonClass('btn-warning') else $('.ci_widget.ci-error').show() + @setMergeButtonClass('btn-danger') - switch state - when "success" - $('.mr-state-widget').addClass("panel-success") - when "failed" - $('.mr-state-widget').addClass("panel-danger") - when "running", "pending" - $('.mr-state-widget').addClass("panel-warning") - - + showCiCoverage: (coverage) -> + cov_html = $('') + cov_html.addClass('ci-coverage') + cov_html.text('Coverage ' + coverage + '%') + $('.ci_widget:visible').append(cov_html) loadDiff: (event) -> $.ajax type: 'GET' - url: this.$('.merge-request-tabs .diffs-tab a').attr('href') + url: this.$('.merge-request-tabs .diffs-tab a').attr('href') + ".json" beforeSend: => this.$('.mr-loading-status .loading').show() complete: => @@ -126,4 +150,17 @@ class MergeRequest this.$('.merge-in-progress').hide() this.$('.automerge_widget.already_cannot_be_merged').show() -this.MergeRequest = MergeRequest + setMergeButtonClass: (css_class) -> + $('.accept_merge_request').removeClass("btn-create").addClass(css_class) + + mergeInProgress: -> + $.ajax + type: 'GET' + url: $('.merge-request').data('url') + success: (data) => + switch data.state + when 'merged' + location.reload() + else + setTimeout(merge_request.mergeInProgress, 3000) + dataType: 'json' diff --git a/app/assets/javascripts/merge_requests.js.coffee b/app/assets/javascripts/merge_requests.js.coffee index 9201c84c5e..83434c1b9b 100644 --- a/app/assets/javascripts/merge_requests.js.coffee +++ b/app/assets/javascripts/merge_requests.js.coffee @@ -1,8 +1,35 @@ # # * Filter merge requests # -@merge_requestsPage = -> - $('#assignee_id').select2() - $('#milestone_id').select2() - $('#milestone_id, #assignee_id').on 'change', -> - $(this).closest('form').submit() +@MergeRequests = + init: -> + MergeRequests.initSearch() + + # Make sure we trigger ajax request only after user stop typing + initSearch: -> + @timer = null + $("#issue_search").keyup -> + clearTimeout(@timer) + @timer = setTimeout(MergeRequests.filterResults, 500) + + filterResults: => + form = $("#issue_search_form") + search = $("#issue_search").val() + $('.merge-requests-holder').css("opacity", '0.5') + issues_url = form.attr('action') + '? '+ form.serialize() + + $.ajax + type: "GET" + url: form.attr('action') + data: form.serialize() + complete: -> + $('.merge-requests-holder').css("opacity", '1.0') + success: (data) -> + $('.merge-requests-holder').html(data.html) + # Change url so if user reload a page - search results are saved + History.replaceState {page: issues_url}, document.title, issues_url + MergeRequests.reload() + dataType: "json" + + reload: -> + $('#filter_issue_search').val($('#issue_search').val()) diff --git a/app/assets/javascripts/milestone.js.coffee b/app/assets/javascripts/milestone.js.coffee index ea01c318d4..d644d50b66 100644 --- a/app/assets/javascripts/milestone.js.coffee +++ b/app/assets/javascripts/milestone.js.coffee @@ -1,4 +1,4 @@ -class Milestone +class @Milestone @updateIssue: (li, issue_url, data) -> $.ajax type: "PUT" @@ -49,6 +49,13 @@ class Milestone data: data success: (data) -> if data.saved == true + if data.assignee_avatar_url + img_tag = $('') + img_tag.attr('src', data.assignee_avatar_url) + img_tag.addClass('avatar s16') + $(li).find('.assignee-icon').html(img_tag) + else + $(li).find('.assignee-icon').html('') $(li).effect 'highlight' else new Flash("Issue update failed", 'alert') @@ -115,5 +122,3 @@ class Milestone Milestone.updateMergeRequest(ui.item, merge_request_url, data) ).disableSelection() - -@Milestone = Milestone diff --git a/app/assets/javascripts/namespace_select.js.coffee b/app/assets/javascripts/namespace_select.js.coffee index 00d135d144..a02c4515cc 100644 --- a/app/assets/javascripts/namespace_select.js.coffee +++ b/app/assets/javascripts/namespace_select.js.coffee @@ -1,24 +1,25 @@ -$ -> - namespaceFormatResult = (namespace) -> - markup = "
" - markup += "" + namespace.kind + "" - markup += "" + namespace.path + "" - markup += "
" - markup +class @NamespaceSelect + constructor: -> + namespaceFormatResult = (namespace) -> + markup = "
" + markup += "" + namespace.kind + "" + markup += "" + namespace.path + "" + markup += "
" + markup - formatSelection = (namespace) -> - namespace.kind + ": " + namespace.path + formatSelection = (namespace) -> + namespace.kind + ": " + namespace.path - $('.ajax-namespace-select').each (i, select) -> - $(select).select2 - placeholder: "Search for namespace" - multiple: $(select).hasClass('multiselect') - minimumInputLength: 0 - query: (query) -> - Api.namespaces query.term, (namespaces) -> - data = { results: namespaces } - query.callback(data) + $('.ajax-namespace-select').each (i, select) -> + $(select).select2 + placeholder: "Search for namespace" + multiple: $(select).hasClass('multiselect') + minimumInputLength: 0 + query: (query) -> + Api.namespaces query.term, (namespaces) -> + data = { results: namespaces } + query.callback(data) - dropdownCssClass: "ajax-namespace-dropdown" - formatResult: namespaceFormatResult - formatSelection: formatSelection + dropdownCssClass: "ajax-namespace-dropdown" + formatResult: namespaceFormatResult + formatSelection: formatSelection diff --git a/app/assets/javascripts/network.js.coffee b/app/assets/javascripts/network.js.coffee index cea5986f45..f4ef07a50a 100644 --- a/app/assets/javascripts/network.js.coffee +++ b/app/assets/javascripts/network.js.coffee @@ -1,11 +1,9 @@ -class Network +class @Network constructor: (opts) -> $("#filter_ref").click -> $(this).closest('form').submit() - branch_graph = new BranchGraph($(".network-graph"), opts) + @branch_graph = new BranchGraph($(".network-graph"), opts) vph = $(window).height() - 250 $('.network-graph').css 'height': (vph + 'px') - -@Network = Network diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee index 83b1bae0ea..6dfe10f000 100644 --- a/app/assets/javascripts/notes.js.coffee +++ b/app/assets/javascripts/notes.js.coffee @@ -1,4 +1,4 @@ -class Notes +class @Notes @interval: null constructor: (notes_url, note_ids, last_fetched_at) -> @@ -6,6 +6,7 @@ class Notes @notes_url = gon.relative_url_root + @notes_url if gon.relative_url_root? @note_ids = note_ids @last_fetched_at = last_fetched_at + @noteable_url = document.URL @initRefresh() @setupMainTargetNoteForm() @cleanBinding() @@ -16,30 +17,28 @@ class Notes $(document).on "ajax:success", ".js-main-target-form", @addNote $(document).on "ajax:success", ".js-discussion-note-form", @addDiscussionNote - # change note in UI after update + # change note in UI after update $(document).on "ajax:success", "form.edit_note", @updateNote # Edit note link $(document).on "click", ".js-note-edit", @showEditForm $(document).on "click", ".note-edit-cancel", @cancelEdit + # Reopen and close actions for Issue/MR combined with note form submit + $(document).on "click", ".js-note-target-reopen", @targetReopen + $(document).on "click", ".js-note-target-close", @targetClose + $(document).on "click", ".js-comment-button", @updateCloseButton + $(document).on "keyup", ".js-note-text", @updateTargetButtons + # remove a note (in general) $(document).on "click", ".js-note-delete", @removeNote # delete note attachment $(document).on "click", ".js-note-attachment-delete", @removeAttachment - # Preview button - $(document).on "click", ".js-note-preview-button", @previewNote - - # Preview button - $(document).on "click", ".js-note-write-button", @writeNote - # reset main target form after submit - $(document).on "ajax:complete", ".js-main-target-form", @resetMainTargetForm - - # attachment button - $(document).on "click", ".js-choose-note-attachment-button", @chooseNoteAttachment + $(document).on "ajax:complete", ".js-main-target-form", @reenableTargetFormSubmitButton + $(document).on "ajax:success", ".js-main-target-form", @resetMainTargetForm # update the file name when an attachment is selected $(document).on "change", ".js-note-attachment-input", @updateFormAttachment @@ -57,8 +56,10 @@ class Notes $(document).on "visibilitychange", @visibilityChange @notes_forms = '.js-main-target-form textarea, .js-discussion-note-form textarea' - $(document).on('keypress', @notes_forms, (e)-> - if e.keyCode == 10 || (e.ctrlKey && e.keyCode == 13) + # Chrome doesn't fire keypress or keyup for Command+Enter, so we need keydown. + $(document).on('keydown', @notes_forms, (e) -> + return if e.originalEvent.repeat + if e.keyCode == 10 || ((e.metaKey || e.ctrlKey) && e.keyCode == 13) $(@).parents('form').submit() ) @@ -70,15 +71,15 @@ class Notes $(document).off "click", ".note-edit-cancel" $(document).off "click", ".js-note-delete" $(document).off "click", ".js-note-attachment-delete" - $(document).off "click", ".js-note-preview-button" - $(document).off "click", ".js-note-write-button" $(document).off "ajax:complete", ".js-main-target-form" - $(document).off "click", ".js-choose-note-attachment-button" + $(document).off "ajax:success", ".js-main-target-form" $(document).off "click", ".js-discussion-reply-button" $(document).off "click", ".js-add-diff-note-button" $(document).off "visibilitychange" - $(document).off "keypress", @notes_forms - + $(document).off "keydown", @notes_forms + $(document).off "keyup", ".js-note-text" + $(document).off "click", ".js-note-target-reopen" + $(document).off "click", ".js-note-target-close" initRefresh: -> clearInterval(Notes.interval) @@ -87,7 +88,8 @@ class Notes , 15000 refresh: -> - @getContent() unless document.hidden + unless document.hidden or (@noteable_url != document.URL) + @getContent() getContent: -> $.ajax @@ -112,10 +114,6 @@ class Notes if @isNewNote(note) @note_ids.push(note.id) $('ul.main-notes-list').append(note.html) - code = "#note_" + note.id + " .highlight pre code" - $(code).each (i, e) -> - hljs.highlightBlock(e) - ### Check if note does not exists on page @@ -155,47 +153,6 @@ class Notes # cleanup after successfully creating a diff/discussion note @removeDiscussionNoteForm(form) - ### - Shows write note textarea. - ### - writeNote: (e) -> - e.preventDefault() - form = $(this).closest("form") - # toggle tabs - form.find(".js-note-write-button").parent().addClass "active" - form.find(".js-note-preview-button").parent().removeClass "active" - - # toggle content - form.find(".note-write-holder").show() - form.find(".note-preview-holder").hide() - - ### - Shows the note preview. - - Lets the server render GFM into Html and displays it. - ### - previewNote: (e) -> - e.preventDefault() - form = $(this).closest("form") - # toggle tabs - form.find(".js-note-write-button").parent().removeClass "active" - form.find(".js-note-preview-button").parent().addClass "active" - - # toggle content - form.find(".note-write-holder").hide() - form.find(".note-preview-holder").show() - - preview = form.find(".js-note-preview") - noteText = form.find(".js-note-text").val() - if noteText.trim().length is 0 - preview.text "Nothing to preview." - else - preview.text "Loading..." - $.post($(this).data("url"), - note: noteText - ).success (previewData) -> - preview.html previewData - ### Called in response the main target form has been successfully submitted. @@ -210,17 +167,15 @@ class Notes form.find(".js-errors").remove() # reset text and preview - form.find(".js-note-write-button").click() + form.find(".js-md-write-button").click() form.find(".js-note-text").val("").trigger "input" - ### - Called when clicking the "Choose File" button. + form.find(".js-note-text").data("autosave").reset() - Opens the file selection dialog. - ### - chooseNoteAttachment: -> - form = $(this).closest("form") - form.find(".js-note-attachment-input").click() + reenableTargetFormSubmitButton: -> + form = $(".js-main-target-form") + + form.find(".js-note-text").trigger "input" ### Shows the main form and does some setup on it. @@ -258,23 +213,34 @@ class Notes setupNoteForm: (form) -> disableButtonIfEmptyField form.find(".js-note-text"), form.find(".js-comment-button") form.removeClass "js-new-note-form" + form.find('.div-dropzone').remove() # setup preview buttons - form.find(".js-note-write-button, .js-note-preview-button").tooltip placement: "left" - previewButton = form.find(".js-note-preview-button") - form.find(".js-note-text").on "input", -> + form.find(".js-md-write-button, .js-md-preview-button").tooltip placement: "left" + previewButton = form.find(".js-md-preview-button") + + textarea = form.find(".js-note-text") + + textarea.on "input", -> if $(this).val().trim() isnt "" previewButton.removeClass("turn-off").addClass "turn-on" else previewButton.removeClass("turn-on").addClass "turn-off" + new Autosave textarea, [ + "Note" + form.find("#note_commit_id").val() + form.find("#note_line_code").val() + form.find("#note_noteable_type").val() + form.find("#note_noteable_id").val() + ] # remove notify commit author checkbox for non-commit notes form.find(".js-notify-commit-author").remove() if form.find("#note_noteable_type").val() isnt "Commit" GitLab.GfmAutoComplete.setup() + new DropzoneInput(form) form.show() - ### Called in response to the new note form being submitted @@ -298,11 +264,10 @@ class Notes Updates the current note field. ### updateNote: (xhr, note, status) => - note_li = $("#note_" + note.id) + note_li = $(".note-row-" + note.id) note_li.replaceWith(note.html) - code = "#note_" + note.id + " .highlight pre code" - $(code).each (i, e) -> - hljs.highlightBlock(e) + note_li.find('.note-edit-form').hide() + note_li.find('.note-body > .note-text').show() ### Called in response to clicking the edit note link @@ -314,12 +279,20 @@ class Notes showEditForm: (e) -> e.preventDefault() note = $(this).closest(".note") - note.find(".note-text").hide() + note.find(".note-body > .note-text").hide() + note.find(".note-header").hide() + base_form = note.find(".note-edit-form") + form = base_form.clone().insertAfter(base_form) + form.addClass('current-note-edit-form') + form.find('.div-dropzone').remove() # Show the attachment delete link note.find(".js-note-attachment-delete").show() + + # Setup markdown form GitLab.GfmAutoComplete.setup() - form = note.find(".note-edit-form") + new DropzoneInput(form) + form.show() textarea = form.find("textarea") textarea.focus() @@ -333,9 +306,9 @@ class Notes cancelEdit: (e) -> e.preventDefault() note = $(this).closest(".note") - note.find(".note-text").show() - note.find(".js-note-attachment-delete").hide() - note.find(".note-edit-form").hide() + note.find(".note-body > .note-text").show() + note.find(".note-header").show() + note.find(".current-note-edit-form").remove() ### Called in response to deleting a note of any kind. @@ -367,7 +340,7 @@ class Notes removeAttachment: -> note = $(this).closest(".note") note.find(".note-attachment").remove() - note.find(".note-text").show() + note.find(".note-body > .note-text").show() note.find(".js-note-attachment-delete").hide() note.find(".note-edit-form").hide() @@ -406,30 +379,6 @@ class Notes form.find(".js-note-text").focus() form.addClass "js-discussion-note-form" - ### - General note form setup. - - deactivates the submit button when text is empty - hides the preview button when text is empty - setup GFM auto complete - show the form - ### - setupNoteForm: (form) => - disableButtonIfEmptyField form.find(".js-note-text"), form.find(".js-comment-button") - form.removeClass "js-new-note-form" - form.removeClass "js-new-note-form" - GitLab.GfmAutoComplete.setup() - - # setup preview buttons - previewButton = form.find(".js-note-preview-button") - form.find(".js-note-text").on "input", -> - if $(this).val().trim() isnt "" - previewButton.removeClass("turn-off").addClass "turn-on" - else - previewButton.removeClass("turn-on").addClass "turn-off" - - form.show() - ### Called when clicking on the "add a comment" button on the side of a diff line. @@ -438,7 +387,7 @@ class Notes ### addDiffNote: (e) => e.preventDefault() - link = e.target + link = e.currentTarget form = $(".js-new-note-form") row = $(link).closest("tr") nextRow = row.next() @@ -465,6 +414,8 @@ class Notes removeDiscussionNoteForm: (form)-> row = form.closest("tr") + form.find(".js-note-text").data("autosave").reset() + # show the reply button (will only work for replies) form.prev(".js-discussion-reply-button").show() if row.is(".js-temp-notes-holder") @@ -482,7 +433,7 @@ class Notes @removeDiscussionNoteForm(form) updateVotes: -> - (new NotesVotes).updateVotes() + true ### Called after an attachment file has been selected. @@ -502,4 +453,29 @@ class Notes visibilityChange: => @refresh() -@Notes = Notes + targetReopen: (e) => + @submitNoteForm($(e.target).parents('form')) + + targetClose: (e) => + @submitNoteForm($(e.target).parents('form')) + + submitNoteForm: (form) => + noteText = form.find(".js-note-text").val() + if noteText.trim().length > 0 + form.submit() + + updateCloseButton: (e) => + textarea = $(e.target) + form = textarea.parents('form') + form.find('.js-note-target-close').text('Close') + + updateTargetButtons: (e) => + textarea = $(e.target) + form = textarea.parents('form') + + if textarea.val().trim().length > 0 + form.find('.js-note-target-reopen').text('Comment & reopen') + form.find('.js-note-target-close').text('Comment & close') + else + form.find('.js-note-target-reopen').text('Reopen') + form.find('.js-note-target-close').text('Close') diff --git a/app/assets/javascripts/notes_votes.js.coffee b/app/assets/javascripts/notes_votes.js.coffee deleted file mode 100644 index b31eb9ac9d..0000000000 --- a/app/assets/javascripts/notes_votes.js.coffee +++ /dev/null @@ -1,22 +0,0 @@ -class NotesVotes - updateVotes: -> - votes = $("#votes .votes") - notes = $("#notes-list .note .vote") - - # only update if there is a vote display - if votes.size() - upvotes = notes.filter(".upvote").size() - downvotes = notes.filter(".downvote").size() - votesCount = upvotes + downvotes - upvotesPercent = (if votesCount then (100.0 / votesCount * upvotes) else 0) - downvotesPercent = (if votesCount then (100.0 - upvotesPercent) else 0) - - # change vote bar lengths - votes.find(".bar-success").css "width", upvotesPercent + "%" - votes.find(".bar-danger").css "width", downvotesPercent + "%" - - # replace vote numbers - votes.find(".upvotes").text votes.find(".upvotes").text().replace(/\d+/, upvotes) - votes.find(".downvotes").text votes.find(".downvotes").text().replace(/\d+/, downvotes) - -@NotesVotes = NotesVotes diff --git a/app/assets/javascripts/profile.js.coffee b/app/assets/javascripts/profile.js.coffee index 0e99921f89..de356fbec7 100644 --- a/app/assets/javascripts/profile.js.coffee +++ b/app/assets/javascripts/profile.js.coffee @@ -1,30 +1,29 @@ -$ -> - $('.edit_user .application-theme input, .edit_user .code-preview-theme input').click -> - # Submit the form - $('.edit_user').submit() +class @Profile + constructor: -> + $('.edit_user .application-theme input, .edit_user .code-preview-theme input').click -> + # Submit the form + $('.edit_user').submit() - new Flash("Appearance settings saved", "notice") + new Flash("Appearance settings saved", "notice") - $('.update-username form').on 'ajax:before', -> - $('.loading-gif').show() - $(this).find('.update-success').hide() - $(this).find('.update-failed').hide() + $('.update-username form').on 'ajax:before', -> + $('.loading-gif').show() + $(this).find('.update-success').hide() + $(this).find('.update-failed').hide() - $('.update-username form').on 'ajax:complete', -> - $(this).find('.btn-save').enableButton() - $(this).find('.loading-gif').hide() + $('.update-username form').on 'ajax:complete', -> + $(this).find('.btn-save').enableButton() + $(this).find('.loading-gif').hide() - $('.update-notifications').on 'ajax:complete', -> - $(this).find('.btn-save').enableButton() + $('.update-notifications').on 'ajax:complete', -> + $(this).find('.btn-save').enableButton() - $('.js-choose-user-avatar-button').bind "click", -> - form = $(this).closest("form") - form.find(".js-user-avatar-input").click() + $('.js-choose-user-avatar-button').bind "click", -> + form = $(this).closest("form") + form.find(".js-user-avatar-input").click() - $('.js-user-avatar-input').bind "change", -> - form = $(this).closest("form") - filename = $(this).val().replace(/^.*[\\\/]/, '') - form.find(".js-avatar-filename").text(filename) - - $('.profile-groups-avatars').tooltip("placement": "top") + $('.js-user-avatar-input').bind "change", -> + form = $(this).closest("form") + filename = $(this).val().replace(/^.*[\\\/]/, '') + form.find(".js-avatar-filename").text(filename) diff --git a/app/assets/javascripts/project.js.coffee b/app/assets/javascripts/project.js.coffee index f7c64b4b81..eb8c1fa142 100644 --- a/app/assets/javascripts/project.js.coffee +++ b/app/assets/javascripts/project.js.coffee @@ -1,53 +1,26 @@ -class Project +class @Project constructor: -> - $('.project-edit-container').on 'ajax:before', => - $('.project-edit-container').hide() - $('.save-project-loader').show() + # Git clone panel switcher + scope = $ '.git-clone-holder' + if scope.length > 0 + $('a, button', scope).click -> + $('a, button', scope).removeClass 'active' + $(@).addClass 'active' + $('#project_clone', scope).val $(@).data 'clone' + $(".clone").text("").append $(@).data 'clone' - @initEvents() + # Ref switcher + $('.project-refs-select').on 'change', -> + $(@).parents('form').submit() + $('.hide-no-ssh-message').on 'click', (e) -> + path = '/' + $.cookie('hide_no_ssh_message', 'false', { path: path }) + $(@).parents('.no-ssh-key-message').remove() + e.preventDefault() - initEvents: -> - disableButtonIfEmptyField '#project_name', '.project-submit' - - $('#project_issues_enabled').change -> - if ($(this).is(':checked') == true) - $('#project_issues_tracker').removeAttr('disabled') - else - $('#project_issues_tracker').attr('disabled', 'disabled') - - $('#project_issues_tracker').change() - - $('#project_issues_tracker').change -> - if ($(this).val() == gon.default_issues_tracker || $(this).is(':disabled')) - $('#project_issues_tracker_id').attr('disabled', 'disabled') - else - $('#project_issues_tracker_id').removeAttr('disabled') - - -@Project = Project - -$ -> - # Git clone panel switcher - scope = $ '.git-clone-holder' - if scope.length > 0 - $('a, button', scope).click -> - $('a, button', scope).removeClass 'active' - $(@).addClass 'active' - $('#project_clone', scope).val $(@).data 'clone' - $(".clone").text("").append $(@).data 'clone' - - # Ref switcher - $('.project-refs-select').on 'change', -> - $(@).parents('form').submit() - - $('.hide-no-ssh-message').on 'click', (e) -> - path = '/' - $.cookie('hide_no_ssh_message', 'false', { path: path }) - $(@).parents('.no-ssh-key-message').hide() - e.preventDefault() - - $('.project-side .star').on 'ajax:success', (e, data, status, xhr) -> - $(@).toggleClass('on').find('.count').html(data.star_count) - .on 'ajax:error', (e, xhr, status, error) -> - new Flash('Star toggle failed. Try again later.', 'alert') + $('.hide-no-password-message').on 'click', (e) -> + path = '/' + $.cookie('hide_no_password_message', 'false', { path: path }) + $(@).parents('.no-password-message').remove() + e.preventDefault() diff --git a/app/assets/javascripts/project_avatar.js.coffee b/app/assets/javascripts/project_avatar.js.coffee new file mode 100644 index 0000000000..8bec6e2ccc --- /dev/null +++ b/app/assets/javascripts/project_avatar.js.coffee @@ -0,0 +1,9 @@ +class @ProjectAvatar + constructor: -> + $('.js-choose-project-avatar-button').bind 'click', -> + form = $(this).closest('form') + form.find('.js-project-avatar-input').click() + $('.js-project-avatar-input').bind 'change', -> + form = $(this).closest('form') + filename = $(this).val().replace(/^.*[\\\/]/, '') + form.find('.js-avatar-filename').text(filename) diff --git a/app/assets/javascripts/project_fork.js.coffee b/app/assets/javascripts/project_fork.js.coffee new file mode 100644 index 0000000000..e15a1c4ef7 --- /dev/null +++ b/app/assets/javascripts/project_fork.js.coffee @@ -0,0 +1,5 @@ +class @ProjectFork + constructor: -> + $('.fork-thumbnail a').on 'click', -> + $('.fork-namespaces').hide() + $('.save-project-loader').show() diff --git a/app/assets/javascripts/project_import.js.coffee b/app/assets/javascripts/project_import.js.coffee index 7cf44da99f..6633564a07 100644 --- a/app/assets/javascripts/project_import.js.coffee +++ b/app/assets/javascripts/project_import.js.coffee @@ -1,7 +1,5 @@ -class ProjectImport +class @ProjectImport constructor: -> setTimeout -> Turbolinks.visit(location.href) , 5000 - -@ProjectImport = ProjectImport diff --git a/app/assets/javascripts/project_members.js.coffee b/app/assets/javascripts/project_members.js.coffee new file mode 100644 index 0000000000..896ba7e53e --- /dev/null +++ b/app/assets/javascripts/project_members.js.coffee @@ -0,0 +1,4 @@ +class @ProjectMembers + constructor: -> + $('li.project_member').bind 'ajax:success', -> + $(this).fadeOut() diff --git a/app/assets/javascripts/project_new.js.coffee b/app/assets/javascripts/project_new.js.coffee new file mode 100644 index 0000000000..836269c44f --- /dev/null +++ b/app/assets/javascripts/project_new.js.coffee @@ -0,0 +1,11 @@ +class @ProjectNew + constructor: -> + $('.project-edit-container').on 'ajax:before', => + $('.project-edit-container').hide() + $('.save-project-loader').show() + + @initEvents() + + + initEvents: -> + disableButtonIfEmptyField '#project_name', '.project-submit' diff --git a/app/assets/javascripts/project_show.js.coffee b/app/assets/javascripts/project_show.js.coffee new file mode 100644 index 0000000000..6828ae471e --- /dev/null +++ b/app/assets/javascripts/project_show.js.coffee @@ -0,0 +1,15 @@ +class @ProjectShow + constructor: -> + $('.project-home-panel .star').on 'ajax:success', (e, data, status, xhr) -> + $(@).toggleClass('on').find('.count').html(data.star_count) + .on 'ajax:error', (e, xhr, status, error) -> + new Flash('Star toggle failed. Try again later.', 'alert') + + $("a[data-toggle='tab']").on "shown.bs.tab", (e) -> + $.cookie "default_view", $(e.target).attr("href"), { expires: 30, path: '/' } + + defaultView = $.cookie("default_view") + if defaultView + $("a[href=" + defaultView + "]").tab "show" + else + $("a[data-toggle='tab']:first").tab "show" diff --git a/app/assets/javascripts/project_users_select.js.coffee b/app/assets/javascripts/project_users_select.js.coffee deleted file mode 100644 index cfbcd5108c..0000000000 --- a/app/assets/javascripts/project_users_select.js.coffee +++ /dev/null @@ -1,59 +0,0 @@ -@projectUsersSelect = - init: -> - $('.ajax-project-users-select').each (i, select) -> - project_id = $(select).data('project-id') || $('body').data('project-id') - - $(select).select2 - placeholder: $(select).data('placeholder') || "Search for a user" - multiple: $(select).hasClass('multiselect') - minimumInputLength: 0 - query: (query) -> - Api.projectUsers project_id, query.term, (users) -> - data = { results: users } - - nullUser = { - name: 'Unassigned', - avatar: null, - username: 'none', - id: '' - } - - data.results.unshift(nullUser) - - query.callback(data) - - initSelection: (element, callback) -> - id = $(element).val() - if id isnt "" - Api.user(id, callback) - - - formatResult: projectUsersSelect.projectUserFormatResult - formatSelection: projectUsersSelect.projectUserFormatSelection - dropdownCssClass: "ajax-project-users-dropdown" - dropdownAutoWidth: true - escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results - m - - projectUserFormatResult: (user) -> - if user.avatar_url - avatar = user.avatar_url - else - avatar = gon.default_avatar_url - - if user.id == '' - avatarMarkup = '' - else - avatarMarkup = "
" - - "
- #{avatarMarkup} -
#{user.name}
-
#{user.username}
-
" - - projectUserFormatSelection: (user) -> - user.name - -$ -> - projectUsersSelect.init() diff --git a/app/assets/javascripts/projects_list.js.coffee b/app/assets/javascripts/projects_list.js.coffee new file mode 100644 index 0000000000..c0e36d1ccc --- /dev/null +++ b/app/assets/javascripts/projects_list.js.coffee @@ -0,0 +1,24 @@ +class @ProjectsList + constructor: -> + $(".projects-list .js-expand").on 'click', (e) -> + e.preventDefault() + list = $(this).closest('.projects-list') + list.find("li").show() + list.find("li.bottom").hide() + + $(".projects-list-filter").keyup -> + terms = $(this).val() + uiBox = $(this).closest('.panel') + if terms == "" || terms == undefined + uiBox.find(".projects-list li").show() + else + uiBox.find(".projects-list li").each (index) -> + name = $(this).find(".filter-title").text() + + if name.toLowerCase().search(terms.toLowerCase()) == -1 + $(this).hide() + else + $(this).show() + uiBox.find(".projects-list li.bottom").hide() + + diff --git a/app/assets/javascripts/protected_branches.js.coffee b/app/assets/javascripts/protected_branches.js.coffee new file mode 100644 index 0000000000..5753c9d4e7 --- /dev/null +++ b/app/assets/javascripts/protected_branches.js.coffee @@ -0,0 +1,21 @@ +$ -> + $(".protected-branches-list :checkbox").change (e) -> + name = $(this).attr("name") + if name == "developers_can_push" + id = $(this).val() + checked = $(this).is(":checked") + url = $(this).data("url") + $.ajax + type: "PUT" + url: url + dataType: "json" + data: + id: id + developers_can_push: checked + + success: -> + row = $(e.target) + row.closest('tr').effect('highlight') + + error: -> + new Flash("Failed to update branch!", "alert") diff --git a/app/assets/javascripts/search_autocomplete.js.coffee b/app/assets/javascripts/search_autocomplete.js.coffee index e144dfa1d6..c180136526 100644 --- a/app/assets/javascripts/search_autocomplete.js.coffee +++ b/app/assets/javascripts/search_autocomplete.js.coffee @@ -1,4 +1,4 @@ -class SearchAutocomplete +class @SearchAutocomplete constructor: (search_autocomplete_path, project_id, project_ref) -> project_id = '' unless project_id project_ref = '' unless project_ref @@ -9,5 +9,3 @@ class SearchAutocomplete minLength: 1 select: (event, ui) -> location.href = ui.item.url - -@SearchAutocomplete = SearchAutocomplete diff --git a/app/assets/javascripts/shortcuts.js.coffee b/app/assets/javascripts/shortcuts.js.coffee index e7e40a066e..e9aeb1e952 100644 --- a/app/assets/javascripts/shortcuts.js.coffee +++ b/app/assets/javascripts/shortcuts.js.coffee @@ -1,11 +1,30 @@ -class Shortcuts +class @Shortcuts constructor: -> + @enabledHelp = [] + Mousetrap.reset() + Mousetrap.bind('?', @selectiveHelp) + Mousetrap.bind('s', Shortcuts.focusSearch) + + selectiveHelp: (e) => + Shortcuts.showHelp(e, @enabledHelp) + + @showHelp: (e, location) -> if $('#modal-shortcuts').length > 0 $('#modal-shortcuts').modal('show') else $.ajax( url: '/help/shortcuts', - dataType: "script" + dataType: 'script', + success: (e) -> + if location and location.length > 0 + for l in location + $(l).show() + else + $('.hidden-shortcut').show() + $('.js-more-help-button').remove() ) + e.preventDefault() -@Shortcuts = Shortcuts + @focusSearch: (e) -> + $('#search').focus() + e.preventDefault() diff --git a/app/assets/javascripts/shortcuts_dashboard_navigation.js.coffee b/app/assets/javascripts/shortcuts_dashboard_navigation.js.coffee new file mode 100644 index 0000000000..4a05bdccdb --- /dev/null +++ b/app/assets/javascripts/shortcuts_dashboard_navigation.js.coffee @@ -0,0 +1,14 @@ +#= require shortcuts + +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')) + + @findAndFollowLink: (selector) -> + link = $(selector).attr('href') + if link + window.location = link diff --git a/app/assets/javascripts/shortcuts_issueable.coffee b/app/assets/javascripts/shortcuts_issueable.coffee new file mode 100644 index 0000000000..b8dae71e03 --- /dev/null +++ b/app/assets/javascripts/shortcuts_issueable.coffee @@ -0,0 +1,19 @@ +#= require shortcuts_navigation + +class @ShortcutsIssueable extends ShortcutsNavigation + constructor: (isMergeRequest) -> + super() + Mousetrap.bind('a', -> + $('.js-assignee').select2('open') + return false + ) + Mousetrap.bind('m', -> + $('.js-milestone').select2('open') + return false + ) + + if isMergeRequest + @enabledHelp.push('.hidden-shortcut.merge_reuests') + else + @enabledHelp.push('.hidden-shortcut.issues') + diff --git a/app/assets/javascripts/shortcuts_navigation.coffee b/app/assets/javascripts/shortcuts_navigation.coffee new file mode 100644 index 0000000000..31895fbf2b --- /dev/null +++ b/app/assets/javascripts/shortcuts_navigation.coffee @@ -0,0 +1,20 @@ +#= require shortcuts + +class @ShortcutsNavigation extends Shortcuts + constructor: -> + super() + Mousetrap.bind('g p', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-project')) + Mousetrap.bind('g f', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-tree')) + Mousetrap.bind('g c', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-commits')) + Mousetrap.bind('g n', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-network')) + Mousetrap.bind('g g', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-graphs')) + Mousetrap.bind('g i', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-issues')) + Mousetrap.bind('g m', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-merge_requests')) + Mousetrap.bind('g w', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-wiki')) + Mousetrap.bind('g s', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-snippets')) + @enabledHelp.push('.hidden-shortcut.project') + + @findAndFollowLink: (selector) -> + link = $(selector).attr('href') + if link + window.location = link diff --git a/app/assets/javascripts/shortcuts_network.js.coffee b/app/assets/javascripts/shortcuts_network.js.coffee new file mode 100644 index 0000000000..cc95ad7ebf --- /dev/null +++ b/app/assets/javascripts/shortcuts_network.js.coffee @@ -0,0 +1,12 @@ +#= require shortcuts_navigation + +class @ShortcutsNetwork extends ShortcutsNavigation + constructor: (@graph) -> + super() + Mousetrap.bind(['left', 'h'], @graph.scrollLeft) + Mousetrap.bind(['right', 'l'], @graph.scrollRight) + Mousetrap.bind(['up', 'k'], @graph.scrollUp) + Mousetrap.bind(['down', 'j'], @graph.scrollDown) + Mousetrap.bind(['shift+up', 'shift+k'], @graph.scrollTop) + Mousetrap.bind(['shift+down', 'shift+j'], @graph.scrollBottom) + @enabledHelp.push('.hidden-shortcut.network') diff --git a/app/assets/javascripts/sidebar.js.coffee b/app/assets/javascripts/sidebar.js.coffee index c084d730d6..2e3f560825 100644 --- a/app/assets/javascripts/sidebar.js.coffee +++ b/app/assets/javascripts/sidebar.js.coffee @@ -1,26 +1,9 @@ -responsive_resize = -> - current_width = $(window).width() - if current_width < 985 - $('.responsive-side').addClass("ui right wide sidebar") - else - $('.responsive-side').removeClass("ui right wide sidebar") +$(document).on("click", '.toggle-nav-collapse', (e) -> + e.preventDefault() + collapsed = 'page-sidebar-collapsed' + expanded = 'page-sidebar-expanded' -$ -> - # Depending on window size, set the sidebar offscreen. - responsive_resize() - - $('.sidebar-expand-button').click -> - $('.ui.sidebar') - .sidebar({overlay: true}) - .sidebar('toggle') - - # Hide sidebar on click outside of sidebar - $(document).mouseup (e) -> - container = $(".ui.sidebar") - container.sidebar "hide" if not container.is(e.target) and container.has(e.target).length is 0 - return - -# On resize, check if sidebar should be offscreen. -$(window).resize -> - responsive_resize() - return + $('.page-with-sidebar').toggleClass("#{collapsed} #{expanded}") + $('.toggle-nav-collapse i').toggleClass("fa-angle-right fa-angle-left") + $.cookie("collapsed_nav", $('.page-with-sidebar').hasClass(collapsed), { path: '/' }) +) diff --git a/app/assets/javascripts/stat_graph.js.coffee b/app/assets/javascripts/stat_graph.js.coffee index b129619696..f36c71fd25 100644 --- a/app/assets/javascripts/stat_graph.js.coffee +++ b/app/assets/javascripts/stat_graph.js.coffee @@ -1,4 +1,4 @@ -class window.StatGraph +class @StatGraph @log: {} @get_log: -> @log diff --git a/app/assets/javascripts/stat_graph_contributors.js.coffee b/app/assets/javascripts/stat_graph_contributors.js.coffee index 168b733704..27f0fd31d5 100644 --- a/app/assets/javascripts/stat_graph_contributors.js.coffee +++ b/app/assets/javascripts/stat_graph_contributors.js.coffee @@ -1,4 +1,4 @@ -class window.ContributorsStatGraph +class @ContributorsStatGraph init: (log) -> @parsed_log = ContributorsStatGraphUtil.parse_log(log) @set_current_field("commits") @@ -24,22 +24,7 @@ class window.ContributorsStatGraph class: 'graph-author-commits-count' }) commits.text(author.commits + " commits") - - additions = $('', { - class: 'graph-additions' - }) - additions.text(author.additions + " ++") - - deletions = $('', { - class: 'graph-deletions' - }) - deletions.text(author.deletions + " --") - $('').append(commits) - .append(" / ") - .append(additions) - .append(" / ") - .append(deletions) create_author_header: (author) -> list_item = $('
  • ', { diff --git a/app/assets/javascripts/stat_graph_contributors_graph.js.coffee b/app/assets/javascripts/stat_graph_contributors_graph.js.coffee index 834c7e5dab..8b82d20c6c 100644 --- a/app/assets/javascripts/stat_graph_contributors_graph.js.coffee +++ b/app/assets/javascripts/stat_graph_contributors_graph.js.coffee @@ -1,4 +1,4 @@ -class window.ContributorsGraph +class @ContributorsGraph MARGIN: top: 20 right: 20 @@ -44,9 +44,9 @@ class window.ContributorsGraph set_data: (data) -> @data = data -class window.ContributorsMasterGraph extends ContributorsGraph +class @ContributorsMasterGraph extends ContributorsGraph constructor: (@data) -> - @width = $('.container').width() - 70 + @width = $('.container').width() - 345 @height = 200 @x = null @y = null @@ -117,9 +117,9 @@ class window.ContributorsMasterGraph extends ContributorsGraph @svg.select("path").attr("d", @area) @svg.select(".y.axis").call(@y_axis) -class window.ContributorsAuthorGraph extends ContributorsGraph +class @ContributorsAuthorGraph extends ContributorsGraph constructor: (@data) -> - @width = $('.container').width()/2 - 100 + @width = $('.container').width()/2 - 225 @height = 200 @x = null @y = null diff --git a/app/assets/javascripts/stat_graph_contributors_util.js.coffee b/app/assets/javascripts/stat_graph_contributors_util.js.coffee index 364cab1837..1670f5c7bc 100644 --- a/app/assets/javascripts/stat_graph_contributors_util.js.coffee +++ b/app/assets/javascripts/stat_graph_contributors_util.js.coffee @@ -90,4 +90,4 @@ window.ContributorsStatGraphUtil = true else false - + diff --git a/app/assets/javascripts/subscription.js.coffee b/app/assets/javascripts/subscription.js.coffee new file mode 100644 index 0000000000..7f41616d4e --- /dev/null +++ b/app/assets/javascripts/subscription.js.coffee @@ -0,0 +1,17 @@ +class @Subscription + constructor: (url) -> + $(".subscribe-button").unbind("click").click (event)=> + btn = $(event.currentTarget) + action = btn.find("span").text() + current_status = $(".subscription-status").attr("data-status") + btn.prop("disabled", true) + + $.post url, => + btn.prop("disabled", false) + status = if current_status == "subscribed" then "unsubscribed" else "subscribed" + $(".subscription-status").attr("data-status", status) + action = if status == "subscribed" then "Unsubscribe" else "Subscribe" + btn.find("span").text(action) + $(".subscription-status>div").toggleClass("hidden") + + diff --git a/app/assets/javascripts/team_members.js.coffee b/app/assets/javascripts/team_members.js.coffee deleted file mode 100644 index 5eaa8ad4ff..0000000000 --- a/app/assets/javascripts/team_members.js.coffee +++ /dev/null @@ -1,6 +0,0 @@ -class TeamMembers - constructor: -> - $('.team-members .project-access-select').on "change", -> - $(this.form).submit() - -@TeamMembers = TeamMembers diff --git a/app/assets/javascripts/tree.js.coffee b/app/assets/javascripts/tree.js.coffee index 4852e879b6..d428db5b42 100644 --- a/app/assets/javascripts/tree.js.coffee +++ b/app/assets/javascripts/tree.js.coffee @@ -1,4 +1,4 @@ -class TreeView +class @TreeView constructor: -> @initKeyNav() @@ -39,5 +39,3 @@ class TreeView else if e.which is 13 path = $('.tree-item.selected .tree-item-file-name a').attr('href') Turbolinks.visit(path) - -@TreeView = TreeView diff --git a/app/assets/javascripts/user.js.coffee b/app/assets/javascripts/user.js.coffee new file mode 100644 index 0000000000..d0d81f9692 --- /dev/null +++ b/app/assets/javascripts/user.js.coffee @@ -0,0 +1,4 @@ +class @User + constructor: -> + $('.profile-groups-avatars').tooltip("placement": "top") + new ProjectsList() diff --git a/app/assets/javascripts/users_select.js.coffee b/app/assets/javascripts/users_select.js.coffee index 86318bd7d9..aeeed9ca3c 100644 --- a/app/assets/javascripts/users_select.js.coffee +++ b/app/assets/javascripts/users_select.js.coffee @@ -1,5 +1,76 @@ -$ -> - userFormatResult = (user) -> +class @UsersSelect + constructor: -> + @usersPath = "/autocomplete/users.json" + @userPath = "/autocomplete/users/:id.json" + + $('.ajax-users-select').each (i, select) => + @projectId = $(select).data('project-id') + @groupId = $(select).data('group-id') + showNullUser = $(select).data('null-user') + showAnyUser = $(select).data('any-user') + showEmailUser = $(select).data('email-user') + firstUser = $(select).data('first-user') + + $(select).select2 + placeholder: "Search for a user" + multiple: $(select).hasClass('multiselect') + minimumInputLength: 0 + query: (query) => + @users query.term, (users) => + data = { results: users } + + if query.term.length == 0 + if firstUser + # Move current user to the front of the list + for obj, index in data.results + if obj.username == firstUser + data.results.splice(index, 1) + data.results.unshift(obj) + break + + if showNullUser + nullUser = { + name: 'Unassigned', + avatar: null, + username: 'none', + id: 0 + } + data.results.unshift(nullUser) + + if showAnyUser + anyUser = { + name: 'Any', + avatar: null, + username: 'none', + id: null + } + data.results.unshift(anyUser) + + if showEmailUser && data.results.length == 0 && query.term.match(/^[^@]+@[^@]+$/) + emailUser = { + name: "Invite \"#{query.term}\"", + avatar: null, + username: query.term, + id: query.term + } + data.results.unshift(emailUser) + + query.callback(data) + + initSelection: (element, callback) => + id = $(element).val() + if id != "" && id != "0" + @user(id, callback) + + formatResult: (args...) => + @formatResult(args...) + formatSelection: (args...) => + @formatSelection(args...) + dropdownCssClass: "ajax-users-dropdown" + escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results + m + + formatResult: (user) -> if user.avatar_url avatar = user.avatar_url else @@ -11,27 +82,36 @@ $ ->
    #{user.username}
    " - userFormatSelection = (user) -> + formatSelection: (user) -> user.name - $('.ajax-users-select').each (i, select) -> - $(select).select2 - placeholder: "Search for a user" - multiple: $(select).hasClass('multiselect') - minimumInputLength: 0 - query: (query) -> - Api.users query.term, (users) -> - data = { results: users } - query.callback(data) + user: (user_id, callback) => + url = @buildUrl(@userPath) + url = url.replace(':id', user_id) - initSelection: (element, callback) -> - id = $(element).val() - if id isnt "" - Api.user(id, callback) + $.ajax( + url: url + dataType: "json" + ).done (user) -> + callback(user) + # Return users list. Filtered by query + # Only active users retrieved + users: (query, callback) => + url = @buildUrl(@usersPath) - formatResult: userFormatResult - formatSelection: userFormatSelection - dropdownCssClass: "ajax-users-dropdown" - escapeMarkup: (m) -> # we do not want to escape markup since we are displaying html in results - m + $.ajax( + url: url + data: + search: query + per_page: 20 + active: true + project_id: @projectId + group_id: @groupId + dataType: "json" + ).done (users) -> + callback(users) + + buildUrl: (url) -> + url = gon.relative_url_root + url if gon.relative_url_root? + return url diff --git a/app/assets/javascripts/wikis.js.coffee b/app/assets/javascripts/wikis.js.coffee index 17e790e5b7..66757565d3 100644 --- a/app/assets/javascripts/wikis.js.coffee +++ b/app/assets/javascripts/wikis.js.coffee @@ -1,4 +1,4 @@ -class Wikis +class @Wikis constructor: -> $('.build-new-wiki').bind "click", -> field = $('#new_wiki_path') @@ -7,6 +7,3 @@ class Wikis if(slug.length > 0) location.href = path + "/" + slug - - -@Wikis = Wikis diff --git a/app/assets/javascripts/zen_mode.js.coffee b/app/assets/javascripts/zen_mode.js.coffee new file mode 100644 index 0000000000..0fb8f7ed75 --- /dev/null +++ b/app/assets/javascripts/zen_mode.js.coffee @@ -0,0 +1,67 @@ +class @ZenMode + @fullscreen_prefix = 'fullscreen_' + + constructor: -> + @active_zen_area = null + @active_checkbox = null + @scroll_position = 0 + + $(window).scroll => + if not @active_checkbox + @scroll_position = window.pageYOffset + + $('body').on 'click', '.zen-enter-link', (e) => + e.preventDefault() + $(e.currentTarget).closest('.zennable').find('.zen-toggle-comment').prop('checked', true) + + $('body').on 'click', '.zen-leave-link', (e) => + e.preventDefault() + $(e.currentTarget).closest('.zennable').find('.zen-toggle-comment').prop('checked', false) + + $('body').on 'change', '.zen-toggle-comment', (e) => + checkbox = e.currentTarget + if checkbox.checked + # Disable other keyboard shortcuts in ZEN mode + Mousetrap.pause() + @udpateActiveZenArea(checkbox) + else + @exitZenMode() + + $(document).on 'keydown', (e) => + if e.keyCode is $.ui.keyCode.ESCAPE + @exitZenMode() + e.preventDefault() + + $(window).on 'hashchange', @updateZenModeFromLocationHash + + udpateActiveZenArea: (checkbox) => + @active_checkbox = $(checkbox) + @active_checkbox.prop('checked', true) + @active_zen_area = @active_checkbox.parent().find('textarea') + @active_zen_area.focus() + window.location.hash = ZenMode.fullscreen_prefix + @active_checkbox.prop('id') + + exitZenMode: => + if @active_zen_area isnt null + Mousetrap.unpause() + @active_checkbox.prop('checked', false) + @active_zen_area = null + @active_checkbox = null + window.location.hash = '' + window.scrollTo(window.pageXOffset, @scroll_position) + # Enable dropzone when leaving ZEN mode + Dropzone.forElement('.div-dropzone').enable() + + checkboxFromLocationHash: (e) -> + id = $.trim(window.location.hash.replace('#' + ZenMode.fullscreen_prefix, '')) + if id + return $('.zennable input[type=checkbox]#' + id)[0] + else + return null + + updateZenModeFromLocationHash: (e) => + checkbox = @checkboxFromLocationHash() + if checkbox + @udpateActiveZenArea(checkbox) + else + @exitZenMode() diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 0d404f1505..015ff2ce4e 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -6,17 +6,22 @@ *= require jquery.ui.autocomplete *= require jquery.atwho *= require select2 - *= require highlightjs.min *= require_self *= require dropzone/basic + *= require cal-heatmap */ -@import "main/*"; + +@import "base/variables"; +@import "base/mixins"; +@import "base/layout"; + /** * Customized Twitter bootstrap */ -@import 'gl_bootstrap'; +@import 'base/gl_variables'; +@import 'base/gl_bootstrap'; /** * NProgress load bar css @@ -39,7 +44,7 @@ * Page specific styles (issues, projects etc): */ -@import "sections/*"; +@import "pages/*"; /** * Code highlight @@ -55,8 +60,3 @@ * Styles for JS behaviors. */ @import "behaviors.scss"; - -/** -* Styles for responsive sidebar -*/ -@import "semantic-ui/modules/sidebar"; diff --git a/app/assets/stylesheets/gl_bootstrap.scss b/app/assets/stylesheets/base/gl_bootstrap.scss similarity index 57% rename from app/assets/stylesheets/gl_bootstrap.scss rename to app/assets/stylesheets/base/gl_bootstrap.scss index 34b0608646..62a3eade5c 100644 --- a/app/assets/stylesheets/gl_bootstrap.scss +++ b/app/assets/stylesheets/base/gl_bootstrap.scss @@ -1,16 +1,8 @@ /* * Twitter bootstrap with GitLab customizations/additions * - * Some unused bootstrap compontents like panels are not included. - * Other components like tabs are modified to GitLab style. - * */ -$font-size-base: 13px !default; -$nav-pills-active-link-hover-bg: $bg_primary; -$pagination-active-bg: $bg_primary; -$list-group-active-bg: $bg_primary; - // Core variables and mixins @import "bootstrap/variables"; @import "bootstrap/mixins"; @@ -26,6 +18,7 @@ $list-group-active-bg: $bg_primary; @import "bootstrap/grid"; @import "bootstrap/tables"; @import "bootstrap/forms"; +@import "bootstrap/buttons"; // Components @import "bootstrap/component-animations"; @@ -124,7 +117,7 @@ $list-group-active-bg: $bg_primary; color: #888; text-shadow: 0 1px 1px #fff; } - i[class^="icon-"] { + i[class~="fa"] { line-height: 14px; } } @@ -137,10 +130,6 @@ $list-group-active-bg: $bg_primary; } } } - - &.nav-small-tabs > li > a { - padding: 6px 9px; - } } .nav-tabs > li > a, @@ -148,57 +137,6 @@ $list-group-active-bg: $bg_primary; color: #666; } -.nav-small > li > a { - padding: 3px 5px; - font-size: 12px; -} - - -/* - * Callouts from Bootstrap3 docs - * - * Not quite alerts, but custom and helpful notes for folks reading the docs. - * Requires a base and modifier class. - */ - -/* Common styles for all types */ -.bs-callout { - margin: 20px 0; - padding: 20px; - border-left: 3px solid #eee; - color: #666; - background: #f9f9f9; -} -.bs-callout h4 { - margin-top: 0; - margin-bottom: 5px; -} -.bs-callout p:last-child { - margin-bottom: 0; -} - -/* Variations */ -.bs-callout-danger { - background-color: #fdf7f7; - border-color: #eed3d7; - color: #b94a48; -} -.bs-callout-warning { - background-color: #faf8f0; - border-color: #faebcc; - color: #8a6d3b; -} -.bs-callout-info { - background-color: #f4f8fa; - border-color: #bce8f1; - color: #34789a; -} -.bs-callout-success { - background-color: #dff0d8; - border-color: #5cA64d; - color: #3c763d; -} - /** * fix to keep tooltips position in top navigation bar * @@ -213,16 +151,12 @@ $list-group-active-bg: $bg_primary; * */ .panel { - @include border-radius(0px); - .panel-heading { - @include border-radius(0px); - font-size: 14px; - line-height: 18px; + font-weight: bold; .panel-head-actions { position: relative; - top: -7px; + top: -5px; float: right; } } @@ -233,8 +167,8 @@ $list-group-active-bg: $bg_primary; } .form-actions { - margin-bottom: 0; - background: #FFF; + margin: -15px; + margin-top: 18px; } } @@ -248,6 +182,7 @@ $list-group-active-bg: $bg_primary; .panel-heading { padding: 6px 15px; font-size: 13px; + font-weight: normal; a { color: #777; } @@ -255,60 +190,62 @@ $list-group-active-bg: $bg_primary; } } -.panel-default { - .panel-heading { - background-color: #EEE; +.panel-succes .panel-heading, +.panel-info .panel-heading, +.panel-danger .panel-heading, +.panel-warning .panel-heading, +.panel-primary .panel-heading, +.alert { + a:not(.btn) { + @extend .alert-link; + color: #fff; + text-decoration: underline; } } -.panel-danger { - border-color: $border_danger; - .panel-heading { - color: #ffffff; - background-color: $bg_danger; - border-color: $border_danger; - a { - color: #FFF; - text-decoration: underline; - } - } +// Typography ================================================================= + +.text-primary, +.text-primary:hover { + color: $brand-primary; } -.panel-success { - border-color: $border_success; - .panel-heading { - color: #ffffff; - background-color: $bg_success; - border-color: $border_success; - a { - color: #FFF; - text-decoration: underline; - } - } +.text-success, +.text-success:hover { + color: $brand-success; } -.panel-primary { - border-color: $border_primary; - .panel-heading { - color: #ffffff; - background-color: $bg_primary; - border-color: $border_primary; - a { - color: #FFF; - text-decoration: underline; - } - } +.text-danger, +.text-danger:hover { + color: $brand-danger; } -.panel-warning { - border-color: $border_warning; - .panel-heading { - color: #ffffff; - background-color: $bg_warning; - border-color: $border_warning; - a { - color: #FFF; +.text-warning, +.text-warning:hover { + color: $brand-warning; +} + +.text-info, +.text-info:hover { + color: $brand-info; +} + +// Tables ===================================================================== + +table.table { + .dropdown-menu a { + text-decoration: none; + } + + .success, + .warning, + .danger, + .info { + color: #fff; + + a:not(.btn) { text-decoration: underline; + color: #fff; } } } diff --git a/app/assets/stylesheets/base/gl_variables.scss b/app/assets/stylesheets/base/gl_variables.scss new file mode 100644 index 0000000000..56f4c794e1 --- /dev/null +++ b/app/assets/stylesheets/base/gl_variables.scss @@ -0,0 +1,133 @@ +// Override Bootstrap variables here (defaults from bootstrap-sass v3.3.3): +// For all variables see https://github.com/twbs/bootstrap-sass/blob/master/templates/project/_bootstrap-variables.sass +// +// Variables +// -------------------------------------------------- + + +//== Colors +// +//## Gray and brand colors for use across Bootstrap. + +// $gray-base: #000 +// $gray-darker: lighten($gray-base, 13.5%) // #222 +// $gray-dark: lighten($gray-base, 20%) // #333 +// $gray: lighten($gray-base, 33.5%) // #555 +// $gray-light: lighten($gray-base, 46.7%) // #777 +// $gray-lighter: lighten($gray-base, 93.5%) // #eee + +$brand-primary: $gl-primary; +$brand-success: $gl-success; +$brand-info: $gl-info; +$brand-warning: $gl-warning; +$brand-danger: $gl-danger; + + +//== Scaffolding +// +$text-color: $gl-text-color; +$link-color: $gl-link-color; + + +//== Typography +// +//## Font, line-height, and color for body text, headings, and more. + +$font-family-sans-serif: $regular_font; +$font-family-monospace: $monospace_font; +$font-size-base: $gl-font-size; + + +//== Components +// +//## Define common padding and border radius sizes and more. Values based on 14px text and 1.428 line-height (~20px to start). + +$padding-base-vertical: 6px; +$padding-base-horizontal: 14px; + + +//== Forms +// +//## + +$input-color: $text-color; +$input-border: #DDD; +$input-border-focus: $brand-info; +$legend-color: $text-color; + + +//== Pagination +// +//## + +$pagination-color: #fff; +$pagination-bg: $brand-success; +$pagination-border: transparent; + +$pagination-hover-color: #fff; +$pagination-hover-bg: darken($brand-success, 15%); +$pagination-hover-border: transparent; + +$pagination-active-color: #fff; +$pagination-active-bg: darken($brand-success, 15%); +$pagination-active-border: transparent; + +$pagination-disabled-color: #b4bcc2; +$pagination-disabled-bg: lighten($brand-success, 15%); +$pagination-disabled-border: transparent; + + +//== Form states and alerts +// +//## Define colors for form feedback states and, by default, alerts. + +$state-success-text: #fff; +$state-success-bg: $brand-success; +$state-success-border: $brand-success; + +$state-info-text: #fff; +$state-info-bg: $brand-info; +$state-info-border: $brand-info; + +$state-warning-text: #fff; +$state-warning-bg: $brand-warning; +$state-warning-border: $brand-warning; + +$state-danger-text: #fff; +$state-danger-bg: $brand-danger; +$state-danger-border: $brand-danger; + + +//== Alerts +// +//## Define alert colors, border radius, and padding. + +$alert-border-radius: 0; + + +//== Panels +// +//## + +$panel-border-radius: 0; +$panel-default-text: $text-color; +$panel-default-border: $border-color; +$panel-default-heading-bg: $background-color; + + +//== Wells +// +//## + +$well-bg: #F9F9F9; +$well-border: #EEE; + +//== Code +// +//## + +$code-color: #c7254e; +$code-bg: #f9f2f4; + +$kbd-color: #fff; +$kbd-bg: #333; diff --git a/app/assets/stylesheets/main/layout.scss b/app/assets/stylesheets/base/layout.scss similarity index 68% rename from app/assets/stylesheets/main/layout.scss rename to app/assets/stylesheets/base/layout.scss index e28da65c01..62c11b0636 100644 --- a/app/assets/stylesheets/main/layout.scss +++ b/app/assets/stylesheets/base/layout.scss @@ -2,10 +2,10 @@ html { overflow-y: scroll; &.touch .tooltip { display: none !important; } -} -body { - padding-bottom: 20px; + body { + padding-top: 46px; + } } .container { @@ -16,3 +16,7 @@ body { .container .content { margin: 0 0; } + +.navless-container { + margin-top: 30px; +} diff --git a/app/assets/stylesheets/main/mixins.scss b/app/assets/stylesheets/base/mixins.scss similarity index 80% rename from app/assets/stylesheets/main/mixins.scss rename to app/assets/stylesheets/base/mixins.scss index 93faf5ced6..216f25cdcd 100644 --- a/app/assets/stylesheets/main/mixins.scss +++ b/app/assets/stylesheets/base/mixins.scss @@ -58,8 +58,8 @@ } @mixin md-typography { - font-size: 14px; - line-height: 1.6; + font-size: 15px; + line-height: 1.5; img { max-width: 100%; @@ -69,7 +69,12 @@ margin-top: 0; } - code { padding: 0 4px; } + code { + font-family: $monospace_font; + white-space: pre; + word-wrap: normal; + padding: 0; + } h1 { margin-top: 45px; @@ -93,7 +98,7 @@ blockquote p { color: #888; - font-size: 14px; + font-size: 15px; line-height: 1.5; } @@ -114,14 +119,22 @@ li { line-height: 1.5; } -} -@mixin page-title { - color: #333; - line-height: 1.5; - font-weight: normal; - margin-top: 0px; - margin-bottom: 10px; + a[href*="/uploads/"], a[href*="storage.googleapis.com/google-code-attachments/"] { + &:before { + margin-right: 4px; + + font: normal normal normal 14px/1 FontAwesome; + font-size: inherit; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + content: "\f0c6"; + } + + &:hover:before { + text-decoration: none; + } + } } @mixin str-truncated($max_width: 82%) { diff --git a/app/assets/stylesheets/base/variables.scss b/app/assets/stylesheets/base/variables.scss new file mode 100644 index 0000000000..596376c397 --- /dev/null +++ b/app/assets/stylesheets/base/variables.scss @@ -0,0 +1,34 @@ +$style_color: #474D57; +$hover: #FFF3EB; +$gl-text-color: #222222; +$gl-link-color: #446e9b; +$nprogress-color: #c0392b; +$gl-font-size: 14px; +$list-font-size: 15px; +$sidebar_width: 230px; +$avatar_radius: 50%; +$code_font_size: 13px; +$code_line_height: 1.5; +$border-color: #E5E5E5; +$background-color: #f5f5f5; + +/* + * State colors: + */ +$gl-primary: #446e9b; +$gl-success: #019875; +$gl-info: #029ACF; +$gl-warning: #EB9532; +$gl-danger: #d9534f; + +/* + * Commit Diff Colors + */ +$added: #63c363; +$deleted: #f77; + +/* + * Fonts + */ +$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'DejaVu Sans Mono', 'Ubuntu Mono', 'Courier New', 'andale mono', 'lucida console', monospace; +$regular_font: "Helvetica Neue", Helvetica, Arial, sans-serif; diff --git a/app/assets/stylesheets/behaviors.scss b/app/assets/stylesheets/behaviors.scss index be4c4d07f1..469f4f296a 100644 --- a/app/assets/stylesheets/behaviors.scss +++ b/app/assets/stylesheets/behaviors.scss @@ -1,12 +1,22 @@ // Details //-------- -.js-details-container .content { display: none; } -.js-details-container .content.hide { display: block; } -.js-details-container.open .content { display: block; } -.js-details-container.open .content.hide { display: none; } +.js-details-container { + .content { + display: none; + &.hide { display: block; } + } + &.open .content { + display: block; + &.hide { display: none; } + } +} // Toggle between two states. -.js-toggler-container .turn-on { display: block; } -.js-toggler-container .turn-off { display: none; } -.js-toggler-container.on .turn-on { display: none; } -.js-toggler-container.on .turn-off { display: block; } +.js-toggler-container { + .turn-on { display: block; } + .turn-off { display: none; } + &.on { + .turn-on { display: none; } + .turn-off { display: block; } + } +} diff --git a/app/assets/stylesheets/generic/avatar.scss b/app/assets/stylesheets/generic/avatar.scss index 4f038b977e..8595887c3b 100644 --- a/app/assets/stylesheets/generic/avatar.scss +++ b/app/assets/stylesheets/generic/avatar.scss @@ -2,15 +2,21 @@ float: left; margin-right: 12px; width: 40px; - padding: 1px; - @include border-radius(4px); + height: 40px; + padding: 0; + @include border-radius($avatar_radius); &.avatar-inline { float: none; - margin-left: 3px; + margin-left: 4px; + margin-bottom: 2px; - &.s16 { margin-right: 2px; } - &.s24 { margin-right: 2px; } + &.s16 { margin-right: 4px; } + &.s24 { margin-right: 4px; } + } + + &.group-avatar, &.project-avatar, &.avatar-tile { + @include border-radius(0px); } &.s16 { width: 16px; height: 16px; margin-right: 6px; } @@ -21,3 +27,16 @@ &.s90 { width: 90px; height: 90px; margin-right: 15px; } &.s160 { width: 160px; height: 160px; margin-right: 20px; } } + +.identicon { + text-align: center; + vertical-align: top; + + &.s16 { font-size: 12px; line-height: 1.33; } + &.s24 { font-size: 14px; line-height: 1.8; } + &.s26 { font-size: 20px; line-height: 1.33; } + &.s32 { font-size: 22px; line-height: 32px; } + &.s60 { font-size: 32px; line-height: 60px; } + &.s90 { font-size: 36px; line-height: 90px; } + &.s160 { font-size: 96px; line-height: 1.33; } +} diff --git a/app/assets/stylesheets/generic/buttons.scss b/app/assets/stylesheets/generic/buttons.scss index d098f1ecaa..cd6bf64c0a 100644 --- a/app/assets/stylesheets/generic/buttons.scss +++ b/app/assets/stylesheets/generic/buttons.scss @@ -1,127 +1,15 @@ .btn { - display: inline-block; - margin-bottom: 0; - font-weight: normal; - text-align: center; - vertical-align: middle; - cursor: pointer; - background-image: none; - border: $btn-border; - white-space: nowrap; - padding: 6px 12px; - font-size: 13px; - line-height: 18px; - border-radius: 4px; - -webkit-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - -o-user-select: none; - user-select: none; - color: #444444; - background-color: #fff; - text-shadow: none; - - &.hover, - &:hover { - color: #444444; - text-decoration: none; - background-color: #ebebeb; - border-color: #adadad; - } - - &.focus, - &:focus { - color: #444444; - text-decoration: none; - outline: thin dotted #333; - outline: 5px auto -webkit-focus-ring-color; - outline-offset: -2px; - } - - &.active, - &:active { - outline: 0; - background-image: none; - -webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - box-shadow: inset 0 3px 5px rgba(0, 0, 0, 0.125); - } - - &.disabled, - &[disabled] { - cursor: not-allowed; - pointer-events: none; - opacity: 0.65; - filter: alpha(opacity=65); - -webkit-box-shadow: none; - box-shadow: none; - } - - &.btn-primary { - color: #ffffff; - background-color: $bg_primary; - border-color: $border_primary; - - &.hover, - &:hover, - &.disabled, - &[disabled] { - color: #ffffff; - } - } - - &.btn-success { - color: #ffffff; - background-color: $bg_success; - border-color: $border_success; - - - &.hover, - &:hover, - &.disabled, - &[disabled] { - color: #ffffff; - } - } - - &.btn-danger { - color: #ffffff; - background-color: $bg_danger; - border-color: $border_danger; - - - &.hover, - &:hover, - &.disabled, - &[disabled] { - color: #ffffff; - } - } - - &.btn-warning { - color: #ffffff; - background-color: $bg_warning; - border-color: $border_warning; - - - &.hover, - &:hover, - &.disabled, - &[disabled] { - color: #ffffff; - } - } + @extend .btn-default; &.btn-new { @extend .btn-success; } &.btn-create { - @extend .wide; @extend .btn-success; } &.btn-save { - @extend .wide; @extend .btn-primary; } @@ -133,34 +21,17 @@ float: right; } - &.wide { - padding-left: 20px; - padding-right: 20px; - } - - &.btn-small { - padding: 2px 10px; - font-size: 12px; - } - - &.btn-tiny { - font-size: 11px; - padding: 2px 6px; - line-height: 16px; - margin: 2px; - } - &.btn-close { - color: $bg_danger; - border-color: $border_danger; + color: $gl-danger; + border-color: $gl-danger; &:hover { color: #B94A48; } } &.btn-reopen { - color: $bg_success; - border-color: $border_success; + color: $gl-success; + border-color: $gl-success; &:hover { color: #468847; } @@ -173,6 +44,14 @@ margin-right: 0px; } } + + &.btn-save { + @extend .btn-primary; + } + + &.btn-new, &.btn-create { + @extend .btn-success; + } } .btn-block { @@ -193,6 +72,3 @@ } } } - -.btn-group-small > .btn { @extend .btn.btn-small; } -.btn-group-tiny > .btn { @extend .btn.btn-tiny; } diff --git a/app/assets/stylesheets/generic/calendar.scss b/app/assets/stylesheets/generic/calendar.scss new file mode 100644 index 0000000000..a36fefe22c --- /dev/null +++ b/app/assets/stylesheets/generic/calendar.scss @@ -0,0 +1,90 @@ +.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; + } + } +} +/** +* This overwrites the default values of the cal-heatmap gem +*/ +.calendar { + .qi { + background-color: #999; + fill: #fff; + } + + .q1 { + background-color: #dae289; + fill: #ededed; + } + + .q2 { + background-color: #cedb9c; + fill: #ACD5F2; + } + + .q3 { + background-color: #b5cf6b; + fill: #7FA8D1; + } + + .q4 { + background-color: #637939; + fill: #49729B; + } + + .q5 { + background-color: #3b6427; + fill: #254E77; + } + + .domain-background { + fill: none; + shape-rendering: crispedges; + } + + .ch-tooltip { + position: absolute; + display: none; + margin-top: 22px; + margin-left: 1px; + font-size: 13px; + padding: 3px; + font-weight: 550; + background-color: #222; + span { + position: absolute; + width: 200px; + text-align: center; + visibility: hidden; + border-radius: 10px; + &:after { + content: ''; + position: absolute; + top: 100%; + left: 50%; + margin-left: -8px; + width: 0; + height: 0; + border-top: 8px solid #000000; + border-right: 8px solid transparent; + border-left: 8px solid transparent; + } + } + } +} diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss index 9d3c9f372a..7c3021989a 100644 --- a/app/assets/stylesheets/generic/common.scss +++ b/app/assets/stylesheets/generic/common.scss @@ -24,7 +24,7 @@ .slead { color: #666; - font-size: 14px; + font-size: 15px; margin-bottom: 12px; font-weight: normal; line-height: 24px; @@ -54,9 +54,14 @@ pre { text-shadow: none; } +.dropdown-menu-align-right { + left: auto; + right: 0px; +} + .dropdown-menu > li > a:hover, .dropdown-menu > li > a:focus { - background: $bg_primary; + background: $gl-primary; color: #FFF } @@ -66,7 +71,7 @@ pre { /** FLASH message **/ .author_link { - color: $link_color; + color: $gl-link-color; } .help li { color:$style_color; } @@ -128,7 +133,7 @@ p.time { } .highlight_word { - border-bottom: 2px solid #F90; + background: #fafe3d; } .thin_area{ @@ -162,7 +167,7 @@ li.note { background-color: inherit; } -.team_member_show { +.project_member_show { td:first-child { color: #aaa; } @@ -207,24 +212,16 @@ li.note { } } -.no-ssh-key-message { - padding: 10px 0; - background: #C67; - margin: 0; - color: #FFF; - margin-top: -1px; +.browser-alert { + padding: 10px; text-align: center; - + background: #C67; + color: #fff; + font-weight: bold; a { color: #fff; text-decoration: underline; } - - .links-xs { - text-align: center; - font-size: 16px; - padding: 5px; - } } .warning_message { @@ -249,7 +246,7 @@ li.note { .milestone { &.milestone-closed { - background: #eee; + background: #f9f9f9; } .progress { margin-bottom: 0; @@ -281,10 +278,6 @@ img.emoji { height: 220px; } -.navless-container { - margin-top: 20px; -} - .description-block { @extend .light-well; @extend .light; @@ -300,27 +293,21 @@ table { .dashboard-intro-icon { float: left; + text-align: center; font-size: 32px; color: #AAA; - padding: 5px 0; - width: 50px; - min-height: 100px; + width: 60px; } -.broadcast-message { - padding: 10px; - text-align: center; - background: #555; - color: #BBB; -} - -.broadcast-message-preview { - @extend .broadcast-message; - margin-bottom: 20px; +.dashboard-intro-text { + display: inline-block; + margin-left: -60px; + padding-left: 60px; + width: 100%; } .btn-sign-in { - margin-top: 7px; + margin-top: 5px; text-shadow: none; } @@ -330,16 +317,19 @@ table { } } -@media (max-width: $screen-xs-max) { - .container .content { margin-top: 20px; } -} - .wiki .highlight, .note-body .highlight { margin-bottom: 9px; } -.footer-links a { - margin-right: 15px; +.wiki .code { + overflow-x: auto; +} + +.footer-links { + margin-bottom: 20px; + a { + margin-right: 15px; + } } .search_box { @@ -356,3 +346,31 @@ table { font-size: 42px; } +.task-status { + margin-left: 10px; +} + +#nprogress .spinner { + top: auto !important; + bottom: 20px !important; + left: 20px !important; +} + +.header-with-avatar { + h3 { + margin: 0; + font-weight: bold; + } + + .username { + font-size: 18px; + color: #666; + margin-top: 8px; + } + + .description { + font-size: 16px; + color: #666; + margin-top: 8px; + } +} diff --git a/app/assets/stylesheets/generic/files.scss b/app/assets/stylesheets/generic/files.scss index 1412277ffb..8014dcb165 100644 --- a/app/assets/stylesheets/generic/files.scss +++ b/app/assets/stylesheets/generic/files.scss @@ -3,7 +3,7 @@ * */ .file-holder { - border: 1px solid #CCC; + border: 1px solid $border-color; margin-bottom: 1em; table { @@ -11,38 +11,33 @@ } .file-title { - background: #EEE; - border-bottom: 1px solid #CCC; + position: relative; + background: $background-color; + border-bottom: 1px solid $border-color; text-shadow: 0 1px 1px #fff; margin: 0; text-align: left; padding: 10px 15px; - .options { + .file-actions { float: right; - margin-top: -3px; + position: absolute; + top: 5px; + right: 15px; + + .btn { + padding: 0px 10px; + font-size: 13px; + line-height: 28px; + } } .left-options { margin-top: -3px; } - - .file_name { - font-weight: bold; - padding-left: 3px; - font-size: 14px; - - small { - color: #888; - font-size: 13px; - font-weight: normal; - padding-left: 10px; - } - } } .file-content { background: #fff; - font-size: 11px; &.image_file { background: #eee; @@ -54,13 +49,10 @@ } &.wiki { - font-size: 14px; - line-height: 1.6; padding: 25px; .highlight { margin-bottom: 9px; - @include border-radius(4px); > pre { margin: 0; @@ -102,7 +94,7 @@ } .author, .blame_commit { - background: #f5f5f5; + background: $background-color; vertical-align: top; } .lines { @@ -123,7 +115,7 @@ ol { margin-left: 40px; padding: 10px 0; - border-left: 1px solid #CCC; + border-left: 1px solid $border-color; margin-bottom: 0; background: white; li { diff --git a/app/assets/stylesheets/generic/filters.scss b/app/assets/stylesheets/generic/filters.scss new file mode 100644 index 0000000000..bd93a79722 --- /dev/null +++ b/app/assets/stylesheets/generic/filters.scss @@ -0,0 +1,55 @@ +.filter-item { + margin-right: 15px; +} + +.issues-state-filters { + li.active a { + border-color: #DDD !important; + + &, &:hover, &:active, &.active { + background: #f5f5f5 !important; + border-bottom: 1px solid #f5f5f5 !important; + } + } +} + +.issues-details-filters { + font-size: 13px; + background: #f5f5f5; + margin: -10px 0; + padding: 10px 15px; + margin-top: -15px; + border-left: 1px solid #DDD; + border-right: 1px solid #DDD; + + .btn { + font-size: 13px; + } +} + +@media (min-width: 800px) { + .issues-filters, + .issues_bulk_update { + select, .select2-container { + width: 120px !important; + display: inline-block; + } + } +} + +@media (min-width: 1200px) { + .issues-filters, + .issues_bulk_update { + select, .select2-container { + width: 150px !important; + display: inline-block; + } + } +} + +.issues-filters, +.issues_bulk_update { + .select2-container .select2-choice { + color: #444 !important; + } +} diff --git a/app/assets/stylesheets/generic/flash.scss b/app/assets/stylesheets/generic/flash.scss index 95d28aaef6..82eb50ad4b 100644 --- a/app/assets/stylesheets/generic/flash.scss +++ b/app/assets/stylesheets/generic/flash.scss @@ -1,25 +1,17 @@ .flash-container { - display: none; cursor: pointer; margin: 0; - text-align: center; - color: #fff; font-size: 14px; - position: fixed; - bottom: 0; width: 100%; - opacity: 0.8; z-index: 100; .flash-notice { - background: #49C; - padding: 10px; - text-shadow: 0 1px 1px #178; + @extend .alert; + @extend .alert-info; } .flash-alert { - background: #C67; - text-shadow: 0 1px 1px #945; - padding: 10px; + @extend .alert; + @extend .alert-danger; } } diff --git a/app/assets/stylesheets/generic/forms.scss b/app/assets/stylesheets/generic/forms.scss index 2a31cae5df..266041403e 100644 --- a/app/assets/stylesheets/generic/forms.scss +++ b/app/assets/stylesheets/generic/forms.scss @@ -1,3 +1,7 @@ +textarea { + resize: vertical; +} + input[type='search'].search-text-input { background-image: image-url("icon-search.png"); background-repeat: no-repeat; @@ -11,10 +15,6 @@ input[type='text'].danger { text-shadow: 0 1px 1px #fff } -fieldset legend { - font-size: 16px; -} - .datetime-controls { select { width: 100px; @@ -25,9 +25,14 @@ fieldset legend { padding: 17px 20px 18px; margin-top: 18px; margin-bottom: 18px; - background-color: whitesmoke; - border-top: 1px solid #e5e5e5; - padding-left: 17%; + background-color: $background-color; + border-top: 1px solid $border-color; +} + +@media (min-width: $screen-sm-min) { + .form-actions { + padding-left: 17%; + } } label { @@ -83,3 +88,8 @@ label { .form-control { @include box-shadow(none); } + +.issuable-description, +.wiki-content { + margin-top: 35px; +} diff --git a/app/assets/stylesheets/generic/gfm.scss b/app/assets/stylesheets/generic/gfm.scss new file mode 100644 index 0000000000..8fac5e534f --- /dev/null +++ b/app/assets/stylesheets/generic/gfm.scss @@ -0,0 +1,21 @@ +/** + * Styles that apply to all GFM related forms. + */ +.issue-form, .merge-request-form, .wiki-form { + .description { + height: 16em; + border-top-left-radius: 0; + } +} + +.wiki-form { + .description { + height: 26em; + } +} + +.milestone-form { + .description { + height: 14em; + } +} diff --git a/app/assets/stylesheets/generic/highlight.scss b/app/assets/stylesheets/generic/highlight.scss index 4110bddf4f..2e13ee842e 100644 --- a/app/assets/stylesheets/generic/highlight.scss +++ b/app/assets/stylesheets/generic/highlight.scss @@ -1,4 +1,4 @@ -.highlighted-data { +.file-content.code { border: none; box-shadow: none; margin: 0px; @@ -10,11 +10,16 @@ border: none; border-radius: 0; font-family: $monospace_font; - font-size: 12px !important; - line-height: 16px !important; + font-size: $code_font_size !important; + line-height: $code_line_height !important; margin: 0; + overflow: auto; + overflow-y: hidden; + white-space: pre; + word-wrap: normal; code { + font-family: $monospace_font; white-space: pre; word-wrap: normal; padding: 0; @@ -25,10 +30,6 @@ } } - .hljs { - padding: 0; - } - .line-numbers { padding: 10px; text-align: right; @@ -37,8 +38,8 @@ a { font-family: $monospace_font; display: block; - font-size: 12px !important; - line-height: 16px !important; + font-size: $code_font_size !important; + line-height: $code_line_height !important; white-space: nowrap; i { @@ -51,14 +52,19 @@ } } } +} - .highlight { - overflow: auto; - overflow-y: hidden; +.note-text .code { + border: none; + box-shadow: none; + background: $background-color; + padding: 1em; + overflow-x: auto; - pre { - white-space: pre; - word-wrap: normal; - } + code { + font-family: $monospace_font; + white-space: pre; + word-wrap: normal; + padding: 0; } } diff --git a/app/assets/stylesheets/generic/issue_box.scss b/app/assets/stylesheets/generic/issue_box.scss index c468980f64..9558f241b7 100644 --- a/app/assets/stylesheets/generic/issue_box.scss +++ b/app/assets/stylesheets/generic/issue_box.scss @@ -1,112 +1,32 @@ /** - * Issue box: - * Huge block (one per page) for storing title, descripion and other information. + * Issue box for showing Open/Closed state: * Used for Issue#show page, MergeRequest#show page etc * - * CLasses: - * .issue-box - Regular box */ .issue-box { - color: #666; - margin:20px 0; - background: #FFF; - border: 1px solid #EEE; - @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.05)); + display: inline-block; + padding: 7px 13px; + font-weight: normal; + margin-right: 5px; &.issue-box-closed { - border-color: $border_danger; - .state { - background-color: $bg_danger; - color: #FFF; - border-color: $border_danger; - } + background-color: $gl-danger; + color: #FFF; } &.issue-box-merged { - border-color: $border_primary; - .state { - background-color: $bg_primary; - color: #FFF; - border-color: $border_primary; - } + background-color: $gl-primary; + color: #FFF; } &.issue-box-open { - border-color: $border_success; - .state { - border-color: $border_success; - background-color: $bg_success; - color: #FFF; - } + background-color: $gl-success; + color: #FFF; } &.issue-box-expired { - border-color: #cea61b; - .state { - border-color: #faebcc; - background: #cea61b; - color: #FFF; - } - } - - .control-group { - margin-bottom: 0; - } - - .state { - border-bottom: 1px solid #DDD; - padding: 10px 15px; - } - - .title { - font-size: 28px; - font-weight: normal; - line-height: 1.5; - margin: 0; - color: #333; - padding: 10px 15px; - } - - .context { - border: none; - border-top: 1px solid #eee; - padding: 10px 15px; - - // Reset text align for children - .text-right > * { text-align: left; } - - @media (max-width: $screen-xs-max) { - // Don't right align on mobile - .text-right { text-align: left; } - - .row .col-md-6 { - padding-top: 5px; - } - } - } - - .description { - padding: 0 15px 10px 15px; - } - - .title, .context, .description { - .clearfix { - margin: 0; - } - } - - .state-label { - font-size: 14px; - float: left; - font-weight: bold; - } - - .creator { - float: right; - a { - color: #FFF; - text-decoration: underline; - } + background: #cea61b; + color: #FFF; } } diff --git a/app/assets/stylesheets/generic/jquery.scss b/app/assets/stylesheets/generic/jquery.scss index bfbbc7d25e..871b808bad 100644 --- a/app/assets/stylesheets/generic/jquery.scss +++ b/app/assets/stylesheets/generic/jquery.scss @@ -41,8 +41,8 @@ } .ui-state-active { - border: 1px solid $bg_primary; - background: $bg_primary; + border: 1px solid $gl-primary; + background: $gl-primary; color: #FFF; } diff --git a/app/assets/stylesheets/generic/lists.scss b/app/assets/stylesheets/generic/lists.scss index 0cab6c44c9..08bf6e943d 100644 --- a/app/assets/stylesheets/generic/lists.scss +++ b/app/assets/stylesheets/generic/lists.scss @@ -35,18 +35,18 @@ color: #8a6d3b; } - &.smoke { background-color: #f5f5f5; } + &.smoke { background-color: $background-color; } &:hover { background: $hover; - border-bottom: 1px solid #ADF; + border-bottom: 1px solid darken($hover, 10%); } &:last-child { border-bottom: none; &.bottom { - background: #f5f5f5; + background: $background-color; } } @@ -61,7 +61,7 @@ p { padding-top: 1px; margin: 0; - color: #222; + color: $gray-dark; img { position: relative; top: 3px; @@ -69,15 +69,15 @@ } .well-title { - font-size: 14px; + font-size: $list-font-size; line-height: 18px; } .row_title { - font-weight: 500; - color: #444; + color: $gray-dark; + &:hover { - color: #444; + color: $text-color; text-decoration: underline; } } @@ -122,3 +122,7 @@ ul.bordered-list { } } } + +li.task-list-item { + list-style-type: none; +} diff --git a/app/assets/stylesheets/generic/markdown_area.scss b/app/assets/stylesheets/generic/markdown_area.scss index fbfa72c5e5..eb39b6bb7e 100644 --- a/app/assets/stylesheets/generic/markdown_area.scss +++ b/app/assets/stylesheets/generic/markdown_area.scss @@ -20,6 +20,7 @@ opacity: 0; font-size: 50px; transition: opacity 200ms ease-in-out; + pointer-events: none; } .div-dropzone-spinner { @@ -50,3 +51,37 @@ margin-bottom: 0; transition: opacity 200ms ease-in-out; } + +.md-preview-holder { + background: #FFF; + border: 1px solid #ddd; + min-height: 100px; + padding: 5px; + box-shadow: none; +} + +.new_note, +.edit_note, +.issuable-description, +.milestone-description, +.wiki-content, +.merge-request-form { + .nav-tabs { + margin-bottom: 0; + border: none; + + li a, + li.active a { + border: 1px solid #DDD; + } + } +} + +.markdown-area { + background: #FFF; + border: 1px solid #ddd; + min-height: 100px; + padding: 5px; + box-shadow: none; + width: 100%; +} diff --git a/app/assets/stylesheets/generic/mobile.scss b/app/assets/stylesheets/generic/mobile.scss new file mode 100644 index 0000000000..71a1fc4493 --- /dev/null +++ b/app/assets/stylesheets/generic/mobile.scss @@ -0,0 +1,69 @@ +/** Common mobile (screen XS, SM) styles **/ +@media (max-width: $screen-xs-max) { + .container .content { + margin-top: 20px; + } + + .nav.nav-tabs > li > a { + padding: 10px; + font-size: 12px; + margin-right: 3px; + + .badge { + display: none; + } + } + + .issues-filters, + .dash-projects-filters, + .check-all-holder { + display: none; + } + + .rss-btn { + display: none !important; + } + + .project-home-links { + display: none; + } +} + +@media (max-width: $screen-sm-max) { + .issues-filters { + .milestone-filter, .labels-filter { + display: none; + } + } + + .page-title { + .note_created_ago, .new-issue-link { + display: none; + } + } + + .issue_edited_ago, .note_edited_ago { + display: none; + } + + aside { + display: none; + } + + .show-aside { + display: block !important; + } +} + +.show-aside { + display: none; + position: fixed; + right: 0px; + top: 30%; + padding: 5px 15px; + background: #EEE; + font-size: 20px; + color: #777; + z-index: 100; + @include box-shadow(0 1px 2px #DDD); +} diff --git a/app/assets/stylesheets/generic/nav_sidebar.scss b/app/assets/stylesheets/generic/nav_sidebar.scss new file mode 100644 index 0000000000..3bcb7b8133 --- /dev/null +++ b/app/assets/stylesheets/generic/nav_sidebar.scss @@ -0,0 +1,193 @@ +.page-with-sidebar { + background: $background-color; + + .sidebar-wrapper { + position: fixed; + top: 0; + left: 0; + height: 100%; + border-right: 1px solid $border-color; + } +} + +.sidebar-wrapper { + z-index: 99; + background: $background-color; +} + +.content-wrapper { + width: 100%; + padding: 15px; + background: #FFF; +} + +.nav-sidebar { + margin: 0; + list-style: none; + + &.navbar-collapse { + padding: 0px !important; + } +} + +.nav-sidebar li a .count { + float: right; + background: #eee; + padding: 0px 8px; + @include border-radius(6px); +} + +.nav-sidebar li { + &.active a { + color: $text-color; + background: #FFF !important; + font-weight: bold; + border: 1px solid #EEE; + border-right: 1px solid transparent; + border-left: 3px solid $style_color; + + &.no-highlight { + background: none !important; + border: none; + } + + i { + color: $text-color; + } + } +} + +.nav-sidebar li { + &.separate-item { + border-top: 1px solid $border-color; + padding-top: 10px; + margin-top: 10px; + } + + a { + color: $gray; + display: block; + text-decoration: none; + padding: 8px 15px; + font-size: 13px; + line-height: 20px; + padding-left: 20px; + + &:hover { + text-decoration: none; + color: $text-color; + background: $border-color; + } + + &:active, &:focus { + text-decoration: none; + } + + i { + width: 20px; + color: $gray-light; + margin-right: 23px; + } + } +} + +.sidebar-subnav { + margin-left: 0px; + padding-left: 0px; + + li { + list-style: none; + } +} + +@mixin expanded-sidebar { + padding-left: $sidebar_width; + + .sidebar-wrapper { + width: $sidebar_width; + + .nav-sidebar { + margin-top: 29px; + position: fixed; + top: 45px; + width: $sidebar_width; + } + } + + .content-wrapper { + padding: 20px; + } +} + +@mixin folded-sidebar { + padding-left: 50px; + + .sidebar-wrapper { + width: 52px; + + .nav-sidebar { + margin-top: 29px; + position: fixed; + top: 45px; + width: 52px; + + li a { + padding-left: 18px; + font-size: 14px; + padding: 8px 15px; + text-align: center; + + + & > span { + display: none; + } + } + } + + .collapse-nav a { + left: 0px; + padding: 7px 23px 3px 22px; + } + } +} + +.collapse-nav a { + position: fixed; + top: 46px; + padding: 5px 13px 5px 13px; + left: 198px; + font-size: 13px; + background: transparent; + color: black; + border-left: 1px solid $border-color; + border-bottom: 1px solid $border-color; +} + +.collapse-nav a:hover { + text-decoration: none; + background: #f2f6f7; +} + +@media (max-width: $screen-md-max) { + .page-sidebar-collapsed { + @include folded-sidebar; + } + + .page-sidebar-expanded { + @include folded-sidebar; + } + + .collapse-nav { + display: none; + } +} + +@media(min-width: $screen-md-max) { + .page-sidebar-collapsed { + @include folded-sidebar; + } + + .page-sidebar-expanded { + @include expanded-sidebar; + } +} diff --git a/app/assets/stylesheets/generic/selects.scss b/app/assets/stylesheets/generic/selects.scss index e0f508d269..d8e0dc028d 100644 --- a/app/assets/stylesheets/generic/selects.scss +++ b/app/assets/stylesheets/generic/selects.scss @@ -2,25 +2,29 @@ .select2-container, .select2-container.select2-drop-above { .select2-choice { background: #FFF; - border-color: #BBB; - padding: 6px 12px; - font-size: 13px; - line-height: 18px; - height: auto; + border-color: #DDD; + height: 34px; + padding: 6px 14px; + font-size: 14px; + line-height: 1.42857143; + + @include border-radius(4px); .select2-arrow { background: #FFF; - border-left: 1px solid #DDD; + border-left: none; + padding-top: 3px; } } } .select2-container-multi .select2-choices { - @include border-radius(4px) + @include border-radius(4px); + border-color: #CCC; } .select2-container-multi .select2-choices .select2-search-field input { - padding: 6px 12px; + padding: 8px 14px; font-size: 13px; line-height: 18px; height: auto; @@ -29,6 +33,7 @@ .select2-drop-active { border: 1px solid #BBB !important; margin-top: 4px; + font-size: 13px; &.select2-drop-above { margin-bottom: 8px; @@ -42,60 +47,18 @@ .select2-results { max-height: 350px; .select2-highlighted { - background: $bg_primary; + background: $gl-primary; } } } -select { - &.select2 { - width: 100px; - } - - &.select2-sm { - width: 100px; - } +.select2-container { + width: 100% !important; } -@media (min-width: $screen-sm-min) { - select { - &.select2 { - width: 150px; - } - &.select2-sm { - width: 120px; - } - } -} - -/* Medium devices (desktops, 992px and up) */ -@media (min-width: $screen-md-min) { - select { - &.select2 { - width: 170px; - } - &.select2-sm { - width: 140px; - } - } -} - -/* Large devices (large desktops, 1200px and up) */ -@media (min-width: $screen-lg-min) { - select { - &.select2 { - width: 200px; - } - &.select2-sm { - width: 150px; - } - } -} - - /** Branch/tag selector **/ .project-refs-form .select2-container { - margin-right: 10px; + width: 160px !important; } .ajax-users-dropdown, .ajax-project-users-dropdown { @@ -116,6 +79,18 @@ select { } } +.group-result { + .group-image { + float: left; + } + .group-name { + font-weight: bold; + } + .group-path { + color: #999; + } +} + .user-result { .user-image { float: left; @@ -137,3 +112,7 @@ select { font-weight: bolder; } } + +.ajax-users-dropdown { + min-width: 225px !important; +} diff --git a/app/assets/stylesheets/generic/sidebar.scss b/app/assets/stylesheets/generic/sidebar.scss deleted file mode 100644 index f6311ef74e..0000000000 --- a/app/assets/stylesheets/generic/sidebar.scss +++ /dev/null @@ -1,46 +0,0 @@ -.ui.sidebar { - z-index: 1000 !important; - background: #fff; - padding: 10px; - width: 285px; -} - -.ui.right.sidebar { - border-left: 1px solid #e1e1e1; - border-right: 0; -} - -.sidebar-expand-button { - cursor: pointer; - transition: all 0.4s; - -moz-transition: all 0.4s; - -webkit-transition: all 0.4s; -} - -.fixed.sidebar-expand-button { - background: #f9f9f9; - color: #555; - padding: 9px 12px 6px 14px; - border: 1px solid #E1E1E1; - border-right: 0; - position: fixed; - top: 108px; - right: 0px; - margin-right: 0; - &:hover { - background: #ddd; - color: #333; - padding-right: 25px; - } -} - -.btn.btn-default.sidebar-expand-button { - margin-left: 12px; - display: inline-block !important; -} - -@media (min-width: 767px) { -.btn.btn-default.sidebar-expand-button { - display: none!important; - } -} diff --git a/app/assets/stylesheets/generic/tables.scss b/app/assets/stylesheets/generic/tables.scss new file mode 100644 index 0000000000..a66e45577d --- /dev/null +++ b/app/assets/stylesheets/generic/tables.scss @@ -0,0 +1,20 @@ +table { + &.table { + tr { + td, th { + padding: 8px 10px; + line-height: 20px; + vertical-align: middle; + } + th { + font-weight: normal; + font-size: 15px; + border-bottom: 1px solid $border-color !important; + } + td { + border-color: #F1F1F1 !important; + border-bottom: 1px solid; + } + } + } +} diff --git a/app/assets/stylesheets/generic/timeline.scss b/app/assets/stylesheets/generic/timeline.scss new file mode 100644 index 0000000000..97831eb7c2 --- /dev/null +++ b/app/assets/stylesheets/generic/timeline.scss @@ -0,0 +1,134 @@ +.timeline { + list-style: none; + padding: 20px 0 20px; + position: relative; + + &:before { + top: 0; + bottom: 0; + position: absolute; + content: " "; + width: 3px; + background-color: #eeeeee; + margin-left: 29px; + } + + .timeline-entry { + position: relative; + margin-top: 5px; + margin-left: 30px; + margin-bottom: 10px; + clear: both; + + + &:target { + .timeline-entry-inner .timeline-content { + -webkit-animation:target-note 2s linear; + background: $hover; + } + } + + .timeline-entry-inner { + position: relative; + margin-left: -20px; + + &:before, &:after { + content: " "; + display: table; + } + + .timeline-icon { + margin-top: 2px; + background: #fff; + color: #737881; + float: left; + @include border-radius($avatar_radius); + @include box-shadow(0 0 0 3px #EEE); + overflow: hidden; + + .avatar { + margin: 0; + padding: 0; + } + } + + .timeline-content { + position: relative; + background: $background-color; + padding: 10px 15px; + margin-left: 60px; + + img { + max-width: 100%; + } + + &:after { + content: ''; + display: block; + position: absolute; + width: 0; + height: 0; + border-style: solid; + border-width: 9px 9px 9px 0; + border-color: transparent $background-color transparent transparent; + left: 0; + top: 10px; + margin-left: -9px; + } + } + } + } + + .system-note .timeline-entry-inner { + .timeline-icon { + background: none; + margin-left: 12px; + margin-top: 0; + @include box-shadow(none); + + span { + margin: 0 2px; + font-size: 16px; + color: #eeeeee; + } + } + + .timeline-content { + background: none; + margin-left: 45px; + padding: 0px 15px; + + &:after { border: 0; } + + .note-header { + span { font-size: 12px; } + + .avatar { + margin-right: 5px; + } + } + + .note-text { + font-size: 12px; + margin-left: 20px; + } + } + } +} + +@media (max-width: $screen-xs-max) { + .timeline { + &:before { + background: none; + } + .timeline-entry .timeline-entry-inner { + .timeline-icon { + display: none; + } + + .timeline-content { + margin-left: 0; + } + } + } +} diff --git a/app/assets/stylesheets/generic/typography.scss b/app/assets/stylesheets/generic/typography.scss index 9aa819d40f..80190424c1 100644 --- a/app/assets/stylesheets/generic/typography.scss +++ b/app/assets/stylesheets/generic/typography.scss @@ -2,24 +2,11 @@ * Headers * */ -h1.page-title { - @include page-title; - font-size: 28px; -} - -h2.page-title { - @include page-title; - font-size: 24px; -} - -h3.page-title { - @include page-title; - font-size: 22px; -} - -h6 { - color: #888; - text-transform: uppercase; +.page-title { + margin-top: 0px; + line-height: 1.5; + font-weight: normal; + margin-bottom: 5px; } /** CODE **/ @@ -28,56 +15,10 @@ pre { &.dark { background: #333; - color: #f5f5f5; + color: $background-color; } } -/** - * Links - * - */ -a { - outline: none; - color: $link_color; - &:hover { - text-decoration: none; - color: $link_hover_color; - } - - &:focus { - text-decoration: underline; - } - - &.darken { - color: $style_color; - } - - &.lined { - text-decoration: underline; - &:hover { text-decoration: underline; } - } - - &.gray { - color: gray; - } - - &.supp_diff_link { - text-align: center; - padding: 20px 0; - background: #f1f1f1; - width: 100%; - float: left; - } - - &.neib { - margin-right: 15px; - } -} - -a:focus { - outline: none; -} - .monospace { font-family: $monospace_font; } @@ -89,6 +30,8 @@ a:focus { .wiki { @include md-typography; + word-wrap: break-word; + /* Link to current header. */ h1, h2, h3, h4, h5, h6 { position: relative; @@ -126,3 +69,7 @@ a:focus { textarea.js-gfm-input { font-family: $monospace_font; } + +.strikethrough { + text-decoration: line-through; +} diff --git a/app/assets/stylesheets/generic/zen.scss b/app/assets/stylesheets/generic/zen.scss new file mode 100644 index 0000000000..26afc21a6a --- /dev/null +++ b/app/assets/stylesheets/generic/zen.scss @@ -0,0 +1,98 @@ +.zennable { + position: relative; + + input { + display: none; + } + + .zen-enter-link { + color: #888; + position: absolute; + top: -26px; + right: 4px; + } + + .zen-leave-link { + display: none; + color: #888; + position: absolute; + top: 10px; + right: 10px; + padding: 5px; + font-size: 36px; + + &:hover { + color: #111; + } + } + + input:checked ~ .zen-backdrop .zen-enter-link { + display: none; + } + + input:checked ~ .zen-backdrop .zen-leave-link { + display: block; + position: absolute; + top: 0; + } + + input:checked ~ .zen-backdrop { + background-color: white; + position: fixed; + top: 0; + bottom: 0; + left: 0; + right: 0; + z-index: 1031; + + textarea { + border: none; + box-shadow: none; + border-radius: 0; + color: #000; + font-size: 20px; + line-height: 26px; + padding: 30px; + display: block; + outline: none; + resize: none; + height: 100vh; + max-width: 900px; + margin: 0 auto; + } + } + + .zen-backdrop textarea::-webkit-input-placeholder { + color: white; + } + + .zen-backdrop textarea:-moz-placeholder { + color: white; + } + + .zen-backdrop textarea::-moz-placeholder { + color: white; + } + + .zen-backdrop textarea:-ms-input-placeholder { + color: white; + } + + input:checked ~ .zen-backdrop textarea::-webkit-input-placeholder { + color: #999; + } + + input:checked ~ .zen-backdrop textarea:-moz-placeholder { + color: #999; + opacity: 1; + } + + input:checked ~ .zen-backdrop textarea::-moz-placeholder { + color: #999; + opacity: 1; + } + + input:checked ~ .zen-backdrop textarea:-ms-input-placeholder { + color: #999; + } +} diff --git a/app/assets/stylesheets/highlight/dark.scss b/app/assets/stylesheets/highlight/dark.scss index ca51da3fdd..c8cb18ec35 100644 --- a/app/assets/stylesheets/highlight/dark.scss +++ b/app/assets/stylesheets/highlight/dark.scss @@ -1,199 +1,88 @@ -.dark { - background-color: #232323; +/* https://github.com/MozMorris/tomorrow-pygments */ +pre.code.highlight.dark, +.code.dark { - .line.hll { - background: #558; - } - - .highlight{ - border-left: 1px solid #444; - } - - .no-highlight { - color: #DDD; - } + background-color: #1d1f21; + color: #c5c8c6; + pre.code, + .line-numbers, .line-numbers a { - color: #666; + background-color: #1d1f21 !important; + color: #c5c8c6 !important; } - pre { - background-color: #232323; + pre.code { + border-left: 1px solid #666; } - .hljs { - display: block; - background: #232323; - color: #E6E1DC; + // highlight line via anchor + pre .hll { + background-color: #557 !important; } - .hljs-comment, - .hljs-template_comment, - .hljs-javadoc, - .hljs-shebang { - color: #BC9458; - font-style: italic; - } - - .hljs-keyword, - .ruby .hljs-function .hljs-keyword, - .hljs-request, - .hljs-status, - .nginx .hljs-title, - .method, - .hljs-list .hljs-title { - color: #C26230; - } - - .hljs-string, - .hljs-number, - .hljs-regexp, - .hljs-tag .hljs-value, - .hljs-cdata, - .hljs-filter .hljs-argument, - .hljs-attr_selector, - .apache .hljs-cbracket, - .hljs-date, - .tex .hljs-command, - .markdown .hljs-link_label { - color: #A5C261; - } - - .hljs-subst { - color: #519F50; - } - - .hljs-tag, - .hljs-tag .hljs-keyword, - .hljs-tag .hljs-title, - .hljs-doctype, - .hljs-sub .hljs-identifier, - .hljs-pi, - .input_number { - color: #E8BF6A; - } - - .hljs-identifier { - color: #D0D0FF; - } - - .hljs-class .hljs-title, - .haskell .hljs-type, - .smalltalk .hljs-class, - .hljs-javadoctag, - .hljs-yardoctag, - .hljs-phpdoc { - text-decoration: none; - } - - .hljs-constant { - color: #DA4939; - } - - - .hljs-symbol, - .hljs-built_in, - .ruby .hljs-symbol .hljs-string, - .ruby .hljs-symbol .hljs-identifier, - .markdown .hljs-link_url, - .hljs-attribute { - color: #6D9CBE; - } - - .markdown .hljs-link_url { - text-decoration: underline; - } - - - - .hljs-params, - .hljs-variable, - .clojure .hljs-attribute { - color: #D0D0FF; - } - - .css .hljs-tag, - .hljs-rules .hljs-property, - .hljs-pseudo, - .tex .hljs-special { - color: #CDA869; - } - - .css .hljs-class { - color: #9B703F; - } - - .hljs-rules .hljs-keyword { - color: #C5AF75; - } - - .hljs-rules .hljs-value { - color: #CF6A4C; - } - - .css .hljs-id { - color: #8B98AB; - } - - .hljs-annotation, - .apache .hljs-sqbracket, - .nginx .hljs-built_in { - color: #9B859D; - } - - .hljs-preprocessor, - .hljs-preprocessor *, - .hljs-pragma { - color: #8996A8 !important; - } - - .hljs-hexcolor, - .css .hljs-value .hljs-number { - color: #A5C261; - } - - .hljs-title, - .hljs-decorator, - .css .hljs-function { - color: #FFC66D; - } - - .diff .hljs-header, - .hljs-chunk { - background-color: #2F33AB; - color: #E6E1DC; - display: inline-block; - width: 100%; - } - - .diff .hljs-change { - background-color: #4A410D; - color: #F8F8F8; - display: inline-block; - width: 100%; - } - - .hljs-addition { - background-color: #144212; - color: #E6E1DC; - display: inline-block; - width: 100%; - } - - .hljs-deletion { - background-color: #600; - color: #E6E1DC; - display: inline-block; - width: 100%; - } - - .coffeescript .javascript, - .javascript .xml, - .tex .hljs-formula, - .xml .javascript, - .xml .vbscript, - .xml .css, - .xml .hljs-cdata { - opacity: 0.7; - } + .hll { background-color: #373b41 } + .c { color: #969896 } /* Comment */ + .err { color: #cc6666 } /* Error */ + .k { color: #b294bb } /* Keyword */ + .l { color: #de935f } /* Literal */ + .n { color: #c5c8c6 } /* Name */ + .o { color: #8abeb7 } /* Operator */ + .p { color: #c5c8c6 } /* Punctuation */ + .cm { color: #969896 } /* Comment.Multiline */ + .cp { color: #969896 } /* Comment.Preproc */ + .c1 { color: #969896 } /* Comment.Single */ + .cs { color: #969896 } /* Comment.Special */ + .gd { color: #cc6666 } /* Generic.Deleted */ + .ge { font-style: italic } /* Generic.Emph */ + .gh { color: #c5c8c6; font-weight: bold } /* Generic.Heading */ + .gi { color: #b5bd68 } /* Generic.Inserted */ + .gp { color: #969896; font-weight: bold } /* Generic.Prompt */ + .gs { font-weight: bold } /* Generic.Strong */ + .gu { color: #8abeb7; font-weight: bold } /* Generic.Subheading */ + .kc { color: #b294bb } /* Keyword.Constant */ + .kd { color: #b294bb } /* Keyword.Declaration */ + .kn { color: #8abeb7 } /* Keyword.Namespace */ + .kp { color: #b294bb } /* Keyword.Pseudo */ + .kr { color: #b294bb } /* Keyword.Reserved */ + .kt { color: #f0c674 } /* Keyword.Type */ + .ld { color: #b5bd68 } /* Literal.Date */ + .m { color: #de935f } /* Literal.Number */ + .s { color: #b5bd68 } /* Literal.String */ + .na { color: #81a2be } /* Name.Attribute */ + .nb { color: #c5c8c6 } /* Name.Builtin */ + .nc { color: #f0c674 } /* Name.Class */ + .no { color: #cc6666 } /* Name.Constant */ + .nd { color: #8abeb7 } /* Name.Decorator */ + .ni { color: #c5c8c6 } /* Name.Entity */ + .ne { color: #cc6666 } /* Name.Exception */ + .nf { color: #81a2be } /* Name.Function */ + .nl { color: #c5c8c6 } /* Name.Label */ + .nn { color: #f0c674 } /* Name.Namespace */ + .nx { color: #81a2be } /* Name.Other */ + .py { color: #c5c8c6 } /* Name.Property */ + .nt { color: #8abeb7 } /* Name.Tag */ + .nv { color: #cc6666 } /* Name.Variable */ + .ow { color: #8abeb7 } /* Operator.Word */ + .w { color: #c5c8c6 } /* Text.Whitespace */ + .mf { color: #de935f } /* Literal.Number.Float */ + .mh { color: #de935f } /* Literal.Number.Hex */ + .mi { color: #de935f } /* Literal.Number.Integer */ + .mo { color: #de935f } /* Literal.Number.Oct */ + .sb { color: #b5bd68 } /* Literal.String.Backtick */ + .sc { color: #c5c8c6 } /* Literal.String.Char */ + .sd { color: #969896 } /* Literal.String.Doc */ + .s2 { color: #b5bd68 } /* Literal.String.Double */ + .se { color: #de935f } /* Literal.String.Escape */ + .sh { color: #b5bd68 } /* Literal.String.Heredoc */ + .si { color: #de935f } /* Literal.String.Interpol */ + .sx { color: #b5bd68 } /* Literal.String.Other */ + .sr { color: #b5bd68 } /* Literal.String.Regex */ + .s1 { color: #b5bd68 } /* Literal.String.Single */ + .ss { color: #b5bd68 } /* Literal.String.Symbol */ + .bp { color: #c5c8c6 } /* Name.Builtin.Pseudo */ + .vc { color: #cc6666 } /* Name.Variable.Class */ + .vg { color: #cc6666 } /* Name.Variable.Global */ + .vi { color: #cc6666 } /* Name.Variable.Instance */ + .il { color: #de935f } /* Literal.Number.Integer.Long */ } diff --git a/app/assets/stylesheets/highlight/monokai.scss b/app/assets/stylesheets/highlight/monokai.scss index 36bc5df2f4..001e8b3102 100644 --- a/app/assets/stylesheets/highlight/monokai.scss +++ b/app/assets/stylesheets/highlight/monokai.scss @@ -1,148 +1,88 @@ -.monokai { - background-color: #272822; +/* https://github.com/richleland/pygments-css/blob/master/monokai.css */ +pre.code.monokai, +.code.monokai { - .highlight{ - border-left: 1px solid #444; - } - - .line.hll { - background: #558; - } - - .no-highlight { - color: #DDD; - } + background: #272822; + color: #f8f8f2; + pre.highlight, + .line-numbers, .line-numbers a { - color: #666; + background:#272822 !important; + color:#f8f8f2 !important; } - pre { - background-color: #272822; - color: #f8f8f2; + pre.code { + border-left: 1px solid #555; } - .hljs { - display: block; - background: #272822; + // highlight line via anchor + pre .hll { + background-color: #49483e !important; } - .hljs-tag, - .hljs-tag .hljs-title, - .hljs-keyword, - .hljs-literal, - .hljs-strong, - .hljs-change, - .hljs-winutils, - .hljs-flow, - .lisp .hljs-title, - .clojure .hljs-built_in, - .nginx .hljs-title, - .tex .hljs-special { - color: #F92672; - } + .hll { background-color: #49483e } + .c { color: #75715e } /* Comment */ + .err { color: #960050; background-color: #1e0010 } /* Error */ + .k { color: #66d9ef } /* Keyword */ + .l { color: #ae81ff } /* Literal */ + .n { color: #f8f8f2 } /* Name */ + .o { color: #f92672 } /* Operator */ + .p { color: #f8f8f2 } /* Punctuation */ + .cm { color: #75715e } /* Comment.Multiline */ + .cp { color: #75715e } /* Comment.Preproc */ + .c1 { color: #75715e } /* Comment.Single */ + .cs { color: #75715e } /* Comment.Special */ + .ge { font-style: italic } /* Generic.Emph */ + .gs { font-weight: bold } /* Generic.Strong */ + .kc { color: #66d9ef } /* Keyword.Constant */ + .kd { color: #66d9ef } /* Keyword.Declaration */ + .kn { color: #f92672 } /* Keyword.Namespace */ + .kp { color: #66d9ef } /* Keyword.Pseudo */ + .kr { color: #66d9ef } /* Keyword.Reserved */ + .kt { color: #66d9ef } /* Keyword.Type */ + .ld { color: #e6db74 } /* Literal.Date */ + .m { color: #ae81ff } /* Literal.Number */ + .s { color: #e6db74 } /* Literal.String */ + .na { color: #a6e22e } /* Name.Attribute */ + .nb { color: #f8f8f2 } /* Name.Builtin */ + .nc { color: #a6e22e } /* Name.Class */ + .no { color: #66d9ef } /* Name.Constant */ + .nd { color: #a6e22e } /* Name.Decorator */ + .ni { color: #f8f8f2 } /* Name.Entity */ + .ne { color: #a6e22e } /* Name.Exception */ + .nf { color: #a6e22e } /* Name.Function */ + .nl { color: #f8f8f2 } /* Name.Label */ + .nn { color: #f8f8f2 } /* Name.Namespace */ + .nx { color: #a6e22e } /* Name.Other */ + .py { color: #f8f8f2 } /* Name.Property */ + .nt { color: #f92672 } /* Name.Tag */ + .nv { color: #f8f8f2 } /* Name.Variable */ + .ow { color: #f92672 } /* Operator.Word */ + .w { color: #f8f8f2 } /* Text.Whitespace */ + .mf { color: #ae81ff } /* Literal.Number.Float */ + .mh { color: #ae81ff } /* Literal.Number.Hex */ + .mi { color: #ae81ff } /* Literal.Number.Integer */ + .mo { color: #ae81ff } /* Literal.Number.Oct */ + .sb { color: #e6db74 } /* Literal.String.Backtick */ + .sc { color: #e6db74 } /* Literal.String.Char */ + .sd { color: #e6db74 } /* Literal.String.Doc */ + .s2 { color: #e6db74 } /* Literal.String.Double */ + .se { color: #ae81ff } /* Literal.String.Escape */ + .sh { color: #e6db74 } /* Literal.String.Heredoc */ + .si { color: #e6db74 } /* Literal.String.Interpol */ + .sx { color: #e6db74 } /* Literal.String.Other */ + .sr { color: #e6db74 } /* Literal.String.Regex */ + .s1 { color: #e6db74 } /* Literal.String.Single */ + .ss { color: #e6db74 } /* Literal.String.Symbol */ + .bp { color: #f8f8f2 } /* Name.Builtin.Pseudo */ + .vc { color: #f8f8f2 } /* Name.Variable.Class */ + .vg { color: #f8f8f2 } /* Name.Variable.Global */ + .vi { color: #f8f8f2 } /* Name.Variable.Instance */ + .il { color: #ae81ff } /* Literal.Number.Integer.Long */ - .hljs { - color: #DDD; - } - - .hljs .hljs-constant, - .asciidoc .hljs-code { - color: #66D9EF; - } - - .hljs-code, - .hljs-class .hljs-title, - .hljs-header { - color: white; - } - - .hljs-link_label, - .hljs-attribute, - .hljs-symbol, - .hljs-symbol .hljs-string, - .hljs-value, - .hljs-regexp { - color: #BF79DB; - } - - .hljs-link_url, - .hljs-tag .hljs-value, - .hljs-string, - .hljs-bullet, - .hljs-subst, - .hljs-title, - .hljs-emphasis, - .haskell .hljs-type, - .hljs-preprocessor, - .hljs-pragma, - .ruby .hljs-class .hljs-parent, - .hljs-built_in, - .sql .hljs-aggregate, - .django .hljs-template_tag, - .django .hljs-variable, - .smalltalk .hljs-class, - .hljs-javadoc, - .django .hljs-filter .hljs-argument, - .smalltalk .hljs-localvars, - .smalltalk .hljs-array, - .hljs-attr_selector, - .hljs-pseudo, - .hljs-addition, - .hljs-stream, - .hljs-envvar, - .apache .hljs-tag, - .apache .hljs-cbracket, - .tex .hljs-command, - .hljs-prompt { - color: #A6E22E; - } - - .hljs-comment, - .java .hljs-annotation, - .smartquote, - .hljs-blockquote, - .hljs-horizontal_rule, - .python .hljs-decorator, - .hljs-template_comment, - .hljs-pi, - .hljs-doctype, - .hljs-deletion, - .hljs-shebang, - .apache .hljs-sqbracket, - .tex .hljs-formula { - color: #75715E; - } - - .hljs-keyword, - .hljs-literal, - .css .hljs-id, - .hljs-phpdoc, - .hljs-title, - .hljs-header, - .haskell .hljs-type, - .vbscript .hljs-built_in, - .sql .hljs-aggregate, - .rsl .hljs-built_in, - .smalltalk .hljs-class, - .diff .hljs-header, - .hljs-chunk, - .hljs-winutils, - .bash .hljs-variable, - .apache .hljs-tag, - .tex .hljs-special, - .hljs-request, - .hljs-status { - font-weight: bold; - } - - .coffeescript .javascript, - .javascript .xml, - .tex .hljs-formula, - .xml .javascript, - .xml .vbscript, - .xml .css, - .xml .hljs-cdata { - opacity: 0.5; - } + .gh { } /* Generic Heading & Diff Header */ + .gu { color: #75715e; } /* Generic.Subheading & Diff Unified/Comment? */ + .gd { color: #f92672; } /* Generic.Deleted & Diff Deleted */ + .gi { color: #a6e22e; } /* Generic.Inserted & Diff Inserted */ } diff --git a/app/assets/stylesheets/highlight/solarized_dark.scss b/app/assets/stylesheets/highlight/solarized_dark.scss index b9bec22518..f5b827e7c0 100644 --- a/app/assets/stylesheets/highlight/solarized_dark.scss +++ b/app/assets/stylesheets/highlight/solarized_dark.scss @@ -1,125 +1,110 @@ -.solarized-dark { - background-color: #002B36; +/* https://gist.github.com/qguv/7936275 */ +pre.code.highlight.solarized-dark, +.code.solarized-dark { - .highlight{ + background-color: #002b36; + color: #93a1a1; + + pre.code, + .line-numbers, + .line-numbers a { + background-color: #002b36 !important; + color: #93a1a1 !important; + } + + pre.code { border-left: 1px solid #113b46; } - .line.hll { - background: #000; + // highlight line via anchor + pre .hll { + background-color: #174652 !important; } - .no-highlight { - color: #DDD; - } + /* Solarized Dark - pre { - background-color: #002B36; - color: #eee; - } + For use with Jekyll and Pygments - .line-numbers a { - color: #666; - } + http://ethanschoonover.com/solarized - .hljs { - display: block; - background: #002b36; - color: #839496; - } + SOLARIZED HEX ROLE + --------- -------- ------------------------------------------ + base03 #002b36 background + base01 #586e75 comments / secondary content + base1 #93a1a1 body text / default code / primary content + orange #cb4b16 constants + red #dc322f regex, special keywords + blue #268bd2 reserved keywords + cyan #2aa198 strings, numbers + green #859900 operators, other keywords + */ - .hljs-comment, - .hljs-template_comment, - .diff .hljs-header, - .hljs-doctype, - .hljs-pi, - .lisp .hljs-string, - .hljs-javadoc { - color: #586e75; - } - - /* Solarized Green */ - .hljs-keyword, - .hljs-winutils, - .method, - .hljs-addition, - .css .hljs-tag, - .hljs-request, - .hljs-status, - .nginx .hljs-title { - color: #859900; - } - - /* Solarized Cyan */ - .hljs-number, - .hljs-command, - .hljs-string, - .hljs-tag .hljs-value, - .hljs-rules .hljs-value, - .hljs-phpdoc, - .tex .hljs-formula, - .hljs-regexp, - .hljs-hexcolor, - .hljs-link_url { - color: #2aa198; - } - - /* Solarized Blue */ - .hljs-title, - .hljs-localvars, - .hljs-chunk, - .hljs-decorator, - .hljs-built_in, - .hljs-identifier, - .vhdl .hljs-literal, - .hljs-id, - .css .hljs-function { - color: #268bd2; - } - - /* Solarized Yellow */ - .hljs-attribute, - .hljs-variable, - .lisp .hljs-body, - .smalltalk .hljs-number, - .hljs-constant, - .hljs-class .hljs-title, - .hljs-parent, - .haskell .hljs-type, - .hljs-link_reference { - color: #b58900; - } - - /* Solarized Orange */ - .hljs-preprocessor, - .hljs-preprocessor .hljs-keyword, - .hljs-pragma, - .hljs-shebang, - .hljs-symbol, - .hljs-symbol .hljs-string, - .diff .hljs-change, - .hljs-special, - .hljs-attr_selector, - .hljs-subst, - .hljs-cdata, - .clojure .hljs-title, - .css .hljs-pseudo, - .hljs-header { - color: #cb4b16; - } - - /* Solarized Red */ - .hljs-deletion, - .hljs-important { - color: #dc322f; - } - - /* Solarized Violet */ - .hljs-link_label { - color: #6c71c4; - } - - .tex .hljs-formula { - background: #073642; - } + .c { color: #586e75 } /* Comment */ + .err { color: #93a1a1 } /* Error */ + .g { color: #93a1a1 } /* Generic */ + .k { color: #859900 } /* Keyword */ + .l { color: #93a1a1 } /* Literal */ + .n { color: #93a1a1 } /* Name */ + .o { color: #859900 } /* Operator */ + .x { color: #cb4b16 } /* Other */ + .p { color: #93a1a1 } /* Punctuation */ + .cm { color: #586e75 } /* Comment.Multiline */ + .cp { color: #859900 } /* Comment.Preproc */ + .c1 { color: #586e75 } /* Comment.Single */ + .cs { color: #859900 } /* Comment.Special */ + .gd { color: #2aa198 } /* Generic.Deleted */ + .ge { color: #93a1a1; font-style: italic } /* Generic.Emph */ + .gr { color: #dc322f } /* Generic.Error */ + .gh { color: #cb4b16 } /* Generic.Heading */ + .gi { color: #859900 } /* Generic.Inserted */ + .go { color: #93a1a1 } /* Generic.Output */ + .gp { color: #93a1a1 } /* Generic.Prompt */ + .gs { color: #93a1a1; font-weight: bold } /* Generic.Strong */ + .gu { color: #cb4b16 } /* Generic.Subheading */ + .gt { color: #93a1a1 } /* Generic.Traceback */ + .kc { color: #cb4b16 } /* Keyword.Constant */ + .kd { color: #268bd2 } /* Keyword.Declaration */ + .kn { color: #859900 } /* Keyword.Namespace */ + .kp { color: #859900 } /* Keyword.Pseudo */ + .kr { color: #268bd2 } /* Keyword.Reserved */ + .kt { color: #dc322f } /* Keyword.Type */ + .ld { color: #93a1a1 } /* Literal.Date */ + .m { color: #2aa198 } /* Literal.Number */ + .s { color: #2aa198 } /* Literal.String */ + .na { color: #93a1a1 } /* Name.Attribute */ + .nb { color: #B58900 } /* Name.Builtin */ + .nc { color: #268bd2 } /* Name.Class */ + .no { color: #cb4b16 } /* Name.Constant */ + .nd { color: #268bd2 } /* Name.Decorator */ + .ni { color: #cb4b16 } /* Name.Entity */ + .ne { color: #cb4b16 } /* Name.Exception */ + .nf { color: #268bd2 } /* Name.Function */ + .nl { color: #93a1a1 } /* Name.Label */ + .nn { color: #93a1a1 } /* Name.Namespace */ + .nx { color: #93a1a1 } /* Name.Other */ + .py { color: #93a1a1 } /* Name.Property */ + .nt { color: #268bd2 } /* Name.Tag */ + .nv { color: #268bd2 } /* Name.Variable */ + .ow { color: #859900 } /* Operator.Word */ + .w { color: #93a1a1 } /* Text.Whitespace */ + .mf { color: #2aa198 } /* Literal.Number.Float */ + .mh { color: #2aa198 } /* Literal.Number.Hex */ + .mi { color: #2aa198 } /* Literal.Number.Integer */ + .mo { color: #2aa198 } /* Literal.Number.Oct */ + .sb { color: #586e75 } /* Literal.String.Backtick */ + .sc { color: #2aa198 } /* Literal.String.Char */ + .sd { color: #93a1a1 } /* Literal.String.Doc */ + .s2 { color: #2aa198 } /* Literal.String.Double */ + .se { color: #cb4b16 } /* Literal.String.Escape */ + .sh { color: #93a1a1 } /* Literal.String.Heredoc */ + .si { color: #2aa198 } /* Literal.String.Interpol */ + .sx { color: #2aa198 } /* Literal.String.Other */ + .sr { color: #dc322f } /* Literal.String.Regex */ + .s1 { color: #2aa198 } /* Literal.String.Single */ + .ss { color: #2aa198 } /* Literal.String.Symbol */ + .bp { color: #268bd2 } /* Name.Builtin.Pseudo */ + .vc { color: #268bd2 } /* Name.Variable.Class */ + .vg { color: #268bd2 } /* Name.Variable.Global */ + .vi { color: #268bd2 } /* Name.Variable.Instance */ + .il { color: #2aa198 } /* Literal.Number.Integer.Long */ } diff --git a/app/assets/stylesheets/highlight/solarized_light.scss b/app/assets/stylesheets/highlight/solarized_light.scss new file mode 100644 index 0000000000..6b44c00c30 --- /dev/null +++ b/app/assets/stylesheets/highlight/solarized_light.scss @@ -0,0 +1,110 @@ +/* https://gist.github.com/qguv/7936275 */ +pre.code.highlight.solarized-light, +.code.solarized-light { + + background-color: #fdf6e3; + color: #586e75; + + pre.code, + .line-numbers, + .line-numbers a { + background-color: #fdf6e3 !important; + color: #586e75 !important; + } + + pre.code { + border-left: 1px solid #c5d0d4; + } + + // highlight line via anchor + pre .hll { + background-color: #ddd8c5 !important; + } + + /* Solarized Light + + For use with Jekyll and Pygments + + http://ethanschoonover.com/solarized + + SOLARIZED HEX ROLE + --------- -------- ------------------------------------------ + base01 #586e75 body text / default code / primary content + base1 #93a1a1 comments / secondary content + base3 #fdf6e3 background + orange #cb4b16 constants + red #dc322f regex, special keywords + blue #268bd2 reserved keywords + cyan #2aa198 strings, numbers + green #859900 operators, other keywords + */ + + .c { color: #93a1a1 } /* Comment */ + .err { color: #586e75 } /* Error */ + .g { color: #586e75 } /* Generic */ + .k { color: #859900 } /* Keyword */ + .l { color: #586e75 } /* Literal */ + .n { color: #586e75 } /* Name */ + .o { color: #859900 } /* Operator */ + .x { color: #cb4b16 } /* Other */ + .p { color: #586e75 } /* Punctuation */ + .cm { color: #93a1a1 } /* Comment.Multiline */ + .cp { color: #859900 } /* Comment.Preproc */ + .c1 { color: #93a1a1 } /* Comment.Single */ + .cs { color: #859900 } /* Comment.Special */ + .gd { color: #2aa198 } /* Generic.Deleted */ + .ge { color: #586e75; font-style: italic } /* Generic.Emph */ + .gr { color: #dc322f } /* Generic.Error */ + .gh { color: #cb4b16 } /* Generic.Heading */ + .gi { color: #859900 } /* Generic.Inserted */ + .go { color: #586e75 } /* Generic.Output */ + .gp { color: #586e75 } /* Generic.Prompt */ + .gs { color: #586e75; font-weight: bold } /* Generic.Strong */ + .gu { color: #cb4b16 } /* Generic.Subheading */ + .gt { color: #586e75 } /* Generic.Traceback */ + .kc { color: #cb4b16 } /* Keyword.Constant */ + .kd { color: #268bd2 } /* Keyword.Declaration */ + .kn { color: #859900 } /* Keyword.Namespace */ + .kp { color: #859900 } /* Keyword.Pseudo */ + .kr { color: #268bd2 } /* Keyword.Reserved */ + .kt { color: #dc322f } /* Keyword.Type */ + .ld { color: #586e75 } /* Literal.Date */ + .m { color: #2aa198 } /* Literal.Number */ + .s { color: #2aa198 } /* Literal.String */ + .na { color: #586e75 } /* Name.Attribute */ + .nb { color: #B58900 } /* Name.Builtin */ + .nc { color: #268bd2 } /* Name.Class */ + .no { color: #cb4b16 } /* Name.Constant */ + .nd { color: #268bd2 } /* Name.Decorator */ + .ni { color: #cb4b16 } /* Name.Entity */ + .ne { color: #cb4b16 } /* Name.Exception */ + .nf { color: #268bd2 } /* Name.Function */ + .nl { color: #586e75 } /* Name.Label */ + .nn { color: #586e75 } /* Name.Namespace */ + .nx { color: #586e75 } /* Name.Other */ + .py { color: #586e75 } /* Name.Property */ + .nt { color: #268bd2 } /* Name.Tag */ + .nv { color: #268bd2 } /* Name.Variable */ + .ow { color: #859900 } /* Operator.Word */ + .w { color: #586e75 } /* Text.Whitespace */ + .mf { color: #2aa198 } /* Literal.Number.Float */ + .mh { color: #2aa198 } /* Literal.Number.Hex */ + .mi { color: #2aa198 } /* Literal.Number.Integer */ + .mo { color: #2aa198 } /* Literal.Number.Oct */ + .sb { color: #93a1a1 } /* Literal.String.Backtick */ + .sc { color: #2aa198 } /* Literal.String.Char */ + .sd { color: #586e75 } /* Literal.String.Doc */ + .s2 { color: #2aa198 } /* Literal.String.Double */ + .se { color: #cb4b16 } /* Literal.String.Escape */ + .sh { color: #586e75 } /* Literal.String.Heredoc */ + .si { color: #2aa198 } /* Literal.String.Interpol */ + .sx { color: #2aa198 } /* Literal.String.Other */ + .sr { color: #dc322f } /* Literal.String.Regex */ + .s1 { color: #2aa198 } /* Literal.String.Single */ + .ss { color: #2aa198 } /* Literal.String.Symbol */ + .bp { color: #268bd2 } /* Name.Builtin.Pseudo */ + .vc { color: #268bd2 } /* Name.Variable.Class */ + .vg { color: #268bd2 } /* Name.Variable.Global */ + .vi { color: #268bd2 } /* Name.Variable.Instance */ + .il { color: #2aa198 } /* Literal.Number.Integer.Long */ +} diff --git a/app/assets/stylesheets/highlight/white.scss b/app/assets/stylesheets/highlight/white.scss index c06bed3c21..a52ffc971d 100644 --- a/app/assets/stylesheets/highlight/white.scss +++ b/app/assets/stylesheets/highlight/white.scss @@ -1,186 +1,87 @@ -.white { +/* https://github.com/aahan/pygments-github-style */ +pre.code.highlight.white, +.code.white { + background-color: #fff; + color: #333; - .line.hll { - background: #FFA; - } - - .highlight{ - border-left: 1px solid #eee; - } - - pre { - background-color: #fff; - color: #333; - } - - .hljs { - background: #FFF; - } - + pre.highlight, + .line-numbers, .line-numbers a { - color: #999; + background-color: #fff !important; + color: #333 !important; } - .hljs { - display: block; - background: #fff; color: black; + pre.code { + border-left: 1px solid #bbb; } - .hljs-comment, - .hljs-template_comment, - .hljs-javadoc, - .hljs-comment * { - color: #006a00; + // highlight line via anchor + pre .hll { + background-color: #f8eec7 !important; } - .hljs-keyword, - .hljs-literal, - .nginx .hljs-title { - color: #aa0d91; - } - .method, - .hljs-list .hljs-title, - .hljs-tag .hljs-title, - .setting .hljs-value, - .hljs-winutils, - .tex .hljs-command, - .http .hljs-title, - .hljs-request, - .hljs-status { - color: #008; - } - - .hljs-envvar, - .tex .hljs-special { - color: #660; - } - - .hljs-string { - color: #c41a16; - } - .hljs-tag .hljs-value, - .hljs-cdata, - .hljs-filter .hljs-argument, - .hljs-attr_selector, - .apache .hljs-cbracket, - .hljs-date, - .hljs-regexp { - color: #080; - } - - .hljs-sub .hljs-identifier, - .hljs-pi, - .hljs-tag, - .hljs-tag .hljs-keyword, - .hljs-decorator, - .ini .hljs-title, - .hljs-shebang, - .hljs-prompt, - .hljs-hexcolor, - .hljs-rules .hljs-value, - .hljs-symbol, - .hljs-symbol .hljs-string, - .hljs-number, - .css .hljs-function, - .clojure .hljs-title, - .clojure .hljs-built_in, - .hljs-function .hljs-title, - .coffeescript .hljs-attribute { - color: #1c00cf; - } - - .hljs-class .hljs-title, - .haskell .hljs-type, - .smalltalk .hljs-class, - .hljs-javadoctag, - .hljs-yardoctag, - .hljs-phpdoc, - .hljs-typename, - .hljs-tag .hljs-attribute, - .hljs-doctype, - .hljs-class .hljs-id, - .hljs-built_in, - .setting, - .hljs-params, - .clojure .hljs-attribute { - color: #5c2699; - } - - .hljs-variable { - color: #3f6e74; - } - .css .hljs-tag, - .hljs-rules .hljs-property, - .hljs-pseudo, - .hljs-subst { - color: #000; - } - - .css .hljs-class, - .css .hljs-id { - color: #9B703F; - } - - .hljs-value .hljs-important { - color: #ff7700; - font-weight: bold; - } - - .hljs-rules .hljs-keyword { - color: #C5AF75; - } - - .hljs-annotation, - .apache .hljs-sqbracket, - .nginx .hljs-built_in { - color: #9B859D; - } - - .hljs-preprocessor, - .hljs-preprocessor *, - .hljs-pragma { - color: #643820; - } - - .tex .hljs-formula { - background-color: #EEE; - font-style: italic; - } - - .diff .hljs-header, - .hljs-chunk { - color: #808080; - font-weight: bold; - } - - .diff .hljs-change { - background-color: #BCCFF9; - } - - .hljs-addition { - background-color: #BAEEBA; - } - - .hljs-deletion { - background-color: #FFC8BD; - } - - .hljs-comment .hljs-yardoctag { - font-weight: bold; - } - - .method .hljs-id { - color: #000; - } -} - -.shadow { - @include box-shadow(0 5px 15px #000); -} - -.wiki, .note-body { - .highlight { - border: 1px solid #DDD; - } + .hll { background-color: #f8f8f8 } + .c { color: #999988; font-style: italic; } + .err { color: #a61717; background-color: #e3d2d2; } + .k { font-weight: bold; } + .o { font-weight: bold; } + .cm { color: #999988; font-style: italic; } + .cp { color: #999999; font-weight: bold; } + .c1 { color: #999988; font-style: italic; } + .cs { color: #999999; font-weight: bold; font-style: italic; } + .gd { color: #000000; background-color: #ffdddd; } + .gd .x { color: #000000; background-color: #ffaaaa; } + .ge { font-style: italic; } + .gr { color: #aa0000; } + .gh { color: #999999; } + .gi { color: #000000; background-color: #ddffdd; } + .gi .x { color: #000000; background-color: #aaffaa; } + .go { color: #888888; } + .gp { color: #555555; } + .gs { font-weight: bold; } + .gu { color: #800080; font-weight: bold; } + .gt { color: #aa0000; } + .kc { font-weight: bold; } + .kd { font-weight: bold; } + .kn { font-weight: bold; } + .kp { font-weight: bold; } + .kr { font-weight: bold; } + .kt { color: #445588; font-weight: bold; } + .m { color: #009999; } + .s { color: #dd1144; } + .n { color: #333333; } + .na { color: teal; } + .nb { color: #0086b3; } + .nc { color: #445588; font-weight: bold; } + .no { color: teal; } + .ni { color: purple; } + .ne { color: #990000; font-weight: bold; } + .nf { color: #990000; font-weight: bold; } + .nn { color: #555555; } + .nt { color: navy; } + .nv { color: teal; } + .ow { font-weight: bold; } + .w { color: #bbbbbb; } + .mf { color: #009999; } + .mh { color: #009999; } + .mi { color: #009999; } + .mo { color: #009999; } + .sb { color: #dd1144; } + .sc { color: #dd1144; } + .sd { color: #dd1144; } + .s2 { color: #dd1144; } + .se { color: #dd1144; } + .sh { color: #dd1144; } + .si { color: #dd1144; } + .sx { color: #dd1144; } + .sr { color: #009926; } + .s1 { color: #dd1144; } + .ss { color: #990073; } + .bp { color: #999999; } + .vc { color: teal; } + .vg { color: teal; } + .vi { color: teal; } + .il { color: #009999; } + .gc { color: #999; background-color: #EAF2F5; } } diff --git a/app/assets/stylesheets/main/fonts.scss b/app/assets/stylesheets/main/fonts.scss deleted file mode 100644 index d90274a0db..0000000000 --- a/app/assets/stylesheets/main/fonts.scss +++ /dev/null @@ -1,3 +0,0 @@ -/** Typo **/ -$monospace_font: 'Menlo', 'Liberation Mono', 'Consolas', 'Courier New', 'andale mono', 'lucida console', monospace; -$regular_font: "Helvetica Neue", Helvetica, Arial, sans-serif; diff --git a/app/assets/stylesheets/main/variables.scss b/app/assets/stylesheets/main/variables.scss deleted file mode 100644 index 02ce2c8338..0000000000 --- a/app/assets/stylesheets/main/variables.scss +++ /dev/null @@ -1,48 +0,0 @@ -/* - * General Colors - */ -$style_color: #474D57; -$hover: #D9EDF7; - -/* - * Link colors - */ -$link_color: #446e9b; -$link_hover_color: #2FA0BB; - -$btn-border: 1px solid #ccc; - -/* - * Success colors (green) - */ -$border_success: #019875; -$bg_success: #019875; - -/* - * Danger colors (red) - */ -$border_danger: #d43f3a; -$bg_danger: #d9534f; - -/* - * Primary colors (blue) - */ -$border_primary: #446e9b; -$bg_primary: #446e9b; - -/* - * Warning colors (yellow) - */ -$bg_warning: #EB9532; -$border_warning: #EB9532; - -/** - * Commit Diff Colors - */ -$added: #63c363; -$deleted: #f77; - -/** - * - */ -$nprogress-color: #3498db; diff --git a/app/assets/stylesheets/sections/admin.scss b/app/assets/stylesheets/pages/admin.scss similarity index 78% rename from app/assets/stylesheets/sections/admin.scss rename to app/assets/stylesheets/pages/admin.scss index a51deee797..144852e787 100644 --- a/app/assets/stylesheets/sections/admin.scss +++ b/app/assets/stylesheets/pages/admin.scss @@ -50,3 +50,14 @@ line-height: 2; } } + +.broadcast-message { + @extend .alert-warning; + padding: 10px; + text-align: center; +} + +.broadcast-message-preview { + @extend .broadcast-message; + margin-bottom: 20px; +} diff --git a/app/assets/stylesheets/pages/commit.scss b/app/assets/stylesheets/pages/commit.scss new file mode 100644 index 0000000000..e7125c0399 --- /dev/null +++ b/app/assets/stylesheets/pages/commit.scss @@ -0,0 +1,123 @@ +.commit-title{ + display: block; +} + +.commit-title{ + margin-bottom: 10px; +} + +.commit-author, .commit-committer{ + display: block; + color: #999; + font-weight: normal; + font-style: italic; +} + +.commit-author strong, .commit-committer strong{ + font-weight: bold; + font-style: normal; +} + +.commit-description { + background: none; + border: none; + margin: 0; + padding: 0; + margin-top: 10px; +} + +.commit-stat-summary { + color: #666; + font-size: 14px; + font-weight: normal; + padding: 3px 0; + margin-bottom: 10px; +} + +.commit-info-row { + margin-bottom: 10px; + .avatar { + @extend .avatar-inline; + } + .commit-committer-link, + .commit-author-link { + color: #444; + font-weight: bold; + } +} + +.commit-box { + margin: 10px 0; + border-top: 1px solid #ddd; + border-bottom: 1px solid #ddd; + padding: 20px 0; + + .commit-title { + margin: 0; + } + + .commit-description { + margin-top: 15px; + } +} + +.file-stats a { + color: $style_color; +} + +.file-stats { + .new-file { + a { + color: #090; + } + i { + color: #1BCF00; + } + } + .renamed-file { + i { + color: #FE9300; + } + } + .deleted-file { + a { + color: #B00; + } + i { + color: #EE0000; + } + } + .edit-file{ + i{ + color: #555; + } + } +} + +/* + * Commit message textarea for web editor and + * custom merge request message + */ +.commit-message-container { + background-color: $body-bg; + position: relative; + font-family: $monospace_font; + $left: 12px; + .max-width-marker { + width: 72ch; + color: rgba(0, 0, 0, 0.0); + font-family: inherit; + left: $left; + height: 100%; + border-right: 1px solid mix($input-border, white); + position: absolute; + z-index: 1; + } + > textarea { + background-color: rgba(0, 0, 0, 0.0); + font-family: inherit; + padding-left: $left; + position: relative; + z-index: 2; + } +} diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss new file mode 100644 index 0000000000..84361e1548 --- /dev/null +++ b/app/assets/stylesheets/pages/commits.scss @@ -0,0 +1,118 @@ +.commits-compare-switch{ + @extend .btn; + background: image-url("switch_icon.png") no-repeat center center; + text-indent: -9999px; + float: left; + margin-right: 9px; +} + +.lists-separator { + margin: 10px 0; + border-color: #DDD; +} + +.commits-row { + ul { + margin: 0; + + li.commit { + padding: 8px 0; + } + } + + .commits-row-date { + font-size: 15px; + line-height: 20px; + margin-bottom: 5px; + } +} + +.commits-feed-holder { + float: right; + + .btn { + padding: 4px 12px; + } +} + +li.commit { + .commit-row-title { + font-size: $list-font-size; + line-height: 20px; + margin-bottom: 2px; + + .notes_count { + float: right; + margin-right: 10px; + } + + .commit_short_id { + min-width: 65px; + font-family: $monospace_font; + } + + .str-truncated { + max-width: 70%; + } + + .commit-row-message { + color: #444; + + &:hover { + text-decoration: underline; + } + } + + .text-expander { + background: #eee; + color: #555; + padding: 0 5px; + cursor: pointer; + margin-left: 4px; + &:hover { + background-color: #ddd; + } + } + } + + .commit-row-description { + font-size: 14px; + border-left: 1px solid #EEE; + padding: 10px 15px; + margin: 5px 0 10px 5px; + background: #f9f9f9; + display: none; + + pre { + border: none; + background: inherit; + padding: 0; + margin: 0; + } + } + + .commit-row-info { + color: #777; + line-height: 24px; + font-size: 13px; + + a { + color: #777; + } + + .committed_ago { + display: inline-block; + } + } + + &.inline-commit { + .commit-row-title { + font-size: 13px; + } + + .committed_ago { + float: right; + @extend .cgray; + } + } +} diff --git a/app/assets/stylesheets/sections/dashboard.scss b/app/assets/stylesheets/pages/dashboard.scss similarity index 57% rename from app/assets/stylesheets/sections/dashboard.scss rename to app/assets/stylesheets/pages/dashboard.scss index 2be24e9b13..af9c83e5dc 100644 --- a/app/assets/stylesheets/sections/dashboard.scss +++ b/app/assets/stylesheets/pages/dashboard.scss @@ -23,49 +23,18 @@ } } -.dashboard { - .dash-filter { - width: 205px; - float: left; - height: inherit; - } -} - -@media (max-width: 1200px) { - .dashboard .dash-filter { - width: 140px; - } -} - -.dash-sidebar-tabs { - margin-bottom: 2px; - border: none; - margin: 0 !important; - - li { - &.active { - a { - background-color: #EEE; - border-bottom: 1px solid #EEE !important; - &:hover { - background: #eee; - } - } - } - - a { - border-color: #DDD !important; - } - } -} - .project-row, .group-row { - padding: 8px 15px !important; + padding: 0 !important; font-size: 14px; line-height: 24px; + .str-truncated { + max-width: 72%; + } + a { display: block; + padding: 8px 15px; } .project-name, .group-name { @@ -97,21 +66,28 @@ margin-left: 10px; float: left; margin-right: 15px; - font-size: 20px; margin-bottom: 15px; - border: 1px solid #EEE; - padding: 8px 12px; - border-radius: 50px; - background: #f5f5f5; - text-align: center; i { - color: #BBB; + color: #888; + } +} + +.dash-project-avatar { + float: left; + + .avatar { + margin-top: -8px; + margin-left: -15px; + @include border-radius(0px); + } + .identicon { + line-height: 40px; } } .dash-project-access-icon { float: left; - margin-right: 3px; + margin-right: 5px; width: 16px; } diff --git a/app/assets/stylesheets/sections/diff.scss b/app/assets/stylesheets/pages/diff.scss similarity index 84% rename from app/assets/stylesheets/sections/diff.scss rename to app/assets/stylesheets/pages/diff.scss index 488d06919b..af6ea58382 100644 --- a/app/assets/stylesheets/sections/diff.scss +++ b/app/assets/stylesheets/pages/diff.scss @@ -1,24 +1,37 @@ .diff-file { - border: 1px solid #CCC; + border: 1px solid $border-color; margin-bottom: 1em; .diff-header { - @extend .clearfix; - background: #EEE; - border-bottom: 1px solid #CCC; - padding: 5px 5px 5px 10px; + position: relative; + background: $background-color; + border-bottom: 1px solid $border-color; + padding: 10px 15px; color: #555; + z-index: 10; > span { font-family: $monospace_font; - line-height: 2; + word-break: break-all; + margin-right: 200px; + display: block; + + .file-mode { + margin-left: 10px; + color: #777; + } } .diff-btn-group { float: right; + position: absolute; + top: 5px; + right: 15px; .btn { - background-color: #FFF; + padding: 0px 10px; + font-size: 13px; + line-height: 28px; } } @@ -26,26 +39,21 @@ font-family: $monospace_font; font-size: smaller; } - - .file-mode { - font-family: $monospace_font; - margin-left: 10px; - } } .diff-content { overflow: auto; overflow-y: hidden; background: #FFF; color: #333; - font-size: 12px; + font-size: $code_font_size; .old { span.idiff { - background-color: #F99; + background-color: #f8cbcb; } } .new { span.idiff { - background-color: #8F8; + background-color: #a6f3a6; } } .unfold { @@ -64,8 +72,8 @@ margin: 0px; padding: 0px; td { - line-height: 18px; - font-size: 12px; + line-height: $code_line_height; + font-size: $code_font_size; } } @@ -83,10 +91,10 @@ margin: 0px; padding: 0px; border: none; - background: #F5F5F5; - color: #666; + background: $background-color; + color: rgba(0,0,0,0.3); padding: 0px 5px; - border-right: 1px solid #ccc; + border-right: 1px solid $border-color; text-align: right; min-width: 35px; max-width: 50px; @@ -96,7 +104,7 @@ float: left; width: 35px; font-weight: normal; - color: #666; + color: rgba(0,0,0,0.3); &:hover { text-decoration: underline; } @@ -114,30 +122,28 @@ .line_holder { &.old .old_line, &.old .new_line { - background: #FCC; - border-color: #E7BABA; + background: #ffdddd; + border-color: #f1c0c0; } &.new .old_line, &.new .new_line { - background: #CFC; - border-color: #B9ECB9; + background: #dbffdb; + border-color: #c1e9c1; } } .line_content { display: block; - white-space: pre; - height: 18px; margin: 0px; padding: 0px 0.5em; border: none; &.new { - background: #CFD; + background: #eaffea; } &.old { - background: #FDD; + background: #ffecec; } &.matched { - color: #ccc; + color: $border-color; background: #fafafa; } &.parallel { @@ -341,3 +347,12 @@ margin: 0; border: none; } + +.diff-file .line_content { + white-space: pre; +} + +.diff-wrap-lines .line_content { + white-space: pre-wrap; +} + diff --git a/app/assets/stylesheets/sections/editor.scss b/app/assets/stylesheets/pages/editor.scss similarity index 55% rename from app/assets/stylesheets/sections/editor.scss rename to app/assets/stylesheets/pages/editor.scss index f62f46ee16..759ba6b1c2 100644 --- a/app/assets/stylesheets/sections/editor.scss +++ b/app/assets/stylesheets/pages/editor.scss @@ -16,8 +16,6 @@ } } .commit-button-annotation { - @extend .alert; - @extend .alert-info; display: inline-block; margin: 0; padding: 2px; @@ -31,4 +29,26 @@ margin: 5px 8px 0 8px; } } + + .file-title { + @extend .monospace; + font-size: 14px; + padding: 5px; + } + + .editor-ref { + background: $background-color; + padding: 11px 15px; + border-right: 1px solid #CCC; + display: inline-block; + margin: -5px -5px; + margin-right: 10px; + } + + .editor-file-name { + .new-file-name { + display: inline-block; + width: 200px; + } + } } diff --git a/app/assets/stylesheets/sections/errors.scss b/app/assets/stylesheets/pages/errors.scss similarity index 100% rename from app/assets/stylesheets/sections/errors.scss rename to app/assets/stylesheets/pages/errors.scss diff --git a/app/assets/stylesheets/sections/events.scss b/app/assets/stylesheets/pages/events.scss similarity index 70% rename from app/assets/stylesheets/sections/events.scss rename to app/assets/stylesheets/pages/events.scss index 656aa5b18a..d4af7506d5 100644 --- a/app/assets/stylesheets/sections/events.scss +++ b/app/assets/stylesheets/pages/events.scss @@ -45,35 +45,39 @@ padding: 12px 0px; border-bottom: 1px solid #eee; .event-title { - @include str-truncated(72%); - color: #333; - font-weight: normal; + max-width: 70%; + @include str-truncated(calc(100% - 174px)); + font-weight: 500; font-size: 14px; .author_name { color: #333; } } .event-body { + font-size: 13px; margin-left: 35px; - margin-right: 100px; + margin-right: 80px; + color: #777; - .event-info { - color: #666; - } .event-note { - color: #666; margin-top: 5px; + word-wrap: break-word; .md { font-size: 13px; + + iframe.twitter-share-button { + vertical-align: bottom; + } } pre { border: none; background: #f9f9f9; border-radius: 0; - color: #666; + color: #777; margin: 0 20px; + overflow: hidden; } .note-image-attach { @@ -120,7 +124,6 @@ padding: 3px; padding-left: 0; border: none; - color: #666; .commit-row-title { font-size: 12px; } @@ -144,49 +147,51 @@ } } -/** - * Event filter - * - */ -.event_filter { - position: absolute; - width: 40px; - margin-left: -55px; - - .filter_icon { - a { - text-align:center; - background: $bg_primary; - margin-bottom: 10px; - float: left; - padding: 9px 6px; - font-size: 18px; - width: 40px; - color: #FFF; - @include border-radius(3px); - } - - &.inactive { - a { - color: #DDD; - background: #f9f9f9; - } - } - } -} /* * Last push widget */ .event-last-push { + overflow: auto; .event-last-push-text { - @include str-truncated(75%); - line-height: 24px; + @include str-truncated(100%); + padding: 5px 0; + font-size: 13px; + float:left; + margin-right: -150px; + padding-right: 150px; + line-height: 20px; } } @media (max-width: $screen-xs-max) { - .event-item .event-title { - @include str-truncated(65%); + .event-item { + .event-title { + white-space: normal; + overflow: visible; + max-width: 100%; + } + .avatar { + display: none; + } + + .event-body { + margin: 0; + border-left: 2px solid #DDD; + padding-left: 10px; + } + + .event-item-timestamp { + display: none; + } + } +} + +.event_filter { + li a { + font-size: 13px; + padding: 5px 10px; + background: $background-color; + margin-left: 4px; } } diff --git a/app/assets/stylesheets/sections/explore.scss b/app/assets/stylesheets/pages/explore.scss similarity index 100% rename from app/assets/stylesheets/sections/explore.scss rename to app/assets/stylesheets/pages/explore.scss diff --git a/app/assets/stylesheets/sections/graph.scss b/app/assets/stylesheets/pages/graph.scss similarity index 84% rename from app/assets/stylesheets/sections/graph.scss rename to app/assets/stylesheets/pages/graph.scss index 3d878d1e52..c3b10d144e 100644 --- a/app/assets/stylesheets/sections/graph.scss +++ b/app/assets/stylesheets/pages/graph.scss @@ -1,11 +1,11 @@ .project-network { - border: 1px solid #CCC; + border: 1px solid $border-color; .controls { color: #888; font-size: 14px; padding: 5px; - border-bottom: 1px solid #bbb; + border-bottom: 1px solid $border-color; background: #EEE; } diff --git a/app/assets/stylesheets/sections/groups.scss b/app/assets/stylesheets/pages/groups.scss similarity index 87% rename from app/assets/stylesheets/sections/groups.scss rename to app/assets/stylesheets/pages/groups.scss index e49fe1a9dd..2b1b747139 100644 --- a/app/assets/stylesheets/sections/groups.scss +++ b/app/assets/stylesheets/pages/groups.scss @@ -1,6 +1,5 @@ .new-group-member-holder { margin-top: 50px; - background: #f9f9f9; padding-top: 20px; } diff --git a/app/assets/stylesheets/sections/header.scss b/app/assets/stylesheets/pages/header.scss similarity index 60% rename from app/assets/stylesheets/sections/header.scss rename to app/assets/stylesheets/pages/header.scss index e0e0d60c38..dde19b801f 100644 --- a/app/assets/stylesheets/sections/header.scss +++ b/app/assets/stylesheets/pages/header.scss @@ -4,9 +4,11 @@ */ header { &.navbar-gitlab { + z-index: 100; margin-bottom: 0; min-height: 40px; border: none; + width: 100%; .navbar-inner { filter: none; @@ -29,7 +31,7 @@ header { .navbar-toggle { color: $style_color; - margin: 0 -15px 0 0; + margin: 0; padding: 10px; border-radius: 0; @@ -52,13 +54,12 @@ header { border-width: 0; font-size: 18px; - .app_logo { margin-left: -15px; } - .title { @include str-truncated(70%); } .navbar-collapse { + margin-top: 47px; padding-right: 0; padding-left: 0; } @@ -83,7 +84,10 @@ header { } } - z-index: 10; + .container { + width: 100% !important; + padding: 0px; + } /** * @@ -96,18 +100,14 @@ header { a { float: left; - padding: 0px; - margin: 0 6px; + padding: 5px 0; + height: 46px; + width: 52px; + text-align: center; - h1 { - margin: 0; - background: image-url('logo-black.png') no-repeat center center; - background-size: 32px; - float: left; - height: 46px; - width: 40px; - @include header-font; - text-indent: -9999px; + img { + width: 36px; + height: 36px; } } &:hover { @@ -130,13 +130,13 @@ header { } .profile-pic { - position: relative; - top: -1px; - padding-right: 0px !important; + padding: 0px !important; + width: 46px; + height: 46px; + margin-left: 5px; img { - width: 26px; - height: 26px; - @include border-radius(4px); + width: 46px; + height: 46px; } } @@ -169,83 +169,6 @@ header { @include transition(all 0.15s ease-in 0s); } } - - - /* - * Dark header - * - */ - &.header-dark { - &.navbar-gitlab { - .navbar-inner { - background: #708090; - border-bottom: 1px solid #AAA; - - .navbar-toggle { color: #fff; } - - .nav > li > a { - color: #AAA; - - &:hover, &:focus, &:active { - background: none; - color: #FFF; - } - } - } - } - - .turbolink-spinner { - color: #FFF; - } - - .search { - .search-input { - background-color: #D2D5DA; - background-color: rgba(255, 255, 255, 0.5); - border: 1px solid #AAA; - - &:focus { - background-color: white; - } - } - } - .search-input::-webkit-input-placeholder { - color: #666; - } - .app_logo { - a { - h1 { - background: image-url('logo-white.png') no-repeat center center; - background-size: 32px; - color: #fff; - } - } - } - .title { - a { - color: #FFF; - &:hover { - text-decoration: underline; - } - } - color: #fff; - } - } - - .app_logo { - .separator { - margin-left: 0; - margin-right: 0; - } - } - - .separator { - float: left; - height: 46px; - width: 2px; - margin-left: 10px; - margin-right: 10px; - } } .search .search-input { diff --git a/app/assets/stylesheets/pages/help.scss b/app/assets/stylesheets/pages/help.scss new file mode 100644 index 0000000000..6da7a2511a --- /dev/null +++ b/app/assets/stylesheets/pages/help.scss @@ -0,0 +1,70 @@ +.documentation-index { + h1 { + margin: 0; + } + + h2 { + font-size: 20px; + } + + li { + line-height: 24px; + color: #888; + + a { + margin-right: 3px; + } + } +} + + +.shortcut-mappings { + font-size: 12px; + color: #555; + + tbody:first-child tr:first-child { + padding-top: 0 + } + + th { + padding-top: 15px; + line-height: 1.5; + color: #333; + text-align: left + } + + td { + padding-top: 3px; + padding-bottom: 3px; + vertical-align: top; + line-height: 20px + } + + .shortcut { + padding-right: 10px; + color: #999; + text-align: right; + white-space: nowrap + } + + .key { + @extend .label; + @extend .label-inverse; + font: 11px Consolas, "Liberation Mono", Menlo, Courier, monospace; + padding: 3px 5px; + } +} + +.modal-body { + position: relative; + overflow-y: auto; + padding: 15px; +} + +body.modal-open { + overflow: hidden; +} + +.modal .modal-dialog { + width: 860px; +} diff --git a/app/assets/stylesheets/pages/import.scss b/app/assets/stylesheets/pages/import.scss new file mode 100644 index 0000000000..3df4bb84bd --- /dev/null +++ b/app/assets/stylesheets/pages/import.scss @@ -0,0 +1,18 @@ +i.icon-gitorious { + display: inline-block; + background-position: 0px 0px; + background-size: contain; + background-repeat: no-repeat; +} + +i.icon-gitorious-small { + background-image: image-url('gitorious-logo-blue.png'); + width: 13px; + height: 13px; +} + +i.icon-gitorious-big { + background-image: image-url('gitorious-logo-black.png'); + width: 18px; + height: 18px; +} diff --git a/app/assets/stylesheets/pages/issuable.scss b/app/assets/stylesheets/pages/issuable.scss new file mode 100644 index 0000000000..a640a4e205 --- /dev/null +++ b/app/assets/stylesheets/pages/issuable.scss @@ -0,0 +1,47 @@ +@media (max-width: $screen-sm-max) { + .issuable-affix { + margin-top: 20px; + } +} + +@media (max-width: $screen-md-max) { + .issuable-affix { + position: static; + } +} + +@media (min-width: $screen-md-max) { + .issuable-affix { + &.affix-top { + position: static; + } + + &.affix { + position: fixed; + top: 70px; + width: 220px; + } + } +} + +.issuable-context-title { + font-size: 14px; + line-height: 1.4; + margin-bottom: 5px; + + .avatar { + margin-left: 0; + } + + label { + color: #666; + font-weight: normal; + margin-right: 4px; + } +} + +.issuable-affix .context { + font-size: 13px; + + .btn { font-size: 13px; } +} diff --git a/app/assets/stylesheets/sections/issues.scss b/app/assets/stylesheets/pages/issues.scss similarity index 66% rename from app/assets/stylesheets/sections/issues.scss rename to app/assets/stylesheets/pages/issues.scss index a7fa715d2e..cd86a9be8b 100644 --- a/app/assets/stylesheets/sections/issues.scss +++ b/app/assets/stylesheets/pages/issues.scss @@ -5,11 +5,13 @@ .issue-title { margin-bottom: 5px; - font-size: 14px; + font-size: $list-font-size; + font-weight: bold; } .issue-info { color: #999; + font-size: 13px; } .issue-check { @@ -39,12 +41,9 @@ } .check-all-holder { - height: 32px; + line-height: 36px; float: left; - margin-right: 12px; - padding: 6px 15px; - border: 1px solid #ccc; - @include border-radius(4px); + margin-right: 15px; } .issues_content { @@ -57,31 +56,8 @@ } } -@media (min-width: 800px) { .issues_filters select { width: 160px; } } -@media (min-width: 1200px) { .issues_filters select { width: 220px; } } - -@media (min-width: 800px) { .issues_bulk_update .select2-container { min-width: 120px; } } -@media (min-width: 1200px) { .issues_bulk_update .select2-container { min-width: 160px; } } - -.issues_bulk_update { - .select2-container .select2-choice { - color: #444 !important; - font-weight: 500; - } -} - -#update_status { - width: 100px; -} - .participants { - margin-bottom: 10px; -} - -.issues_bulk_update { - .select2-container { - text-shadow: none; - } + margin-bottom: 20px; } .issue-search-form { @@ -94,8 +70,15 @@ } } -.issue-show-labels .color-label { - padding: 6px 10px; +.issue-show-labels { + a { + margin-right: 5px; + margin-bottom: 5px; + display: inline-block; + .color-label { + padding: 6px 10px; + } + } } form.edit-issue { @@ -110,12 +93,12 @@ form.edit-issue { } &.closed { - background: #F5f5f5; + background: #F9F9F9; border-color: #E5E5E5; } &.merged { - background: #F5f5f5; + background: #F9F9F9; border-color: #E5E5E5; } } @@ -151,4 +134,23 @@ form.edit-issue { } } } + + .issue { + &:hover .issue-actions { + display: none !important; + } + + .issue-updated-at { + display: none; + } + } +} + +h2.issue-title { + margin-top: 0; + font-weight: bold; +} + +.issue-form .select2-container { + width: 250px !important; } diff --git a/app/assets/stylesheets/sections/labels.scss b/app/assets/stylesheets/pages/labels.scss similarity index 100% rename from app/assets/stylesheets/sections/labels.scss rename to app/assets/stylesheets/pages/labels.scss diff --git a/app/assets/stylesheets/pages/login.scss b/app/assets/stylesheets/pages/login.scss new file mode 100644 index 0000000000..83b866c3a6 --- /dev/null +++ b/app/assets/stylesheets/pages/login.scss @@ -0,0 +1,124 @@ +/* Login Page */ +.login-page { + .container { + max-width: 960px; + } + + .navbar-gitlab .container { + max-width: none; + } + + .brand-holder { + font-size: 18px; + line-height: 1.5; + + p { + color: #888; + } + + h1:first-child { + font-weight: normal; + margin-bottom: 30px; + } + + img { + max-width: 100%; + margin-bottom: 30px; + } + + a { + font-weight: bold; + } + } + + .login-box{ + background: #fafafa; + border-radius: 10px; + box-shadow: 0 0px 2px #CCC; + padding: 15px; + + .login-heading h3 { + font-weight: 300; + line-height: 1.5; + margin: 0 0 10px 0; + } + + .login-footer { + margin-top: 10px; + + p:last-child { + margin-bottom: 0; + } + } + + a.forgot { + float: right; + padding-top: 6px + } + + .nav .active a { + background: transparent; + } + } + + .form-control { + font-size: 14px; + padding: 10px 8px; + width: 100%; + height: auto; + + &.top { + @include border-radius(5px 5px 0 0); + margin-bottom: 0px; + } + + &.bottom { + @include border-radius(0 0 5px 5px); + border-top: 0; + margin-bottom: 20px; + } + + &.middle { + border-top: 0; + margin-bottom:0px; + @include border-radius(0); + } + + &:active, &:focus { + background-color: #FFF; + } + } + + .devise-errors { + h2 { + margin-top: 0; + font-size: 14px; + color: #a00; + } + } + + .remember-me { + margin-top: -10px; + + label { + font-weight: normal; + } + } +} + +@media (max-width: $screen-xs-max) { + .login-page { + .col-sm-5.pull-right { + float: none !important; + } + } +} + +.oauth-image-link { + margin-right: 10px; + + img { + width: 32px; + height: 32px; + } +} diff --git a/app/assets/stylesheets/pages/merge_requests.scss b/app/assets/stylesheets/pages/merge_requests.scss new file mode 100644 index 0000000000..8abd4207be --- /dev/null +++ b/app/assets/stylesheets/pages/merge_requests.scss @@ -0,0 +1,191 @@ + + /** + * MR -> show: Automerge widget + * + */ +.automerge_widget { + form { + margin-bottom: 0; + .clearfix { + margin-bottom: 0; + } + } + + .accept-merge-holder { + .accept-action { + display: inline-block; + } + + .accept-control { + display: inline-block; + margin: 0; + margin-left: 20px; + padding: 10px 0; + line-height: 20px; + font-weight: bold; + + .remove_source_checkbox { + margin: 0; + font-weight: bold; + } + } + } +} + +@media(min-width: $screen-sm-max) { + .merge-request .merge-request-tabs{ + margin: 20px 0; + + li { + a { + padding: 15px 40px; + font-size: 14px; + } + } + } +} + +.mr_source_commit, +.mr_target_commit { + .commit { + margin: 0; + padding: 2px 0; + list-style: none; + &:hover { + background: none; + } + } +} + +.label-branch { + @include border-radius(4px); + padding: 3px 4px; + border: none; + background: $hover; + color: #333; + font-family: $monospace_font; + font-weight: normal; + overflow: hidden; + + .label-project { + @include border-radius-left(4px); + padding: 3px 4px; + background: #279; + position: relative; + left: -4px; + letter-spacing: -1px; + } +} + +.mr-list { + .merge-request { + padding: 10px 15px; + position: relative; + + .merge-request-title { + margin-bottom: 5px; + font-size: $list-font-size; + font-weight: bold; + } + + .merge-request-info { + color: #999; + font-size: 13px; + + .merge-request-labels { + display: inline-block; + } + } + } +} + +.merge-request-angle { + text-align: center; + margin: 0 auto; + font-size: 2em; + line-height: 1.1; +} + +.merge-request-form-info { + padding-top: 15px; +} + +// hide mr close link for inline diff comment form +.diff-file .close-mr-link, +.diff-file .reopen-mr-link { + display: none; +} + +.mr-state-widget { + font-size: 13px; + background: #F9F9F9; + margin-bottom: 20px; + color: #666; + border: 1px solid #EEE; + @include box-shadow(0 1px 1px rgba(0, 0, 0, 0.09)); + + .ci_widget { + padding: 10px 15px; + font-size: 15px; + border-bottom: 1px solid #BBB; + color: #777; + background-color: $background-color; + + &.ci-success { + color: $gl-success; + border-color: $gl-success; + background-color: #F1FAF1; + } + + &.ci-pending, + &.ci-running { + color: $gl-warning; + border-color: $gl-warning; + background-color: #FAF5F1; + } + + &.ci-failed, + &.ci-canceled, + &.ci-error { + color: $gl-danger; + border-color: $gl-danger; + background-color: #FAF1F1; + } + } + + .mr-widget-body { + padding: 10px 15px; + + h4 { + font-weight: normal; + } + + p:last-child { + margin-bottom: 0; + } + } + + .mr-widget-footer { + padding: 10px 15px; + border-top: 1px solid #EEE; + } + + .ci-coverage { + float: right; + } +} + +.merge-request-show-labels { + a { + margin-right: 5px; + margin-bottom: 5px; + display: inline-block; + .color-label { + padding: 6px 10px; + } + } +} + +.merge-request-form .select2-container { + width: 250px !important; +} diff --git a/app/assets/stylesheets/pages/milestone.scss b/app/assets/stylesheets/pages/milestone.scss new file mode 100644 index 0000000000..15e3948e40 --- /dev/null +++ b/app/assets/stylesheets/pages/milestone.scss @@ -0,0 +1,9 @@ +.issues-sortable-list .str-truncated { + max-width: 90%; +} + +li.milestone { + h4 { + font-weight: bold; + } +} diff --git a/app/assets/stylesheets/pages/note_form.scss b/app/assets/stylesheets/pages/note_form.scss new file mode 100644 index 0000000000..a052203078 --- /dev/null +++ b/app/assets/stylesheets/pages/note_form.scss @@ -0,0 +1,175 @@ +/** + * Note Form + */ + +.comment-btn { + @extend .btn-create; +} +.reply-btn { + @extend .btn-primary; +} +.diff-file .diff-content { + tr.line_holder:hover { + &> td.line_content { + background: $hover !important; + border-color: darken($hover, 10%) !important; + } + &> td.new_line, + &> td.old_line { + background: darken($hover, 4%) !important; + border-color: darken($hover, 10%) !important; + } + } + + tr.line_holder:hover > td .line_note_link { + opacity: 1.0; + filter: alpha(opacity=100); + } +} +.diff-file, +.discussion { + .new_note { + margin: 0; + border: none; + } +} +.new_note { + display: none; +} + +.new_note, .edit_note { + .buttons { + float: left; + margin-top: 8px; + } + .clearfix { + margin-bottom: 0; + } + + .note-preview-holder { + > p { + overflow-x: auto; + } + } + + img { + max-width: 100%; + } + + .note_text { + width: 100%; + } +} + +/* loading indicator */ +.notes-busy { + margin: 18px; +} + +.note-image-attach { + @extend .col-md-4; + @extend .thumbnail; + margin-left: 45px; + float: none; +} + +.common-note-form { + margin: 0; + background: #F9F9F9; + padding: 5px; + border: 1px solid #DDD; +} + +.note-form-actions { + background: #F9F9F9; + height: 45px; + + .note-form-option { + margin-top: 8px; + margin-left: 30px; + @extend .pull-left; + } + + .js-notify-commit-author { + float: left; + } + + .write-preview-btn { + // makes the "absolute" position for links relative to this + position: relative; + + // preview/edit buttons + > a { + position: absolute; + right: 5px; + top: 8px; + } + } +} + +.note-edit-form { + display: none; + font-size: 13px; + + .form-actions { + padding-left: 20px; + + .btn-save { + float: left; + } + + .note-form-option { + float: left; + padding: 2px 0 0 25px; + } + } +} + +.js-note-attachment-delete { + display: none; +} + +.parallel-comment { + padding: 6px; +} + +.error-alert > .alert { + margin-top: 5px; + margin-bottom: 5px; +} + +.discussion-body, +.diff-file { + .notes .note { + border-color: #ddd; + padding: 10px 15px; + } + + .discussion-reply-holder { + background: #f9f9f9; + padding: 10px 15px; + border-top: 1px solid #DDD; + } +} + +.discussion-notes-count { + font-size: 16px; +} + +.edit_note { + .markdown-area { + min-height: 140px; + } + .note-form-actions { + background: transparent; + } +} + +.comment-hints { + color: #999; + background: #FFF; + padding: 5px; + margin-top: -11px; + border: 1px solid #DDD; + font-size: 13px; +} diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss new file mode 100644 index 0000000000..facd7e1931 --- /dev/null +++ b/app/assets/stylesheets/pages/notes.scss @@ -0,0 +1,206 @@ +/** + * Notes + */ + +@-webkit-keyframes targe3-note { + from { background:#fffff0; } + 50% { background:#ffffd3; } + to { background:#fffff0; } +} + +ul.notes { + display: block; + list-style: none; + margin: 0px; + padding: 0px; + + .discussion-header, + .note-header { + @extend .cgray; + padding-bottom: 15px; + + a:hover { + text-decoration: none; + } + + .avatar { + float: left; + margin-right: 10px; + } + + .discussion-last-update, + .note-last-update { + &:before { + content: "\00b7"; + } + font-size: 13px; + } + .author { + color: #333; + font-weight: bold; + &:hover { + color: $gl-link-color; + } + } + .author-username { + } + } + + .discussion { + overflow: hidden; + display: block; + position:relative; + } + + .note { + display: block; + position:relative; + .note-body { + overflow: auto; + .note-text { + overflow: auto; + word-wrap: break-word; + @include md-typography; + + hr { + margin: 10px 0; + } + } + } + .note-header { + padding-bottom: 3px; + } + + &:last-child { + border-bottom: none; + } + } +} + +// Diff code in discussion view +.discussion-body .diff-file { + .diff-header > span { + margin-right: 10px; + } + .line_content { + white-space: pre-wrap; + } +} + +.diff-file .notes_holder { + font-size: 13px; + line-height: 18px; + font-family: $regular_font; + + td { + border: 1px solid #ddd; + border-left: none; + + &.notes_line { + text-align: center; + padding: 10px 0; + background: #FFF; + } + &.notes_line2 { + text-align: center; + padding: 10px 0; + border-left: 1px solid #ddd !important; + } + &.notes_content { + background-color: #fff; + border-width: 1px 0; + padding-top: 0; + vertical-align: top; + &.parallel{ + border-width: 1px; + } + } + } +} + +/** + * Actions for Discussions/Notes + */ + +.discussion, +.note { + &.note:hover { + .note-actions { display: block; } + } + .discussion-header:hover { + .discussion-actions { display: block; } + } + + .discussion-actions, + .note-actions { + display: none; + float: right; + + [class~="fa"] { + font-size: 16px; + line-height: 16px; + vertical-align: middle; + } + + a { + @extend .cgray; + + &:hover { + &.danger { @extend .cred; } + } + } + } +} +.diff-file .note .note-actions { + right: 0; + top: 0; +} + + +/** + * Line note button on the side of diffs + */ + +.diff-file tr.line_holder { + @mixin show-add-diff-note { + filter: alpha(opacity=100); + opacity: 1.0; + } + + .add-diff-note { + margin-top: -4px; + @include border-radius(40px); + background: #FFF; + padding: 4px; + font-size: 16px; + color: $gl-link-color; + margin-left: -60px; + position: absolute; + z-index: 10; + width: 32px; + + transition: all 0.2s ease; + + // "hide" it by default + opacity: 0.0; + filter: alpha(opacity=0); + + &:hover { + width: 38px; + font-size: 20px; + background: $gl-info; + color: #FFF; + @include show-add-diff-note; + } + } + + // "show" the icon also if we just hover somewhere over the line + &:hover > td { + background: $hover !important; + + .add-diff-note { + @include show-add-diff-note; + } + } +} + diff --git a/app/assets/stylesheets/sections/notifications.scss b/app/assets/stylesheets/pages/notifications.scss similarity index 75% rename from app/assets/stylesheets/sections/notifications.scss rename to app/assets/stylesheets/pages/notifications.scss index f11c5dff4a..cc273f5522 100644 --- a/app/assets/stylesheets/sections/notifications.scss +++ b/app/assets/stylesheets/pages/notifications.scss @@ -10,13 +10,13 @@ } .ns-part { - color: $bg_primary; + color: $gl-primary; } .ns-watch { - color: $bg_success; + color: $gl-success; } .ns-mute { - color: $bg_danger; + color: $gl-danger; } diff --git a/app/assets/stylesheets/sections/profile.scss b/app/assets/stylesheets/pages/profile.scss similarity index 69% rename from app/assets/stylesheets/sections/profile.scss rename to app/assets/stylesheets/pages/profile.scss index 086875582f..65655d4bfa 100644 --- a/app/assets/stylesheets/sections/profile.scss +++ b/app/assets/stylesheets/pages/profile.scss @@ -1,31 +1,7 @@ .account-page { fieldset { margin-bottom: 15px; - border-bottom: 1px dashed #ddd; padding-bottom: 15px; - - &:last-child { - border: none; - } - - legend { - border: none; - margin-bottom: 10px; - } - } -} - -.oauth_select_holder { - img { - padding: 2px; - margin-right: 10px; - } - .active { - img { - border: 1px solid #4BD; - background: $hover; - @include border-radius(5px); - } } } @@ -41,11 +17,6 @@ } } -.user-show-username { - font-weight: 200; - color: #666; -} - /* * Appearance settings * @@ -80,6 +51,10 @@ &.violet { background: #548; } + + &.blue { + background: #2980b9; + } } } } @@ -103,11 +78,18 @@ } } -.profile-groups-avatars { - margin: 0 5px 10px 0; +.oauth-buttons { + .btn-group { + margin-right: 10px; + } - img { - width: 50px; - height: 50px; + .btn { + line-height: 36px; + height: 56px; + + img { + width: 32px; + height: 32px; + } } } diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/pages/projects.scss similarity index 61% rename from app/assets/stylesheets/sections/projects.scss rename to app/assets/stylesheets/pages/projects.scss index 6e34f736ac..c005470355 100644 --- a/app/assets/stylesheets/sections/projects.scss +++ b/app/assets/stylesheets/pages/projects.scss @@ -15,60 +15,97 @@ } .project-home-panel { - border-bottom: 1px solid #DDD; - padding-bottom: 15px; - margin-bottom: 30px; + margin-bottom: 20px; + position: relative; + padding-left: 85px; &.empty-project { - border-bottom: 0px; - padding-bottom: 15px; - margin-bottom: 0px; + border-bottom: 0px; + padding-bottom: 15px; + margin-bottom: 0px; } - .project-home-title { - font-size: 18px; - color: #444; - margin: 0; - line-height: 32px; + .project-identicon-holder { + position: absolute; + left: 0; + + .avatar { + width: 70px; + height: 70px; + } + + .identicon { + font-size: 45px; + line-height: 1.6; + } + + .avatar, .identicon { + @include border-radius(4px); + box-shadow: 0 1px 2px #ddd; + } } + .project-home-dropdown { margin-left: 10px; float: right; } - .project-home-extra { - margin-top: 15px; + + .project-home-row { + @extend .clearfix; + margin-bottom: 15px; + + &.project-home-row-top { + margin-bottom: 15px; + } + + .project-home-desc { + font-size: 16px; + line-height: 1.3; + margin-right: 215px; + } .project-home-desc { float: left; - color: #777; - margin-bottom: 10px; - } - - .project-home-links { - float: right; - a { - margin-left: 10px; - font-weight: 500; - } + color: $gray; } } .visibility-level-label { - font-size: 17px; - background: #f1f1f1; - border-radius: 4px; - color: #444; - position: absolute; - margin-left: -55px; - text-shadow: 0 1px 1px #FFF; - width: 40px; - text-align: center; - padding: 6px; - + color: $gray; i { color: inherit; } } + + .project-repo-buttons { + margin-top: -3px; + position: absolute; + right: 0; + width: 260px; + text-align: right; + + .btn { + font-weight: bold; + font-size: 14px; + line-height: 16px; + + .count { + padding-left: 10px; + border-left: 1px solid #ccc; + display: inline-block; + margin-left: 10px; + } + } + } +} + +.project-home-links { + padding: 10px 0px; + float: right; + a { + margin-left: 10px; + font-weight: 500; + } } .git-clone-holder { @@ -76,29 +113,16 @@ margin-right: 45px; } - .btn, - .form-control { - border: 1px solid #E1E1E1; - box-shadow: none; - padding: 6px 9px; - } - - .btn { - background: none; - color: $link_color; - - &.active { - color: #333; - font-weight: bold; - } - } - .form-control { cursor: auto; @extend .monospace; background: #FAFAFA; width: 100%; } + + .input-group-addon { + background: #FAFAFA; + } } .project-visibility-level-holder { @@ -117,7 +141,7 @@ .option-descr { margin-left: 24px; - color: #666; + color: $gray; } } } @@ -150,7 +174,7 @@ ul.nav.nav-projects-tabs { } } -.team_member_row form { +.project_member_row form { margin: 0px; } @@ -159,6 +183,7 @@ ul.nav.nav-projects-tabs { li { .project-info { margin-bottom: 10px; + overflow: hidden; } .access-icon { @@ -195,8 +220,10 @@ ul.nav.nav-projects-tabs { white-space: normal; text-align: left; padding: 10px 15px; - background-color: #F1f1f1; - border-color: #EEE; + + &.dropdown-toggle { + text-align: center; + } &:hover { background-color: #eee; @@ -241,15 +268,15 @@ ul.nav.nav-projects-tabs { } .vs-public { - color: $bg_primary; + color: $gl-primary; } .vs-internal { - color: $bg_warning; + color: $gl-warning; } .vs-private { - color: $bg_success; + color: $gl-success; } .breadcrumb.repo-breadcrumb { @@ -263,3 +290,39 @@ ul.nav.nav-projects-tabs { color: #999; } } + +.fork-namespaces { + .thumbnail { + + &.fork-exists-thumbnail { + border-color: #EEE; + + .caption { + color: #999; + } + } + + &.fork-thumbnail { + border-color: #AAA; + + &:hover { + background-color: $hover; + } + } + + a { + text-decoration: none; + } + } +} + +table.table.protected-branches-list tr.no-border { + th, td { + border: 0; + } +} + +.project-import .btn { + float: left; + margin-right: 10px; +} diff --git a/app/assets/stylesheets/pages/search.scss b/app/assets/stylesheets/pages/search.scss new file mode 100644 index 0000000000..bdaa17ac33 --- /dev/null +++ b/app/assets/stylesheets/pages/search.scss @@ -0,0 +1,7 @@ +.search-results { + .search-result-row { + border-bottom: 1px solid #EEE; + padding-bottom: 10px; + margin-bottom: 10px; + } +} diff --git a/app/assets/stylesheets/sections/snippets.scss b/app/assets/stylesheets/pages/snippets.scss similarity index 100% rename from app/assets/stylesheets/sections/snippets.scss rename to app/assets/stylesheets/pages/snippets.scss diff --git a/app/assets/stylesheets/sections/stat_graph.scss b/app/assets/stylesheets/pages/stat_graph.scss similarity index 100% rename from app/assets/stylesheets/sections/stat_graph.scss rename to app/assets/stylesheets/pages/stat_graph.scss diff --git a/app/assets/stylesheets/sections/themes.scss b/app/assets/stylesheets/pages/themes.scss similarity index 100% rename from app/assets/stylesheets/sections/themes.scss rename to app/assets/stylesheets/pages/themes.scss diff --git a/app/assets/stylesheets/sections/tree.scss b/app/assets/stylesheets/pages/tree.scss similarity index 79% rename from app/assets/stylesheets/sections/tree.scss rename to app/assets/stylesheets/pages/tree.scss index 678a6cd716..57f63b52aa 100644 --- a/app/assets/stylesheets/sections/tree.scss +++ b/app/assets/stylesheets/pages/tree.scss @@ -17,19 +17,6 @@ @include border-radius(0); tr { - td, th { - padding: 8px 10px; - line-height: 20px; - } - th { - font-weight: normal; - font-size: 15px; - border-bottom: 1px solid #CCC !important; - } - td { - border-color: #F1F1F1 !important; - border-bottom: 1px solid; - } &:hover { td { background: $hover; @@ -40,7 +27,7 @@ } &.selected { td { - background: #f5f5f5; + background: $background-color; border-top: 1px solid #EEE; border-bottom: 1px solid #EEE; } @@ -52,14 +39,9 @@ .tree-item-file-name { max-width: 320px; vertical-align: middle; - a { - &:hover { - color: $link_hover_color; - } - } - i { - color: $bg_primary; + i, a { + color: $gl-link-color; } img { @@ -79,13 +61,18 @@ .tree_author { padding-right: 8px; + + .commit-author-name { + color: gray; + } } .tree_commit { color: gray; .tree-commit-link { - color: #444; + color: gray; + &:hover { text-decoration: underline; } @@ -111,6 +98,11 @@ background: #f1f1f1; border-left: 1px solid #DDD; } + td.lines { + code { + font-family: $monospace_font; + } + } } } @@ -120,7 +112,7 @@ .tree-ref-holder { float: left; - margin-right: 6px; + margin-right: 15px; .select2-container .select2-choice, .select2-container.select2-drop-above .select2-choice { padding: 4px 12px; @@ -128,13 +120,13 @@ } .readme-holder { - border-top: 1px dashed #CCC; - padding-top: 10px; - .readme-file-title { font-size: 14px; + font-weight: bold; margin-bottom: 20px; color: #777; + border-bottom: 1px solid #DDD; + padding: 10px 0; } } diff --git a/app/assets/stylesheets/pages/ui_dev_kit.scss b/app/assets/stylesheets/pages/ui_dev_kit.scss new file mode 100644 index 0000000000..277afa1db9 --- /dev/null +++ b/app/assets/stylesheets/pages/ui_dev_kit.scss @@ -0,0 +1,9 @@ +.gitlab-ui-dev-kit { + > h2 { + font-size: 27px; + border-bottom: 1px solid #CCC; + color: #666; + margin: 30px 0; + font-weight: bold; + } +} diff --git a/app/assets/stylesheets/pages/votes.scss b/app/assets/stylesheets/pages/votes.scss new file mode 100644 index 0000000000..dc9a7d71e8 --- /dev/null +++ b/app/assets/stylesheets/pages/votes.scss @@ -0,0 +1,4 @@ +.votes-inline { + display: inline-block; + margin: 0 8px; +} diff --git a/app/assets/stylesheets/sections/wiki.scss b/app/assets/stylesheets/pages/wiki.scss similarity index 100% rename from app/assets/stylesheets/sections/wiki.scss rename to app/assets/stylesheets/pages/wiki.scss diff --git a/app/assets/stylesheets/print.scss b/app/assets/stylesheets/print.scss index 42dbf4d6ef..1be0551ad3 100644 --- a/app/assets/stylesheets/print.scss +++ b/app/assets/stylesheets/print.scss @@ -11,3 +11,7 @@ header, nav, nav.main-nav, nav.navbar-collapse, nav.navbar-collapse.collapse {di .wiki h1 {font-size: 30px;} .wiki h2 {font-size: 22px;} .wiki h3 {font-size: 18px; font-weight: bold; } + +.sidebar-wrapper { display: none; } +.nav { display: none; } +.btn { display: none; } diff --git a/app/assets/stylesheets/sections/commits.scss b/app/assets/stylesheets/sections/commits.scss deleted file mode 100644 index 0083d01c46..0000000000 --- a/app/assets/stylesheets/sections/commits.scss +++ /dev/null @@ -1,250 +0,0 @@ -/** - * Commit file - */ -.commit-committer-link, -.commit-author-link { - font-size: 13px; - color: #555; - &:hover { - color: #999; - } -} - -/** COMMIT BLOCK **/ -.commit-title{ - display: block; -} -.commit-title{ - margin-bottom: 10px; -} -.commit-author, .commit-committer{ - display: block; - color: #999; - font-weight: normal; - font-style: italic; -} -.commit-author strong, .commit-committer strong{ - font-weight: bold; - font-style: normal; -} - - -.file-stats a { - color: $style_color; -} - -.file-stats { - .new-file { - a { - color: #090; - } - i { - color: #1BCF00; - } - } - .renamed-file { - i { - color: #FE9300; - } - } - .deleted-file { - a { - color: #B00; - } - i { - color: #EE0000; - } - } - .edit-file{ - i{ - color: #555; - } - } -} - -.label_commit { - @include border-radius(4px); - padding: 2px 4px; - font-size: 13px; - background: #474D57; - color: #fff; - font-family: $monospace_font; -} - - -.commits-compare-switch{ - background: image-url("switch_icon.png") no-repeat center center; - width: 32px; - height: 32px; - text-indent: -9999px; - float: left; - margin-right: 9px; - border: 1px solid #DDD; - @include border-radius(4px); - padding: 4px; - background-color: #EEE; -} - -.commit-description { - background: none; - border: none; - margin: 0; - padding: 0; - margin-top: 10px; -} - -.commit-box { - margin: 10px 0; - border-top: 1px solid #ddd; - border-bottom: 1px solid #ddd; - padding: 20px 0; - - .commit-title { - margin: 0; - font-size: 20px; - } - - .commit-description { - margin-top: 15px; - } -} - - -.commit-stat-summary { - color: #666; - font-size: 14px; - font-weight: normal; - padding: 10px 0; -} - -.commit-info-row { - margin-bottom: 10px; - .avatar { - @extend .avatar-inline; - } - .commit-committer-link, - .commit-author-link { - color: #444; - font-weight: bold; - } -} - -.lists-separator { - margin: 10px 0; - border-top: 1px dashed #CCC; -} - -/** - * COMMIT ROW - */ -li.commit { - .commit-row-title { - font-size: 14px; - margin-bottom: 2px; - - .notes_count { - float: right; - margin-right: 10px; - } - - .commit_short_id { - min-width: 65px; - font-family: $monospace_font; - } - - .str-truncated { - max-width: 70%; - } - - .commit-row-message { - color: #333; - font-weight: 500; - &:hover { - color: #444; - text-decoration: underline; - } - } - - .text-expander { - background: #eee; - color: #555; - padding: 0 5px; - cursor: pointer; - margin-left: 4px; - &:hover { - background-color: #ddd; - } - } - } - - .commit-row-description { - font-size: 14px; - border-left: 1px solid #EEE; - padding: 10px 15px; - margin: 5px 0 10px 5px; - background: #f9f9f9; - display: none; - - pre { - border: none; - background: inherit; - padding: 0; - margin: 0; - } - } - - .commit-row-info { - color: #777; - - a { - color: #777; - } - - .committed_ago { - float: right; - } - } - - &.inline-commit { - .commit-row-title { - font-size: 13px; - } - - .committed_ago { - float: right; - @extend .cgray; - } - } -} - -.commits-feed-holder { - float: right; - .btn { - padding: 4px 12px; - } -} - -.commit-message-container { - background-color: $body-bg; - position: relative; - font-family: $monospace_font; - $left: 12px; - .max-width-marker { - width: 72ch; - color: rgba(0, 0, 0, 0.0); - font-family: inherit; - left: $left; - height: 100%; - border-right: 1px solid mix($input-border, white); - position: absolute; - z-index: 1; - } - > textarea { - background-color: rgba(0, 0, 0, 0.0); - font-family: inherit; - padding-left: $left; - position: relative; - resize: vertical; - z-index: 2; - } -} diff --git a/app/assets/stylesheets/sections/help.scss b/app/assets/stylesheets/sections/help.scss deleted file mode 100644 index 90ed98ba25..0000000000 --- a/app/assets/stylesheets/sections/help.scss +++ /dev/null @@ -1,19 +0,0 @@ -.documentation-index { - h1 { - margin: 0; - } - - h2 { - font-size: 20px; - } - - li { - line-height: 24px; - color: #888; - - a { - font-size: 14px; - margin-right: 3px; - } - } -} diff --git a/app/assets/stylesheets/sections/login.scss b/app/assets/stylesheets/sections/login.scss deleted file mode 100644 index 77ebef690c..0000000000 --- a/app/assets/stylesheets/sections/login.scss +++ /dev/null @@ -1,67 +0,0 @@ -/* Login Page */ -.login-page { - h1 { - font-size: 3em; - font-weight: 200; - } - - .login-box{ - } - - .brand-image { - img { - max-width: 100%; - margin-bottom: 20px; - } - - &.default-brand-image { - margin: 0 80px; - } - } - - .login-logo{ - margin: 10px 0 30px 0; - display: block; - } - - .form-control { - background-color: #F5F5F5; - font-size: 16px; - padding: 14px 10px; - width: 100%; - height: auto; - - &.top { - @include border-radius(5px 5px 0 0); - margin-bottom: 0px; - } - - &.bottom { - @include border-radius(0 0 5px 5px); - border-top: 0; - margin-bottom: 20px; - } - - &.middle { - border-top: 0; - margin-bottom:0px; - @include border-radius(0); - } - - &:active, &:focus { - background-color: #FFF; - } - } - - .login-box a.forgot { - float: right; - padding-top: 6px - } - - .devise-errors { - h2 { - font-size: 14px; - color: #a00; - } - } -} diff --git a/app/assets/stylesheets/sections/merge_requests.scss b/app/assets/stylesheets/sections/merge_requests.scss deleted file mode 100644 index 3db6da2a9f..0000000000 --- a/app/assets/stylesheets/sections/merge_requests.scss +++ /dev/null @@ -1,124 +0,0 @@ - - /** - * MR -> show: Automerge widget - * - */ -.automerge_widget { - form { - margin-bottom: 0; - .clearfix { - margin-bottom: 0; - } - } - - .accept-group { - label { - margin: 5px; - margin-left: 20px; - } - } -} - -.merge-request .merge-request-tabs{ - border-bottom: 2px solid $border_primary; - margin: 20px 0; - - li { - a { - padding: 15px 40px; - font-size: 14px; - margin-bottom: -2px; - border-bottom: 2px solid $border_primary; - @include border-radius(0px); - } - } -} - -.mr_source_commit, -.mr_target_commit { - .commit { - margin: 0; - padding: 2px 0; - list-style: none; - &:hover { - background: none; - } - } -} - -.label-branch { - @include border-radius(4px); - padding: 3px 4px; - border: none; - background: $hover; - color: #333; - font-family: $monospace_font; - font-weight: normal; - overflow: hidden; - - .label-project { - @include border-radius-left(4px); - padding: 3px 4px; - background: #279; - position: relative; - left: -4px; - letter-spacing: -1px; - } -} - -.mr-list { - .merge-request { - padding: 10px 15px; - position: relative; - - .merge-request-title { - margin-bottom: 5px; - font-size: 14px; - } - - .merge-request-info { - color: #999; - - .merge-request-labels { - display: inline-block; - } - } - } -} - -.merge-request-angle { - text-align: center; - margin: 0 auto; - font-size: 2em; - line-height: 1.1; -} - -.merge-request-form-info { - padding-top: 15px; -} - -// hide mr close link for inline diff comment form -.diff-file .close-mr-link, -.diff-file .reopen-mr-link { - display: none; -} - -.mr-state-widget { - .panel-body { - h4 { - margin-top: 0px; - } - - p:last-child { - margin-bottom: 0; - } - } -} - -.merge-request-show-labels .label { - padding: 6px 10px; -} - -.mr-commits .commit { - padding: 10px 15px; -} diff --git a/app/assets/stylesheets/sections/milestone.scss b/app/assets/stylesheets/sections/milestone.scss deleted file mode 100644 index d20391e38f..0000000000 --- a/app/assets/stylesheets/sections/milestone.scss +++ /dev/null @@ -1,3 +0,0 @@ -.issues-sortable-list .str-truncated { - max-width: 70%; -} diff --git a/app/assets/stylesheets/sections/nav.scss b/app/assets/stylesheets/sections/nav.scss deleted file mode 100644 index f5d09c2df1..0000000000 --- a/app/assets/stylesheets/sections/nav.scss +++ /dev/null @@ -1,132 +0,0 @@ -.main-nav { - background: #f5f5f5; - margin: 20px 0; - margin-top: 0; - padding-top: 4px; - border-bottom: 1px solid #E9E9E9; - - ul { - padding: 0; - margin: auto; - height: 40px; - overflow: hidden; - .count { - font-weight: normal; - display: inline-block; - height: 15px; - padding: 1px 6px; - height: auto; - font-size: 0.82em; - line-height: 14px; - text-align: center; - color: #777; - background: #eee; - @include border-radius(8px); - } - .label { - background: $hover; - text-shadow: none; - color: $style_color; - } - li { - list-style-type: none; - margin: 0; - display: table-cell; - width: 1%; - &.active { - a { - color: #333; - font-weight: bold; - &:after { - content: ''; - display: block; - position: relative; - bottom: 8px; - left: 50%; - width: 0; - height: 0; - border-color: transparent transparent #333 transparent; - border-style: solid; - border-width: 6px; - margin-left: -6px; - } - } - } - - &:hover { - a { - color: $link_color; - &:after { - content: ''; - display: block; - position: relative; - bottom: 8px; - left: 50%; - width: 0; - height: 0; - border-color: transparent transparent $link_color transparent; - border-style: solid; - border-width: 6px; - margin-left: -6px; - } - } - } - - &.home { - a { - i { - font-size: 20px; - position: relative; - top: 4px; - } - } - } - } - a { - display: block; - text-align: center; - font-weight: 500; - height: 38px; - line-height: 34px; - color: #777; - text-shadow: 0 1px 1px white; - padding: 0 10px; - text-decoration: none; - padding-top: 2px; - } - } - - @media (max-width: $screen-xs-max) { - font-size: 18px; - margin: 0; - - max-height: none; - - &, .container { - padding: 0; - border-top: 0; - } - - ul { - height: auto; - - li { - display: list-item; - width: auto; - padding: 5px 0; - - &.active { - background-color: $link_hover_color; - - a { - color: #fff; - font-weight: normal; - text-shadow: none; - - &:after { display: none; } - } - } - } - } - } -} diff --git a/app/assets/stylesheets/sections/notes.scss b/app/assets/stylesheets/sections/notes.scss deleted file mode 100644 index 18db7abc64..0000000000 --- a/app/assets/stylesheets/sections/notes.scss +++ /dev/null @@ -1,364 +0,0 @@ -/** - * Notes - */ - -@-webkit-keyframes targe3-note { - from { background:#fffff0; } - 50% { background:#ffffd3; } - to { background:#fffff0; } -} - -ul.notes { - display: block; - list-style: none; - margin: 0px; - padding: 0px; - - .discussion-header, - .note-header { - @extend .cgray; - padding-top: 5px; - padding-bottom: 15px; - - .avatar { - float: left; - margin-right: 10px; - } - - .discussion-last-update, - .note-last-update { - &:before { - content: "\00b7"; - } - font-size: 13px; - } - .author { - color: #555; - font-weight: bold; - font-size: 14px; - &:hover { - color: $link_hover_color; - } - } - } - - .discussion { - padding: 10px 0; - overflow: hidden; - display: block; - position:relative; - border-bottom: 1px solid #EEE; - - .discussion-body { - margin-left: 50px; - } - } - - .note { - padding: 8px 0; - overflow: hidden; - display: block; - position:relative; - border-bottom: 1px solid #eee; - p { color: $style_color; } - - .avatar { - margin-top: 3px; - } - .attachment { - font-size: 14px; - } - .note-body { - @include md-typography; - margin-left: 43px; - } - .note-header { - padding-bottom: 3px; - } - - &:last-child { - border-bottom: none; - } - } - - .note:target { - -webkit-animation:target-note 2s linear; - background: #fffff0; - } -} - -.diff-file .notes_holder { - font-size: 13px; - line-height: 18px; - font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; - - td { - border: 1px solid #ddd; - border-left: none; - - &.notes_line { - text-align: center; - padding: 10px 0; - background: #eee; - } - &.notes_line2 { - text-align: center; - padding: 10px 0; - border-left: 1px solid #ddd !important; - } - &.notes_content { - background-color: #fff; - border-width: 1px 0; - padding-top: 0; - vertical-align: top; - } - } -} - -/** - * Actions for Discussions/Notes - */ - -.discussion, -.note { - &.note:hover { - .note-actions { display: block; } - } - .discussion-header:hover { - .discussion-actions { display: block; } - } - - .discussion-actions, - .note-actions { - display: none; - float: right; - - [class^="icon-"], - [class*="icon-"] { - font-size: 16px; - line-height: 16px; - vertical-align: middle; - } - - a { - @extend .cgray; - - &:hover { - color: $link_hover_color; - &.danger { @extend .cred; } - } - } - } -} -.diff-file .note .note-actions { - right: 0; - top: 0; -} - - -/** - * Line note button on the side of diffs - */ - -.diff-file tr.line_holder { - .add-diff-note { - background: image-url("diff_note_add.png") no-repeat left 0; - height: 22px; - margin-left: -65px; - position: absolute; - width: 22px; - z-index: 10; - - // "hide" it by default - opacity: 0.0; - filter: alpha(opacity=0); - - &:hover { - opacity: 1.0; - filter: alpha(opacity=100); - } - } - - // "show" the icon also if we just hover somewhere over the line - &:hover > td { - background: $hover !important; - - .add-diff-note { - opacity: 1.0; - filter: alpha(opacity=100); - } - } -} - -/** - * Note Form - */ - -.comment-btn { - @extend .btn-create; -} -.reply-btn { - @extend .btn-primary; -} -.diff-file .diff-content { - tr.line_holder:hover { - &> td.line_content { - background: $hover !important; - border-color: darken($hover, 10%) !important; - } - &> td.new_line, - &> td.old_line { - background: darken($hover, 4%) !important; - border-color: darken($hover, 10%) !important; - } - } - - tr.line_holder:hover > td .line_note_link { - opacity: 1.0; - filter: alpha(opacity=100); - } -} -.diff-file, -.discussion { - .new_note { - margin: 0; - border: none; - } -} -.new_note { - display: none; - .buttons { - float: left; - margin-top: 8px; - } - .clearfix { - margin-bottom: 0; - } - - .note-preview-holder, - .note_text { - background: #FFF; - border: 1px solid #ddd; - min-height: 100px; - padding: 5px; - font-size: 14px; - box-shadow: none; - } - - .note-preview-holder { - > p { - overflow-x: auto; - } - } - - .note_text { - width: 100%; - } - .nav-tabs { - margin-bottom: 0; - border: none; - - li a, - li.active a { - border: 1px solid #DDD; - } - } -} - -/* loading indicator */ -.notes-busy { - margin: 18px; -} - -.note-image-attach { - @extend .col-md-4; - @extend .thumbnail; - margin-left: 45px; - float: none; -} - -.common-note-form { - margin: 0; - background: #F9F9F9; - padding: 5px; - border: 1px solid #DDD; -} - -.note-form-actions { - background: #F9F9F9; - height: 45px; - - .note-form-option { - margin-top: 8px; - margin-left: 30px; - @extend .pull-left; - } - - .js-notify-commit-author { - float: left; - } - - .write-preview-btn { - // makes the "absolute" position for links relative to this - position: relative; - - // preview/edit buttons - > a { - position: absolute; - right: 5px; - top: 8px; - } - } -} - -.note-edit-form { - display: none; - - .note_text { - border: 1px solid #DDD; - box-shadow: none; - font-size: 14px; - height: 80px; - width: 100%; - } - - .form-actions { - padding-left: 20px; - - .btn-save { - float: left; - } - - .note-form-option { - float: left; - padding: 2px 0 0 25px; - } - } -} - -.js-note-attachment-delete { - display: none; -} - -.parallel-comment { - padding: 6px; -} - -.error-alert > .alert { - margin-top: 5px; - margin-bottom: 5px; -} - -.discussion-body, -.diff-file { - .notes .note { - border-color: #ddd; - padding: 10px 15px; - } - - .discussion-reply-holder { - background: #f9f9f9; - padding: 10px 15px; - border-top: 1px solid #DDD; - } -} diff --git a/app/assets/stylesheets/sections/votes.scss b/app/assets/stylesheets/sections/votes.scss deleted file mode 100644 index d683e33e1f..0000000000 --- a/app/assets/stylesheets/sections/votes.scss +++ /dev/null @@ -1,49 +0,0 @@ -.votes { - font-size: 13px; - line-height: 15px; - .progress { - height: 4px; - margin: 0; - .bar { - float: left; - height: 100%; - } - .bar-success { - @include linear-gradient(#62C462, #51A351); - background-color: #468847; - } - .bar-danger { - @include linear-gradient(#EE5F5B, #BD362F); - background-color: #B94A48; - } - } - .upvotes { - display: inline-block; - color: #468847; - } - .downvotes { - display: inline-block; - color: #B94A48; - } -} -.votes-block { - margin: 6px; - .downvotes { - float: right; - } -} -.votes-inline { - display: inline-block; - margin: 0 8px; -} - -.votes-holder { - float: right; - width: 250px; - - @media (max-width: $screen-xs-max) { - width: 100%; - margin-top: 5px; - margin-bottom: 10px; - } -} diff --git a/app/assets/stylesheets/themes/dark-theme.scss b/app/assets/stylesheets/themes/dark-theme.scss new file mode 100644 index 0000000000..b7b22a8724 --- /dev/null +++ b/app/assets/stylesheets/themes/dark-theme.scss @@ -0,0 +1,63 @@ +@mixin dark-theme($color-light, $color, $color-darker, $color-dark) { + header { + &.navbar-gitlab { + .navbar-inner { + background: $color; + + .navbar-toggle { + color: #FFF; + } + + .app_logo, .navbar-toggle { + &:hover { + background-color: $color-darker; + } + } + + .app_logo { + background-color: $color-dark; + } + + .title { + color: #FFF; + + a { + color: #FFF; + &:hover { + text-decoration: underline; + } + } + } + + .search { + .search-input { + background-color: $color-light; + background-color: rgba(255, 255, 255, 0.5); + border: 1px solid $color-light; + + &:focus { + background-color: white; + } + } + } + + .search-input::-webkit-input-placeholder { + color: #666; + } + + .nav > li > a { + color: $color-light; + + &:hover, &:focus, &:active { + background: none; + color: #FFF; + } + } + + .search-input { + border-color: $color-light; + } + } + } + } +} diff --git a/app/assets/stylesheets/themes/ui_basic.scss b/app/assets/stylesheets/themes/ui_basic.scss index 3e3744fdc3..097d5c5b73 100644 --- a/app/assets/stylesheets/themes/ui_basic.scss +++ b/app/assets/stylesheets/themes/ui_basic.scss @@ -9,17 +9,22 @@ .navbar-inner { background: #F1F1F1; border-bottom: 1px solid #DDD; + + .title { + color: #555; + + a { + color: #555; + &:hover { + text-decoration: underline; + } + } + } + .nav > li > a { color: $style_color; } - .separator { - background: #F9F9F9; - border-left: 1px solid #DDD; - } } } } - .main-nav { - background: #FFF; - } } diff --git a/app/assets/stylesheets/themes/ui_blue.scss b/app/assets/stylesheets/themes/ui_blue.scss new file mode 100644 index 0000000000..e223058be8 --- /dev/null +++ b/app/assets/stylesheets/themes/ui_blue.scss @@ -0,0 +1,6 @@ +/** + * Blue GitLab UI theme + */ +.ui_blue { + @include dark-theme(#BECDE9, #2980b9, #1970a9, #096099); +} diff --git a/app/assets/stylesheets/themes/ui_color.scss b/app/assets/stylesheets/themes/ui_color.scss index a08f3ff3d4..7ac6903b2e 100644 --- a/app/assets/stylesheets/themes/ui_color.scss +++ b/app/assets/stylesheets/themes/ui_color.scss @@ -1,43 +1,6 @@ /** - * This file represent some UI that can be changed - * during web app restyle or theme select. - * - * Next items should be placed there - * - link colors - * - header restyles - * + * Violet GitLab UI theme */ .ui_color { - /* - * Application Header - * - */ - header { - @extend .header-dark; - &.navbar-gitlab { - .navbar-inner { - background: #548; - border-bottom: 1px solid #436; - .app_logo, .navbar-toggle { - &:hover { - background-color: #436; - } - } - .separator { - background: #436; - border-left: 1px solid #659; - } - .nav > li > a { - color: #98C; - } - .search-input { - border-color: #98C; - } - } - } - } - - .nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus { - background: #659; - } + @include dark-theme(#98C, #548, #436, #325); } diff --git a/app/assets/stylesheets/themes/ui_gray.scss b/app/assets/stylesheets/themes/ui_gray.scss index 959febad6f..9257e5f4d4 100644 --- a/app/assets/stylesheets/themes/ui_gray.scss +++ b/app/assets/stylesheets/themes/ui_gray.scss @@ -1,33 +1,6 @@ /** - * This file represent some UI that can be changed - * during web app restyle or theme select. - * - * Next items should be placed there - * - link colors - * - header restyles - * + * Gray GitLab UI theme */ .ui_gray { - /* - * Application Header - * - */ - header { - @extend .header-dark; - &.navbar-gitlab { - .navbar-inner { - background: #373737; - border-bottom: 1px solid #272727; - .app_logo, .navbar-toggle { - &:hover { - background-color: #272727; - } - } - .separator { - background: #272727; - border-left: 1px solid #474747; - } - } - } - } + @include dark-theme(#979797, #373737, #272727, #222222); } diff --git a/app/assets/stylesheets/themes/ui_mars.scss b/app/assets/stylesheets/themes/ui_mars.scss index 9af5adbf10..4caf5843d9 100644 --- a/app/assets/stylesheets/themes/ui_mars.scss +++ b/app/assets/stylesheets/themes/ui_mars.scss @@ -1,39 +1,6 @@ /** - * This file represent some UI that can be changed - * during web app restyle or theme select. - * - * Next items should be placed there - * - link colors - * - header restyles - * + * Classic GitLab UI theme */ .ui_mars { - /* - * Application Header - * - */ - header { - @extend .header-dark; - &.navbar-gitlab { - .navbar-inner { - background: #474D57; - border-bottom: 1px solid #373D47; - .app_logo, .navbar-toggle { - &:hover { - background-color: #373D47; - } - } - .separator { - background: #373D47; - border-left: 1px solid #575D67; - } - .nav > li > a { - color: #979DA7; - } - .search-input { - border-color: #979DA7; - } - } - } - } + @include dark-theme(#979DA7, #474D57, #373D47, #24272D); } diff --git a/app/assets/stylesheets/themes/ui_modern.scss b/app/assets/stylesheets/themes/ui_modern.scss index 308a03477d..7044988231 100644 --- a/app/assets/stylesheets/themes/ui_modern.scss +++ b/app/assets/stylesheets/themes/ui_modern.scss @@ -1,43 +1,6 @@ /** - * This file represent some UI that can be changed - * during web app restyle or theme select. - * - * Next items should be placed there - * - link colors - * - header restyles - * + * Modern GitLab UI theme */ .ui_modern { - /* - * Application Header - * - */ - header { - @extend .header-dark; - &.navbar-gitlab { - .navbar-inner { - background: #019875; - border-bottom: 1px solid #019875; - .app_logo, .navbar-toggle { - &:hover { - background-color: #018865; - } - } - .separator { - background: #018865; - border-left: 1px solid #11A885; - } - .nav > li > a { - color: #ADC; - } - .search-input { - border-color: #8ba; - } - } - } - } - - .nav-pills > li.active > a, .nav-pills > li.active > a:hover, .nav-pills > li.active > a:focus { - background: #019875; - } + @include dark-theme(#ADC, #019875, #018865, #017855); } diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb new file mode 100644 index 0000000000..b5fda196bf --- /dev/null +++ b/app/controllers/admin/application_settings_controller.rb @@ -0,0 +1,45 @@ +class Admin::ApplicationSettingsController < Admin::ApplicationController + before_filter :set_application_setting + + def show + end + + def update + if @application_setting.update_attributes(application_setting_params) + redirect_to admin_application_settings_path, + notice: 'Application settings saved successfully' + else + render :show + end + end + + private + + def set_application_setting + @application_setting = ApplicationSetting.current + end + + def application_setting_params + restricted_levels = params[:application_setting][:restricted_visibility_levels] + if restricted_levels.nil? + params[:application_setting][:restricted_visibility_levels] = [] + else + restricted_levels.map! do |level| + level.to_i + end + end + + params.require(:application_setting).permit( + :default_projects_limit, + :default_branch_protection, + :signup_enabled, + :signin_enabled, + :gravatar_enabled, + :twitter_sharing_enabled, + :sign_in_text, + :home_page_url, + :max_attachment_size, + restricted_visibility_levels: [] + ) + end +end diff --git a/app/controllers/admin/applications_controller.rb b/app/controllers/admin/applications_controller.rb new file mode 100644 index 0000000000..471d24934a --- /dev/null +++ b/app/controllers/admin/applications_controller.rb @@ -0,0 +1,52 @@ +class Admin::ApplicationsController < Admin::ApplicationController + before_action :set_application, only: [:show, :edit, :update, :destroy] + + def index + @applications = Doorkeeper::Application.where("owner_id IS NULL") + end + + def show + end + + def new + @application = Doorkeeper::Application.new + end + + def edit + end + + def create + @application = Doorkeeper::Application.new(application_params) + + if @application.save + flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create]) + redirect_to admin_application_url(@application) + else + render :new + end + end + + def update + if @application.update(application_params) + redirect_to admin_application_path(@application), notice: 'Application was successfully updated.' + else + render :edit + end + end + + def destroy + @application.destroy + redirect_to admin_applications_url, notice: 'Application was successfully destroyed.' + end + + private + + def set_application + @application = Doorkeeper::Application.where("owner_id IS NULL").find(params[:id]) + end + + # Only allow a trusted parameter "white list" through. + def application_params + params[:doorkeeper_application].permit(:name, :redirect_uri) + end +end diff --git a/app/controllers/admin/background_jobs_controller.rb b/app/controllers/admin/background_jobs_controller.rb index 4c1d0df411..338496013a 100644 --- a/app/controllers/admin/background_jobs_controller.rb +++ b/app/controllers/admin/background_jobs_controller.rb @@ -1,6 +1,6 @@ class Admin::BackgroundJobsController < Admin::ApplicationController def show - ps_output, _ = Gitlab::Popen.popen(%W(ps -U #{Settings.gitlab.user} -o pid,pcpu,pmem,stat,start,command)) + ps_output, _ = Gitlab::Popen.popen(%W(ps -U #{Gitlab.config.gitlab.user} -o pid,pcpu,pmem,stat,start,command)) @sidekiq_processes = ps_output.split("\n").grep(/sidekiq/) end end diff --git a/app/controllers/admin/dashboard_controller.rb b/app/controllers/admin/dashboard_controller.rb index be19139c9b..c491e5c755 100644 --- a/app/controllers/admin/dashboard_controller.rb +++ b/app/controllers/admin/dashboard_controller.rb @@ -1,7 +1,7 @@ class Admin::DashboardController < Admin::ApplicationController def index - @projects = Project.order("created_at DESC").limit(10) - @users = User.order("created_at DESC").limit(10) - @groups = Group.order("created_at DESC").limit(10) + @projects = Project.limit(10) + @users = User.limit(10) + @groups = Group.limit(10) end end diff --git a/app/controllers/admin/deploy_keys_controller.rb b/app/controllers/admin/deploy_keys_controller.rb new file mode 100644 index 0000000000..e93603bef3 --- /dev/null +++ b/app/controllers/admin/deploy_keys_controller.rb @@ -0,0 +1,49 @@ +class Admin::DeployKeysController < Admin::ApplicationController + before_filter :deploy_keys, only: [:index] + before_filter :deploy_key, only: [:show, :destroy] + + def index + + end + + def show + + end + + def new + @deploy_key = deploy_keys.new + end + + def create + @deploy_key = deploy_keys.new(deploy_key_params) + + if @deploy_key.save + redirect_to admin_deploy_keys_path + else + render "new" + end + end + + def destroy + deploy_key.destroy + + respond_to do |format| + format.html { redirect_to admin_deploy_keys_path } + format.json { head :ok } + end + end + + protected + + def deploy_key + @deploy_key ||= deploy_keys.find(params[:id]) + end + + def deploy_keys + @deploy_keys ||= DeployKey.are_public + end + + def deploy_key_params + params.require(:deploy_key).permit(:key, :title) + end +end diff --git a/app/controllers/admin/groups_controller.rb b/app/controllers/admin/groups_controller.rb index 0388997ec6..22d045fc38 100644 --- a/app/controllers/admin/groups_controller.rb +++ b/app/controllers/admin/groups_controller.rb @@ -1,15 +1,16 @@ class Admin::GroupsController < Admin::ApplicationController - before_filter :group, only: [:edit, :show, :update, :destroy, :project_update, :project_teams_update] + before_filter :group, only: [:edit, :show, :update, :destroy, :project_update, :members_update] def index - @groups = Group.order('name ASC') + @groups = Group.all + @groups = @groups.sort(@sort = params[:sort]) @groups = @groups.search(params[:name]) if params[:name].present? - @groups = @groups.page(params[:page]).per(20) + @groups = @groups.page(params[:page]).per(PER_PAGE) end def show - @members = @group.members.order("group_access DESC").page(params[:members_page]).per(30) - @projects = @group.projects.page(params[:projects_page]).per(30) + @members = @group.members.order("access_level DESC").page(params[:members_page]).per(PER_PAGE) + @projects = @group.projects.page(params[:projects_page]).per(PER_PAGE) end def new @@ -21,7 +22,7 @@ class Admin::GroupsController < Admin::ApplicationController def create @group = Group.new(group_params) - @group.path = @group.name.dup.parameterize if @group.name + @group.name = @group.path.dup unless @group.name if @group.save @group.add_owner(current_user) @@ -39,8 +40,8 @@ class Admin::GroupsController < Admin::ApplicationController end end - def project_teams_update - @group.add_users(params[:user_ids].split(','), params[:group_access]) + def members_update + @group.add_users(params[:user_ids].split(','), params[:access_level], current_user) redirect_to [:admin, @group], notice: 'Users were successfully added.' end diff --git a/app/controllers/admin/keys_controller.rb b/app/controllers/admin/keys_controller.rb new file mode 100644 index 0000000000..21111bb44f --- /dev/null +++ b/app/controllers/admin/keys_controller.rb @@ -0,0 +1,34 @@ +class Admin::KeysController < Admin::ApplicationController + before_filter :user, only: [:show, :destroy] + + def show + @key = user.keys.find(params[:id]) + + respond_to do |format| + format.html + format.js { render nothing: true } + end + end + + def destroy + key = user.keys.find(params[:id]) + + respond_to do |format| + if key.destroy + format.html { redirect_to [:admin, user], notice: 'User key was successfully removed.' } + else + format.html { redirect_to [:admin, user], alert: 'Failed to remove user key.' } + end + end + end + + protected + + def user + @user ||= User.find_by!(username: params[:user_id]) + end + + def key_params + params.require(:user_id, :id) + end +end diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 1c7c09d0cd..5176a8399a 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -11,37 +11,34 @@ class Admin::ProjectsController < Admin::ApplicationController @projects = @projects.abandoned if params[:abandoned].present? @projects = @projects.search(params[:name]) if params[:name].present? @projects = @projects.sort(@sort = params[:sort]) - @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(20) + @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(PER_PAGE) end def show if @group - @group_members = @group.members.order("group_access DESC").page(params[:group_members_page]).per(30) + @group_members = @group.members.order("access_level DESC").page(params[:group_members_page]).per(PER_PAGE) end - @project_members = @project.users_projects.page(params[:project_members_page]).per(30) + @project_members = @project.project_members.page(params[:project_members_page]).per(PER_PAGE) end def transfer ::Projects::TransferService.new(@project, current_user, params.dup).execute - redirect_to [:admin, @project.reload] + @project.reload + redirect_to admin_namespace_project_path(@project.namespace, @project) end protected def project - id = params[:project_id] || params[:id] - - @project = Project.find_with_namespace(id) + @project = Project.find_with_namespace( + [params[:namespace_id], '/', params[:id]].join('') + ) @project || render_404 end def group - @group ||= project.group - end - - def repository - @repository ||= project.repository + @group ||= @project.group end end diff --git a/app/controllers/admin/services_controller.rb b/app/controllers/admin/services_controller.rb new file mode 100644 index 0000000000..76a938c5fe --- /dev/null +++ b/app/controllers/admin/services_controller.rb @@ -0,0 +1,54 @@ +class Admin::ServicesController < Admin::ApplicationController + before_filter :service, only: [:edit, :update] + + def index + @services = services_templates + end + + def edit + unless service.present? + redirect_to admin_application_settings_services_path, + alert: "Service is unknown or it doesn't exist" + end + end + + def update + if service.update_attributes(application_services_params[:service]) + redirect_to admin_application_settings_services_path, + notice: 'Application settings saved successfully' + else + render :edit + end + end + + private + + def services_templates + templates = [] + + Service.available_services_names.each do |service_name| + service_template = service_name.concat("_service").camelize.constantize + templates << service_template.where(template: true).first_or_create + end + + templates + end + + def service + @service ||= Service.where(id: params[:id], template: true).first + end + + def application_services_params + params.permit(:id, + service: [ + :title, :token, :type, :active, :api_key, :subdomain, + :room, :recipients, :project_url, :webhook, + :user_key, :device, :priority, :sound, :bamboo_url, :username, :password, + :build_key, :server, :teamcity_url, :build_type, + :description, :issues_url, :new_issue_url, :restrict_to_branch, + :send_from_committer_email, :disable_diffs, + :push_events, :tag_push_events, :note_events, :issues_events, + :merge_requests_events + ]) + end +end diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb index f63df27eeb..b4c011f213 100644 --- a/app/controllers/admin/users_controller.rb +++ b/app/controllers/admin/users_controller.rb @@ -2,14 +2,16 @@ class Admin::UsersController < Admin::ApplicationController before_filter :user, only: [:show, :edit, :update, :destroy] def index - @users = User.filter(params[:filter]) + @users = User.order_name_asc.filter(params[:filter]) @users = @users.search(params[:name]) if params[:name].present? - @users = @users.alphabetically.page(params[:page]) + @users = @users.sort(@sort = params[:sort]) + @users = @users.page(params[:page]) end def show @personal_projects = user.personal_projects @joined_projects = user.projects.joined(@user) + @keys = user.keys end def new @@ -22,7 +24,7 @@ class Admin::UsersController < Admin::ApplicationController def block if user.block - redirect_to :back, alert: "Successfully blocked" + redirect_to :back, notice: "Successfully blocked" else redirect_to :back, alert: "Error occurred. User was not blocked" end @@ -30,7 +32,7 @@ class Admin::UsersController < Admin::ApplicationController def unblock if user.activate - redirect_to :back, alert: "Successfully unblocked" + redirect_to :back, notice: "Successfully unblocked" else redirect_to :back, alert: "Error occurred. User was not unblocked" end @@ -70,8 +72,8 @@ class Admin::UsersController < Admin::ApplicationController end respond_to do |format| + user.skip_reconfirmation! if user.update_attributes(user_params_with_pass) - user.confirm! format.html { redirect_to [:admin, user], notice: 'User was successfully updated.' } format.json { head :ok } else @@ -100,6 +102,9 @@ class Admin::UsersController < Admin::ApplicationController email = user.emails.find(params[:email_id]) email.destroy + user.set_notification_email + user.save if user.notification_email_changed? + respond_to do |format| format.html { redirect_to :back, notice: "Successfully removed email." } format.js { render nothing: true } @@ -116,8 +121,8 @@ class Admin::UsersController < Admin::ApplicationController params.require(:user).permit( :email, :remember_me, :bio, :name, :username, :skype, :linkedin, :twitter, :website_url, :color_scheme_id, :theme_id, :force_random_password, - :extern_uid, :provider, :password_expires_at, :avatar, :hide_no_ssh_key, - :projects_limit, :can_create_group, :admin + :extern_uid, :provider, :password_expires_at, :avatar, :hide_no_ssh_key, :hide_no_password, + :projects_limit, :can_create_group, :admin, :key_id ) end end diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 5ffec7f75b..920a981e7c 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -1,21 +1,25 @@ require 'gon' class ApplicationController < ActionController::Base + include Gitlab::CurrentSettings + include GitlabRoutingHelper + + PER_PAGE = 20 + before_filter :authenticate_user_from_token! before_filter :authenticate_user! before_filter :reject_blocked! before_filter :check_password_expiration - before_filter :add_abilities before_filter :ldap_security_check - before_filter :dev_tools if Rails.env == 'development' before_filter :default_headers before_filter :add_gon_variables before_filter :configure_permitted_parameters, if: :devise_controller? before_filter :require_email, unless: :devise_controller? - protect_from_forgery + protect_from_forgery with: :exception - helper_method :abilities, :can? + helper_method :abilities, :can?, :current_application_settings + helper_method :github_import_enabled?, :gitlab_import_enabled?, :bitbucket_import_enabled? rescue_from Encoding::CompatibilityError do |exception| log_exception(exception) @@ -48,6 +52,17 @@ class ApplicationController < ActionController::Base end end + def authenticate_user!(*args) + # If user is not signed-in and tries to access root_path - redirect him to landing page + if current_application_settings.home_page_url.present? + if current_user.nil? && controller_name == 'dashboard' && action_name == 'show' + redirect_to current_application_settings.home_page_url and return + end + end + + super(*args) + end + def log_exception(exception) application_trace = ActionDispatch::ExceptionWrapper.new(env, exception).application_trace application_trace.map!{ |t| " #{t}\n" } @@ -62,7 +77,7 @@ class ApplicationController < ActionController::Base end end - def after_sign_in_path_for resource + def after_sign_in_path_for(resource) if resource.is_a?(User) && resource.respond_to?(:blocked?) && resource.blocked? sign_out resource flash[:alert] = "Your account is blocked. Retry when an admin has unblocked it." @@ -73,7 +88,7 @@ class ApplicationController < ActionController::Base end def abilities - @abilities ||= Six.new + Ability.abilities end def can?(object, action, subject) @@ -81,52 +96,45 @@ class ApplicationController < ActionController::Base end def project - id = params[:project_id] || params[:id] + unless @project + namespace = params[:namespace_id] + id = params[:project_id] || params[:id] - # Redirect from - # localhost/group/project.git - # to - # localhost/group/project - # - if id =~ /\.git\Z/ - redirect_to request.original_url.gsub(/\.git\Z/, '') and return - end - - @project = Project.find_with_namespace(id) - - if @project and can?(current_user, :read_project, @project) - @project - elsif current_user.nil? - @project = nil - authenticate_user! - else - @project = nil - render_404 and return + # Redirect from + # localhost/group/project.git + # to + # localhost/group/project + # + if id =~ /\.git\Z/ + redirect_to request.original_url.gsub(/\.git\Z/, '') and return + end + + @project = Project.find_with_namespace("#{namespace}/#{id}") + + if @project and can?(current_user, :read_project, @project) + @project + elsif current_user.nil? + @project = nil + authenticate_user! + else + @project = nil + render_404 and return + end end + @project end def repository @repository ||= project.repository - rescue Grit::NoSuchPathError + rescue Grit::NoSuchPathError => e + log_exception(e) nil end - def add_abilities - abilities << Ability - end - def authorize_project!(action) return access_denied! unless can?(current_user, action, project) end - def authorize_code_access! - return access_denied! unless can?(current_user, :download_code, project) - end - - def authorize_push! - return access_denied! unless can?(current_user, :push_code, project) - end - def authorize_labels! # Labels should be accessible for issues and/or merge requests authorize_read_issue! || authorize_read_merge_request! @@ -145,7 +153,7 @@ class ApplicationController < ActionController::Base end def method_missing(method_sym, *arguments, &block) - if method_sym.to_s =~ /^authorize_(.*)!$/ + if method_sym.to_s =~ /\Aauthorize_(.*)!\z/ authorize_project!($1.to_sym) else super @@ -170,7 +178,16 @@ class ApplicationController < ActionController::Base response.headers["Expires"] = "Fri, 01 Jan 1990 00:00:00 GMT" end - def dev_tools + def default_url_options + if !Rails.env.test? + port = Gitlab.config.gitlab.port unless Gitlab.config.gitlab_on_standard_port? + { host: Gitlab.config.gitlab.host, + protocol: Gitlab.config.gitlab.protocol, + port: port, + script_name: Gitlab.config.gitlab.relative_url_root } + else + super + end end def default_headers @@ -182,10 +199,11 @@ class ApplicationController < ActionController::Base end def add_gon_variables - gon.default_issues_tracker = Project.issues_tracker.default_value + gon.default_issues_tracker = Project.new.default_issue_tracker.to_param gon.api_version = API::API.version gon.relative_url_root = Gitlab.config.gitlab.relative_url_root gon.default_avatar_url = URI::join(Gitlab.config.gitlab.url, ActionController::Base.helpers.image_path('no_avatar.png')).to_s + gon.max_file_size = current_application_settings.max_attachment_size; if current_user gon.current_user_id = current_user.id @@ -253,4 +271,75 @@ class ApplicationController < ActionController::Base redirect_to profile_path, notice: 'Please complete your profile with email address' and return end end + + def set_filters_params + params[:sort] ||= 'created_desc' + params[:scope] = 'all' if params[:scope].blank? + params[:state] = 'opened' if params[:state].blank? + + @filter_params = params.dup + + if @project + @filter_params[:project_id] = @project.id + elsif @group + @filter_params[:group_id] = @group.id + else + # TODO: this filter ignore issues/mr created in public or + # 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 + end + + @filter_params + end + + def set_filter_values(collection) + assignee_id = @filter_params[:assignee_id] + author_id = @filter_params[:author_id] + milestone_id = @filter_params[:milestone_id] + + @sort = @filter_params[:sort] + @assignees = User.where(id: collection.pluck(:assignee_id)) + @authors = User.where(id: collection.pluck(:author_id)) + @milestones = Milestone.where(id: collection.pluck(:milestone_id)) + + if assignee_id.present? && !assignee_id.to_i.zero? + @assignee = @assignees.find_by(id: assignee_id) + end + + if author_id.present? && !author_id.to_i.zero? + @author = @authors.find_by(id: author_id) + end + + if milestone_id.present? && !milestone_id.to_i.zero? + @milestone = @milestones.find_by(id: milestone_id) + end + end + + def get_issues_collection + set_filters_params + issues = IssuesFinder.new.execute(current_user, @filter_params) + set_filter_values(issues) + issues + end + + def get_merge_requests_collection + set_filters_params + merge_requests = MergeRequestsFinder.new.execute(current_user, @filter_params) + set_filter_values(merge_requests) + merge_requests + end + + def github_import_enabled? + OauthHelper.enabled_oauth_providers.include?(:github) + end + + def gitlab_import_enabled? + OauthHelper.enabled_oauth_providers.include?(:gitlab) + end + + def bitbucket_import_enabled? + OauthHelper.enabled_oauth_providers.include?(:bitbucket) && Gitlab::BitbucketImport.public_key.present? + end end diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb new file mode 100644 index 0000000000..11af989526 --- /dev/null +++ b/app/controllers/autocomplete_controller.rb @@ -0,0 +1,30 @@ +class AutocompleteController < ApplicationController + def users + @users = + if params[:project_id].present? + project = Project.find(params[:project_id]) + + if can?(current_user, :read_project, project) + project.team.users + end + elsif params[:group_id] + group = Group.find(params[:group_id]) + + if can?(current_user, :read_group, group) + group.users + end + else + User.all + end + + @users = @users.search(params[:search]) if params[:search].present? + @users = @users.active + @users = @users.page(params[:page]).per(PER_PAGE) + render json: @users, only: [:name, :username, :id], methods: [:avatar_url] + end + + def user + @user = User.find(params[:id]) + render json: @user, only: [:name, :username, :id], methods: [:avatar_url] + end +end diff --git a/app/controllers/confirmations_controller.rb b/app/controllers/confirmations_controller.rb new file mode 100644 index 0000000000..af1faca93f --- /dev/null +++ b/app/controllers/confirmations_controller.rb @@ -0,0 +1,17 @@ +class ConfirmationsController < Devise::ConfirmationsController + + protected + + def after_confirmation_path_for(resource_name, resource) + if signed_in?(resource_name) + after_sign_in_path_for(resource) + else + sign_in(resource) + if signed_in?(resource_name) + after_sign_in_path_for(resource) + else + new_session_path(resource_name) + end + end + end +end diff --git a/app/controllers/dashboard/groups_controller.rb b/app/controllers/dashboard/groups_controller.rb new file mode 100644 index 0000000000..ed14f4e1f3 --- /dev/null +++ b/app/controllers/dashboard/groups_controller.rb @@ -0,0 +1,5 @@ +class Dashboard::GroupsController < ApplicationController + def index + @group_members = current_user.group_members.page(params[:page]).per(PER_PAGE) + end +end diff --git a/app/controllers/dashboard/milestones_controller.rb b/app/controllers/dashboard/milestones_controller.rb new file mode 100644 index 0000000000..cb51792df1 --- /dev/null +++ b/app/controllers/dashboard/milestones_controller.rb @@ -0,0 +1,34 @@ +class Dashboard::MilestonesController < ApplicationController + before_filter :load_projects + + def index + project_milestones = case params[:state] + when 'all'; state + when 'closed'; state('closed') + else state('active') + end + @dashboard_milestones = Milestones::GroupService.new(project_milestones).execute + @dashboard_milestones = Kaminari.paginate_array(@dashboard_milestones).page(params[:page]).per(PER_PAGE) + end + + def show + project_milestones = Milestone.where(project_id: @projects).order("due_date ASC") + @dashboard_milestone = Milestones::GroupService.new(project_milestones).milestone(title) + end + + private + + def load_projects + @projects = current_user.authorized_projects.sorted_by_activity.non_archived + end + + def title + params[:title] + end + + def state(state = nil) + conditions = { project_id: @projects } + conditions.reverse_merge!(state: state) if state + Milestone.where(conditions).order("title ASC") + end +end diff --git a/app/controllers/dashboard/projects_controller.rb b/app/controllers/dashboard/projects_controller.rb new file mode 100644 index 0000000000..56e6fcc41c --- /dev/null +++ b/app/controllers/dashboard/projects_controller.rb @@ -0,0 +1,27 @@ +class Dashboard::ProjectsController < ApplicationController + before_filter :event_filter + + def starred + @projects = current_user.starred_projects + @projects = @projects.includes(:namespace, :forked_from_project, :tags) + @projects = @projects.sort(@sort = params[:sort]) + @groups = [] + + respond_to do |format| + format.html + + format.json do + load_events + pager_json("events/_events", @events.count) + end + end + end + + private + + def load_events + @events = Event.in_projects(@projects.pluck(:id)) + @events = @event_filter.apply_filter(@events).with_associations + @events = @events.limit(20).offset(params[:offset] || 0) + end +end diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb index 5aff526d1b..9bd853ed5c 100644 --- a/app/controllers/dashboard_controller.rb +++ b/app/controllers/dashboard_controller.rb @@ -3,66 +3,35 @@ class DashboardController < ApplicationController before_filter :load_projects, except: [:projects] before_filter :event_filter, only: :show - before_filter :default_filter, only: [:issues, :merge_requests] - def show - # Fetch only 30 projects. - # If user needs more - point to Dashboard#projects page - @projects_limit = 30 - - @groups = current_user.authorized_groups.sort_by(&:human_name) - @has_authorized_projects = @projects.count > 0 - @projects_count = @projects.count - @projects = @projects.limit(@projects_limit) - - @events = Event.in_projects(current_user.authorized_projects.pluck(:id)) - @events = @event_filter.apply_filter(@events) - @events = @events.limit(20).offset(params[:offset] || 0) - + @projects = @projects.includes(:namespace) @last_push = current_user.recent_push - @publicish_project_count = Project.publicish(current_user).count - respond_to do |format| format.html - format.json { pager_json("events/_events", @events.count) } - format.atom { render layout: false } + + format.json do + load_events + pager_json("events/_events", @events.count) + end + + format.atom do + load_events + render layout: false + end end end - def projects - @projects = case params[:scope] - when 'personal' then - current_user.namespace.projects - when 'joined' then - current_user.authorized_projects.joined(current_user) - when 'owned' then - current_user.owned_projects - else - current_user.authorized_projects - end - - @projects = @projects.where(namespace_id: Group.find_by(name: params[:group])) if params[:group].present? - @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present? - @projects = @projects.includes(:namespace) - @projects = @projects.tagged_with(params[:tag]) if params[:tag].present? - @projects = @projects.sort(@sort = params[:sort]) - @projects = @projects.page(params[:page]).per(30) - - @tags = current_user.authorized_projects.tags_on(:tags) - @groups = current_user.authorized_groups - end - def merge_requests - @merge_requests = MergeRequestsFinder.new.execute(current_user, params) - @merge_requests = @merge_requests.page(params[:page]).per(20) + @merge_requests = get_merge_requests_collection + @merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE) @merge_requests = @merge_requests.preload(:author, :target_project) end def issues - @issues = IssuesFinder.new.execute(current_user, params) - @issues = @issues.page(params[:page]).per(20) + @issues = get_issues_collection + @issues = @issues.page(params[:page]).per(PER_PAGE) @issues = @issues.preload(:author, :project) respond_to do |format| @@ -77,9 +46,9 @@ class DashboardController < ApplicationController @projects = current_user.authorized_projects.sorted_by_activity.non_archived end - def default_filter - params[:scope] = 'assigned-to-me' if params[:scope].blank? - params[:state] = 'opened' if params[:state].blank? - params[:authorized_only] = true + def load_events + @events = Event.in_projects(current_user.authorized_projects.pluck(:id)) + @events = @event_filter.apply_filter(@events).with_associations + @events = @events.limit(20).offset(params[:offset] || 0) end end diff --git a/app/controllers/explore/groups_controller.rb b/app/controllers/explore/groups_controller.rb index f8e1a31e0b..c51a4a211a 100644 --- a/app/controllers/explore/groups_controller.rb +++ b/app/controllers/explore/groups_controller.rb @@ -1,7 +1,6 @@ class Explore::GroupsController < ApplicationController skip_before_filter :authenticate_user!, - :reject_blocked, :set_current_user_for_observers, - :add_abilities + :reject_blocked, :set_current_user_for_observers layout "explore" @@ -9,6 +8,6 @@ class Explore::GroupsController < ApplicationController @groups = GroupsFinder.new.execute(current_user) @groups = @groups.search(params[:search]) if params[:search].present? @groups = @groups.sort(@sort = params[:sort]) - @groups = @groups.page(params[:page]).per(20) + @groups = @groups.page(params[:page]).per(PER_PAGE) end end diff --git a/app/controllers/explore/projects_controller.rb b/app/controllers/explore/projects_controller.rb index b6fa8b7e38..b295f295bb 100644 --- a/app/controllers/explore/projects_controller.rb +++ b/app/controllers/explore/projects_controller.rb @@ -1,25 +1,27 @@ class Explore::ProjectsController < ApplicationController skip_before_filter :authenticate_user!, - :reject_blocked, - :add_abilities + :reject_blocked layout 'explore' def index @projects = ProjectsFinder.new.execute(current_user) + @tags = @projects.tags_on(:tags) + @projects = @projects.tagged_with(params[:tag]) if params[:tag].present? + @projects = @projects.where(visibility_level: params[:visibility_level]) if params[:visibility_level].present? @projects = @projects.search(params[:search]) if params[:search].present? @projects = @projects.sort(@sort = params[:sort]) - @projects = @projects.includes(:namespace).page(params[:page]).per(20) + @projects = @projects.includes(:namespace).page(params[:page]).per(PER_PAGE) end def trending @trending_projects = TrendingProjectsFinder.new.execute(current_user) - @trending_projects = @trending_projects.page(params[:page]).per(10) + @trending_projects = @trending_projects.page(params[:page]).per(PER_PAGE) end def starred @starred_projects = ProjectsFinder.new.execute(current_user) - @starred_projects = @starred_projects.order('star_count DESC') - @starred_projects = @starred_projects.page(params[:page]).per(10) + @starred_projects = @starred_projects.reorder('star_count DESC') + @starred_projects = @starred_projects.page(params[:page]).per(PER_PAGE) end end diff --git a/app/controllers/files_controller.rb b/app/controllers/files_controller.rb deleted file mode 100644 index 7937454810..0000000000 --- a/app/controllers/files_controller.rb +++ /dev/null @@ -1,16 +0,0 @@ -class FilesController < ApplicationController - def download - note = Note.find(params[:id]) - uploader = note.attachment - - if uploader.file_storage? - if can?(current_user, :read_project, note.project) - send_file uploader.file.path, disposition: 'attachment' - else - not_found! - end - else - redirect_to uploader.url - end - end -end diff --git a/app/controllers/groups/application_controller.rb b/app/controllers/groups/application_controller.rb new file mode 100644 index 0000000000..469a6813ee --- /dev/null +++ b/app/controllers/groups/application_controller.rb @@ -0,0 +1,28 @@ +class Groups::ApplicationController < ApplicationController + + private + + def authorize_read_group! + unless @group and can?(current_user, :read_group, @group) + if current_user.nil? + return authenticate_user! + else + return render_404 + end + end + end + + def authorize_admin_group! + unless can?(current_user, :admin_group, group) + return render_404 + end + end + + def determine_layout + if current_user + 'group' + else + 'public_group' + end + end +end diff --git a/app/controllers/groups/group_members_controller.rb b/app/controllers/groups/group_members_controller.rb new file mode 100644 index 0000000000..265cf4f0f4 --- /dev/null +++ b/app/controllers/groups/group_members_controller.rb @@ -0,0 +1,84 @@ +class Groups::GroupMembersController < Groups::ApplicationController + skip_before_filter :authenticate_user!, only: [:index] + before_filter :group + + # Authorize + before_filter :authorize_read_group! + before_filter :authorize_admin_group!, except: [:index, :leave] + + layout :determine_layout + + def index + @project = @group.projects.find(params[:project_id]) if params[:project_id] + @members = @group.group_members + @members = @members.non_invite unless can?(current_user, :admin_group, @group) + + if params[:search].present? + users = @group.users.search(params[:search]).to_a + @members = @members.where(user_id: users) + end + + @members = @members.order('access_level DESC').page(params[:page]).per(50) + @group_member = GroupMember.new + end + + def create + @group.add_users(params[:user_ids].split(','), params[:access_level], current_user) + + redirect_to group_group_members_path(@group), notice: 'Users were successfully added.' + end + + def update + @member = @group.group_members.find(params[:id]) + @member.update_attributes(member_params) + end + + def destroy + @group_member = @group.group_members.find(params[:id]) + + if can?(current_user, :destroy_group_member, @group_member) # May fail if last owner. + @group_member.destroy + 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 } + end + else + return render_403 + end + end + + def resend_invite + redirect_path = group_group_members_path(@group) + + @group_member = @group.group_members.find(params[:id]) + + if @group_member.invite? + @group_member.resend_invite + + redirect_to redirect_path, notice: 'The invitation was successfully resent.' + else + redirect_to redirect_path, alert: 'The invitation has already been accepted.' + end + end + + def leave + @group_member = @group.group_members.where(user_id: current_user.id).first + + if can?(current_user, :destroy_group_member, @group_member) + @group_member.destroy + redirect_to(dashboard_groups_path, notice: "You left #{group.name} group.") + else + return render_403 + end + end + + protected + + def group + @group ||= Group.find_by(path: params[:group_id]) + end + + def member_params + params.require(:group_member).permit(:access_level, :user_id) + end +end diff --git a/app/controllers/groups/milestones_controller.rb b/app/controllers/groups/milestones_controller.rb index 860d8e0392..546ff2cc71 100644 --- a/app/controllers/groups/milestones_controller.rb +++ b/app/controllers/groups/milestones_controller.rb @@ -4,13 +4,13 @@ class Groups::MilestonesController < ApplicationController before_filter :authorize_group_milestone!, only: :update def index - project_milestones = case params[:status] - when 'all'; status - when 'closed'; status('closed') - else status('active') + project_milestones = case params[:state] + when 'all'; state + when 'closed'; state('closed') + else state('active') end @group_milestones = Milestones::GroupService.new(project_milestones).execute - @group_milestones = Kaminari.paginate_array(@group_milestones).page(params[:page]).per(30) + @group_milestones = Kaminari.paginate_array(@group_milestones).page(params[:page]).per(PER_PAGE) end def show @@ -44,13 +44,13 @@ class Groups::MilestonesController < ApplicationController params[:title] end - def status(state = nil) + def state(state = nil) conditions = { project_id: group.projects } conditions.reverse_merge!(state: state) if state Milestone.where(conditions).order("title ASC") end def authorize_group_milestone! - return render_404 unless can?(current_user, :manage_group, group) + return render_404 unless can?(current_user, :admin_group, group) end end diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb index ddde90d3ee..7af3c07718 100644 --- a/app/controllers/groups_controller.rb +++ b/app/controllers/groups_controller.rb @@ -1,5 +1,5 @@ -class GroupsController < ApplicationController - skip_before_filter :authenticate_user!, only: [:show, :issues, :members, :merge_requests] +class GroupsController < Groups::ApplicationController + skip_before_filter :authenticate_user!, only: [:show, :issues, :merge_requests] respond_to :html before_filter :group, except: [:new, :create] @@ -10,20 +10,18 @@ class GroupsController < ApplicationController # Load group projects before_filter :load_projects, except: [:new, :create, :projects, :edit, :update] - - before_filter :default_filter, only: [:issues, :merge_requests] + before_filter :event_filter, only: :show + before_filter :set_title, only: [:new, :create] layout :determine_layout - before_filter :set_title, only: [:new, :create] - def new @group = Group.new end def create @group = Group.new(group_params) - @group.path = @group.name.dup.parameterize if @group.name + @group.name = @group.path.dup unless @group.name if @group.save @group.add_owner(current_user) @@ -34,27 +32,33 @@ class GroupsController < ApplicationController end def show - @events = Event.in_projects(project_ids) - @events = event_filter.apply_filter(@events) - @events = @events.limit(20).offset(params[:offset] || 0) @last_push = current_user.recent_push if current_user + @projects = @projects.includes(:namespace) respond_to do |format| format.html - format.json { pager_json("events/_events", @events.count) } - format.atom { render layout: false } + + format.json do + load_events + pager_json("events/_events", @events.count) + end + + format.atom do + load_events + render layout: false + end end end def merge_requests - @merge_requests = MergeRequestsFinder.new.execute(current_user, params) - @merge_requests = @merge_requests.page(params[:page]).per(20) + @merge_requests = get_merge_requests_collection + @merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE) @merge_requests = @merge_requests.preload(:author, :target_project) end def issues - @issues = IssuesFinder.new.execute(current_user, params) - @issues = @issues.page(params[:page]).per(20) + @issues = get_issues_collection + @issues = @issues.page(params[:page]).per(PER_PAGE) @issues = @issues.preload(:author, :project) respond_to do |format| @@ -63,19 +67,6 @@ class GroupsController < ApplicationController end end - def members - @project = group.projects.find(params[:project_id]) if params[:project_id] - @members = group.users_groups - - if params[:search].present? - users = group.users.search(params[:search]).to_a - @members = @members.where(user_id: users) - end - - @members = @members.order('group_access DESC').page(params[:page]).per(50) - @users_group = UsersGroup.new - end - def edit end @@ -128,12 +119,6 @@ class GroupsController < ApplicationController end end - def authorize_admin_group! - unless can?(current_user, :manage_group, group) - return render_404 - end - end - def set_title @title = 'New Group' end @@ -148,19 +133,13 @@ class GroupsController < ApplicationController end end - def default_filter - if params[:scope].blank? - if current_user - params[:scope] = 'assigned-to-me' - else - params[:scope] = 'all' - end - end - params[:state] = 'opened' if params[:state].blank? - params[:group_id] = @group.id - end - def group_params params.require(:group).permit(:name, :description, :path, :avatar) end + + def load_events + @events = Event.in_projects(project_ids) + @events = event_filter.apply_filter(@events).with_associations + @events = @events.limit(20).offset(params[:offset] || 0) + end end diff --git a/app/controllers/help_controller.rb b/app/controllers/help_controller.rb index fc498559d6..35ece5b270 100644 --- a/app/controllers/help_controller.rb +++ b/app/controllers/help_controller.rb @@ -3,16 +3,82 @@ class HelpController < ApplicationController end def show - @category = params[:category] - @file = params[:file] + category = clean_path_info(path_params[:category]) + file = path_params[:file] - if File.exists?(Rails.root.join('doc', @category, @file + '.md')) - render 'show' - else - not_found! + respond_to do |format| + format.any(:markdown, :md, :html) do + path = Rails.root.join('doc', category, "#{file}.md") + + if File.exist?(path) + @markdown = File.read(path) + + render 'show.html.haml' + else + # Force template to Haml + render 'errors/not_found.html.haml', layout: 'errors', status: 404 + end + end + + # Allow access to images in the doc folder + format.any(:png, :gif, :jpeg) do + path = Rails.root.join('doc', category, "#{file}.#{params[:format]}") + + if File.exist?(path) + send_file(path, disposition: 'inline') + else + head :not_found + end + end + + # Any other format we don't recognize, just respond 404 + format.any { head :not_found } end end def shortcuts end + + def ui + end + + private + + def path_params + params.require(:category) + params.require(:file) + + params + end + + PATH_SEPS = Regexp.union(*[::File::SEPARATOR, ::File::ALT_SEPARATOR].compact) + + # Taken from ActionDispatch::FileHandler + # Cleans up the path, to prevent directory traversal outside the doc folder. + def clean_path_info(path_info) + parts = path_info.split(PATH_SEPS) + + clean = [] + + # Walk over each part of the path + parts.each do |part| + # Turn `one//two` or `one/./two` into `one/two`. + next if part.empty? || part == '.' + + if part == '..' + # Turn `one/two/../` into `one` + clean.pop + else + # Add simple folder names to the clean path. + clean << part + end + end + + # If the path was an absolute path (i.e. `/` or `/one/two`), + # add `/` to the front of the clean path. + clean.unshift '/' if parts.empty? || parts.first.empty? + + # Join all the clean path parts by the path separator. + ::File.join(*clean) + end end diff --git a/app/controllers/import/base_controller.rb b/app/controllers/import/base_controller.rb new file mode 100644 index 0000000000..93a7ace353 --- /dev/null +++ b/app/controllers/import/base_controller.rb @@ -0,0 +1,19 @@ +class Import::BaseController < ApplicationController + + private + + def get_or_create_namespace + begin + namespace = Group.create!(name: @target_namespace, path: @target_namespace, owner: current_user) + namespace.add_owner(current_user) + rescue ActiveRecord::RecordNotUnique, ActiveRecord::RecordInvalid + namespace = Namespace.find_by_path_or_name(@target_namespace) + unless current_user.can?(:create_projects, namespace) + @already_been_taken = true + return false + end + end + + namespace + end +end diff --git a/app/controllers/import/bitbucket_controller.rb b/app/controllers/import/bitbucket_controller.rb new file mode 100644 index 0000000000..bb8d7e0235 --- /dev/null +++ b/app/controllers/import/bitbucket_controller.rb @@ -0,0 +1,82 @@ +class Import::BitbucketController < Import::BaseController + before_filter :verify_bitbucket_import_enabled + before_filter :bitbucket_auth, except: :callback + + rescue_from OAuth::Error, with: :bitbucket_unauthorized + + def callback + request_token = session.delete(:oauth_request_token) + raise "Session expired!" if request_token.nil? + + request_token.symbolize_keys! + + access_token = client.get_token(request_token, params[:oauth_verifier], callback_import_bitbucket_url) + + current_user.bitbucket_access_token = access_token.token + current_user.bitbucket_access_token_secret = access_token.secret + + current_user.save + redirect_to status_import_bitbucket_url + end + + def status + @repos = client.projects + + @already_added_projects = current_user.created_projects.where(import_type: "bitbucket") + already_added_projects_names = @already_added_projects.pluck(:import_source) + + @repos.to_a.reject!{ |repo| already_added_projects_names.include? "#{repo["owner"]}/#{repo["slug"]}" } + end + + def jobs + jobs = current_user.created_projects.where(import_type: "bitbucket").to_json(only: [:id, :import_status]) + render json: jobs + end + + def create + @repo_id = params[:repo_id] || "" + repo = client.project(@repo_id.gsub("___", "/")) + @project_name = repo["slug"] + + repo_owner = repo["owner"] + repo_owner = current_user.username if repo_owner == client.user["user"]["username"] + @target_namespace = params[:new_namespace].presence || repo_owner + + namespace = get_or_create_namespace || (render and return) + + unless Gitlab::BitbucketImport::KeyAdder.new(repo, current_user).execute + @access_denied = true + render + return + end + + @project = Gitlab::BitbucketImport::ProjectCreator.new(repo, namespace, current_user).execute + end + + private + + def client + @client ||= Gitlab::BitbucketImport::Client.new(current_user.bitbucket_access_token, current_user.bitbucket_access_token_secret) + end + + def verify_bitbucket_import_enabled + not_found! unless bitbucket_import_enabled? + end + + def bitbucket_auth + if current_user.bitbucket_access_token.blank? + go_to_bitbucket_for_permissions + end + end + + def go_to_bitbucket_for_permissions + request_token = client.request_token(callback_import_bitbucket_url) + session[:oauth_request_token] = request_token + + redirect_to client.authorize_url(request_token, callback_import_bitbucket_url) + end + + def bitbucket_unauthorized + go_to_bitbucket_for_permissions + end +end diff --git a/app/controllers/import/github_controller.rb b/app/controllers/import/github_controller.rb new file mode 100644 index 0000000000..87b41454c7 --- /dev/null +++ b/app/controllers/import/github_controller.rb @@ -0,0 +1,68 @@ +class Import::GithubController < Import::BaseController + before_filter :verify_github_import_enabled + before_filter :github_auth, except: :callback + + rescue_from Octokit::Unauthorized, with: :github_unauthorized + + def callback + token = client.get_token(params[:code]) + current_user.github_access_token = token + current_user.save + redirect_to status_import_github_url + end + + def status + @repos = client.repos + client.orgs.each do |org| + @repos += client.org_repos(org.login) + end + + @already_added_projects = current_user.created_projects.where(import_type: "github") + already_added_projects_names = @already_added_projects.pluck(:import_source) + + @repos.reject!{ |repo| already_added_projects_names.include? repo.full_name } + end + + def jobs + jobs = current_user.created_projects.where(import_type: "github").to_json(only: [:id, :import_status]) + render json: jobs + end + + def create + @repo_id = params[:repo_id].to_i + repo = client.repo(@repo_id) + @project_name = repo.name + + repo_owner = repo.owner.login + repo_owner = current_user.username if repo_owner == client.user.login + @target_namespace = params[:new_namespace].presence || repo_owner + + namespace = get_or_create_namespace || (render and return) + + @project = Gitlab::GithubImport::ProjectCreator.new(repo, namespace, current_user).execute + end + + private + + def client + @client ||= Gitlab::GithubImport::Client.new(current_user.github_access_token) + end + + def verify_github_import_enabled + not_found! unless github_import_enabled? + end + + def github_auth + if current_user.github_access_token.blank? + go_to_github_for_permissions + end + end + + def go_to_github_for_permissions + redirect_to client.authorize_url(callback_import_github_url) + end + + def github_unauthorized + go_to_github_for_permissions + end +end diff --git a/app/controllers/import/gitlab_controller.rb b/app/controllers/import/gitlab_controller.rb new file mode 100644 index 0000000000..bddbfded81 --- /dev/null +++ b/app/controllers/import/gitlab_controller.rb @@ -0,0 +1,65 @@ +class Import::GitlabController < Import::BaseController + before_filter :verify_gitlab_import_enabled + before_filter :gitlab_auth, except: :callback + + rescue_from OAuth2::Error, with: :gitlab_unauthorized + + def callback + token = client.get_token(params[:code], callback_import_gitlab_url) + current_user.gitlab_access_token = token + current_user.save + redirect_to status_import_gitlab_url + end + + def status + @repos = client.projects + + @already_added_projects = current_user.created_projects.where(import_type: "gitlab") + already_added_projects_names = @already_added_projects.pluck(:import_source) + + @repos = @repos.to_a.reject{ |repo| already_added_projects_names.include? repo["path_with_namespace"] } + end + + def jobs + jobs = current_user.created_projects.where(import_type: "gitlab").to_json(only: [:id, :import_status]) + render json: jobs + end + + def create + @repo_id = params[:repo_id].to_i + repo = client.project(@repo_id) + @project_name = repo["name"] + + repo_owner = repo["namespace"]["path"] + repo_owner = current_user.username if repo_owner == client.user["username"] + @target_namespace = params[:new_namespace].presence || repo_owner + + namespace = get_or_create_namespace || (render and return) + + @project = Gitlab::GitlabImport::ProjectCreator.new(repo, namespace, current_user).execute + end + + private + + def client + @client ||= Gitlab::GitlabImport::Client.new(current_user.gitlab_access_token) + end + + def verify_gitlab_import_enabled + not_found! unless gitlab_import_enabled? + end + + def gitlab_auth + if current_user.gitlab_access_token.blank? + go_to_gitlab_for_permissions + end + end + + def go_to_gitlab_for_permissions + redirect_to client.authorize_url(callback_import_gitlab_url) + end + + def gitlab_unauthorized + go_to_gitlab_for_permissions + end +end diff --git a/app/controllers/import/gitorious_controller.rb b/app/controllers/import/gitorious_controller.rb new file mode 100644 index 0000000000..6067a87ee0 --- /dev/null +++ b/app/controllers/import/gitorious_controller.rb @@ -0,0 +1,43 @@ +class Import::GitoriousController < Import::BaseController + + def new + redirect_to client.authorize_url(callback_import_gitorious_url) + end + + def callback + session[:gitorious_repos] = params[:repos] + redirect_to status_import_gitorious_url + end + + def status + @repos = client.repos + + @already_added_projects = current_user.created_projects.where(import_type: "gitorious") + already_added_projects_names = @already_added_projects.pluck(:import_source) + + @repos.reject! { |repo| already_added_projects_names.include? repo.full_name } + end + + def jobs + jobs = current_user.created_projects.where(import_type: "gitorious").to_json(only: [:id, :import_status]) + render json: jobs + end + + def create + @repo_id = params[:repo_id] + repo = client.repo(@repo_id) + @target_namespace = params[:new_namespace].presence || repo.namespace + @project_name = repo.name + + namespace = get_or_create_namespace || (render and return) + + @project = Gitlab::GitoriousImport::ProjectCreator.new(repo, namespace, current_user).execute + end + + private + + def client + @client ||= Gitlab::GitoriousImport::Client.new(session[:gitorious_repos]) + end + +end diff --git a/app/controllers/import/google_code_controller.rb b/app/controllers/import/google_code_controller.rb new file mode 100644 index 0000000000..6574be9192 --- /dev/null +++ b/app/controllers/import/google_code_controller.rb @@ -0,0 +1,116 @@ +class Import::GoogleCodeController < Import::BaseController + before_filter :user_map, only: [:new_user_map, :create_user_map] + + def new + + end + + def callback + dump_file = params[:dump_file] + + unless dump_file.respond_to?(:read) + return redirect_to :back, alert: "You need to upload a Google Takeout archive." + end + + begin + dump = JSON.parse(dump_file.read) + rescue + return redirect_to :back, alert: "The uploaded file is not a valid Google Takeout archive." + end + + client = Gitlab::GoogleCodeImport::Client.new(dump) + unless client.valid? + return redirect_to :back, alert: "The uploaded file is not a valid Google Takeout archive." + end + + session[:google_code_dump] = dump + + if params[:create_user_map] == "1" + redirect_to new_user_map_import_google_code_path + else + redirect_to status_import_google_code_path + end + end + + def new_user_map + + end + + def create_user_map + user_map_json = params[:user_map] + user_map_json = "{}" if user_map_json.blank? + + begin + user_map = JSON.parse(user_map_json) + rescue + flash.now[:alert] = "The entered user map is not a valid JSON user map." + + render "new_user_map" and return + end + + unless user_map.is_a?(Hash) && user_map.all? { |k, v| k.is_a?(String) && v.is_a?(String) } + flash.now[:alert] = "The entered user map is not a valid JSON user map." + + render "new_user_map" and return + end + + # This is the default, so let's not save it into the database. + user_map.reject! do |key, value| + value == Gitlab::GoogleCodeImport::Client.mask_email(key) + end + + session[:google_code_user_map] = user_map + + flash[:notice] = "The user map has been saved. Continue by selecting the projects you want to import." + + redirect_to status_import_google_code_path + end + + def status + unless client.valid? + return redirect_to new_import_google_code_path + end + + @repos = client.repos + + @already_added_projects = current_user.created_projects.where(import_type: "google_code") + already_added_projects_names = @already_added_projects.pluck(:import_source) + + @repos.reject! { |repo| already_added_projects_names.include? repo.name } + end + + def jobs + jobs = current_user.created_projects.where(import_type: "google_code").to_json(only: [:id, :import_status]) + render json: jobs + end + + def create + @repo_id = params[:repo_id] + repo = client.repo(@repo_id) + @target_namespace = current_user.namespace + @project_name = repo.name + + namespace = @target_namespace + + user_map = session[:google_code_user_map] + + @project = Gitlab::GoogleCodeImport::ProjectCreator.new(repo, namespace, current_user, user_map).execute + end + + private + + def client + @client ||= Gitlab::GoogleCodeImport::Client.new(session[:google_code_dump]) + end + + def user_map + @user_map ||= begin + user_map = client.user_map + + stored_user_map = session[:google_code_user_map] + user_map.update(stored_user_map) if stored_user_map + + Hash[user_map.sort] + end + end +end diff --git a/app/controllers/invites_controller.rb b/app/controllers/invites_controller.rb new file mode 100644 index 0000000000..1f97ff16c5 --- /dev/null +++ b/app/controllers/invites_controller.rb @@ -0,0 +1,83 @@ +class InvitesController < ApplicationController + before_filter :member + skip_before_filter :authenticate_user!, only: :decline + + respond_to :html + + layout 'navless' + + def show + + end + + def accept + if member.accept_invite!(current_user) + label, path = source_info(member.source) + + redirect_to path, notice: "You have been granted #{member.human_access} access to #{label}." + else + redirect_to :back, alert: "The invitation could not be accepted." + end + end + + def decline + if member.decline_invite! + label, _ = source_info(member.source) + + path = + if current_user + dashboard_path + else + new_user_session_path + end + + redirect_to path, notice: "You have declined the invitation to join #{label}." + else + redirect_to :back, alert: "The invitation could not be declined." + end + end + + private + + def member + return @member if defined?(@member) + + @token = params[:id] + @member = Member.find_by_invite_token(@token) + + unless @member + render_404 and return + end + + @member + end + + def authenticate_user! + return if current_user + + notice = "To accept this invitation, sign in" + notice << " or create an account" if current_application_settings.signup_enabled? + notice << "." + + store_location_for :user, request.fullpath + redirect_to new_user_session_path, notice: notice + end + + def source_info(source) + case source + when Project + project = member.source + label = "project #{project.name_with_namespace}" + path = namespace_project_path(project.namespace, project) + when Group + group = member.source + label = "group #{group.name}" + path = group_path(group) + else + label = "who knows what" + path = dashboard_path + end + + [label, path] + end +end diff --git a/app/controllers/namespaces_controller.rb b/app/controllers/namespaces_controller.rb index c59a2401ce..386d103ee5 100644 --- a/app/controllers/namespaces_controller.rb +++ b/app/controllers/namespaces_controller.rb @@ -4,15 +4,22 @@ class NamespacesController < ApplicationController def show namespace = Namespace.find_by(path: params[:id]) - unless namespace - return render_404 + if namespace + if namespace.is_a?(Group) + group = namespace + else + user = namespace.owner + end end - if namespace.type == "Group" - redirect_to group_path(namespace) + if user + redirect_to user_path(user) + elsif group && can?(current_user, :read_group, group) + redirect_to group_path(group) + elsif current_user.nil? + authenticate_user! else - redirect_to user_path(namespace.owner) + render_404 end end end - diff --git a/app/controllers/oauth/applications_controller.rb b/app/controllers/oauth/applications_controller.rb new file mode 100644 index 0000000000..efa291d939 --- /dev/null +++ b/app/controllers/oauth/applications_controller.rb @@ -0,0 +1,39 @@ +class Oauth::ApplicationsController < Doorkeeper::ApplicationsController + before_filter :authenticate_user! + layout "profile" + + def index + head :forbidden and return + end + + def create + @application = Doorkeeper::Application.new(application_params) + + @application.owner = current_user + + if @application.save + flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :create]) + redirect_to oauth_application_url(@application) + else + render :new + end + end + + def destroy + if @application.destroy + flash[:notice] = I18n.t(:notice, scope: [:doorkeeper, :flash, :applications, :destroy]) + end + + redirect_to applications_profile_url + end + + private + + def set_application + @application = current_user.oauth_applications.find(params[:id]) + end + + rescue_from ActiveRecord::RecordNotFound do |exception| + render "errors/not_found", layout: "errors", status: 404 + end +end diff --git a/app/controllers/oauth/authorizations_controller.rb b/app/controllers/oauth/authorizations_controller.rb new file mode 100644 index 0000000000..a57b4a60c2 --- /dev/null +++ b/app/controllers/oauth/authorizations_controller.rb @@ -0,0 +1,57 @@ +class Oauth::AuthorizationsController < Doorkeeper::AuthorizationsController + before_filter :authenticate_resource_owner! + layout "profile" + + def new + if pre_auth.authorizable? + if skip_authorization? || matching_token? + auth = authorization.authorize + redirect_to auth.redirect_uri + else + render "doorkeeper/authorizations/new" + end + else + render "doorkeeper/authorizations/error" + end + end + + # TODO: Handle raise invalid authorization + def create + redirect_or_render authorization.authorize + end + + def destroy + redirect_or_render authorization.deny + end + + private + + def matching_token? + Doorkeeper::AccessToken.matching_token_for(pre_auth.client, + current_resource_owner.id, + pre_auth.scopes) + end + + def redirect_or_render(auth) + if auth.redirectable? + redirect_to auth.redirect_uri + else + render json: auth.body, status: auth.status + end + end + + def pre_auth + @pre_auth ||= + Doorkeeper::OAuth::PreAuthorization.new(Doorkeeper.configuration, + server.client_via_uid, + params) + end + + def authorization + @authorization ||= strategy.request + end + + def strategy + @strategy ||= server.authorization_request(pre_auth.response_type) + end +end diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb new file mode 100644 index 0000000000..0b27ce7da7 --- /dev/null +++ b/app/controllers/oauth/authorized_applications_controller.rb @@ -0,0 +1,8 @@ +class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicationsController + layout "profile" + + def destroy + Doorkeeper::AccessToken.revoke_all_for(params[:id], current_resource_owner) + redirect_to applications_profile_url, notice: I18n.t(:notice, scope: [:doorkeeper, :flash, :authorized_applications, :destroy]) + end +end diff --git a/app/controllers/omniauth_callbacks_controller.rb b/app/controllers/omniauth_callbacks_controller.rb index 3ed6a69c2d..bb9d65c9ed 100644 --- a/app/controllers/omniauth_callbacks_controller.rb +++ b/app/controllers/omniauth_callbacks_controller.rb @@ -15,15 +15,17 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController error.to_s.humanize if error end + # We only find ourselves here + # if the authentication to LDAP was successful. def ldap - # We only find ourselves here - # if the authentication to LDAP was successful. - @user = Gitlab::LDAP::User.find_or_create(oauth) - @user.remember_me = true if @user.persisted? + @user = Gitlab::LDAP::User.new(oauth) + @user.save if @user.changed? # will also save new users + gl_user = @user.gl_user + gl_user.remember_me = true if @user.persisted? # Do additional LDAP checks for the user filter and EE features - if Gitlab::LDAP::Access.allowed?(@user) - sign_in_and_redirect(@user) + if @user.allowed? + sign_in_and_redirect(gl_user) else flash[:alert] = "Access denied for your LDAP account." redirect_to new_user_session_path @@ -40,32 +42,32 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController def handle_omniauth if current_user - # Change a logged-in user's authentication method: - current_user.extern_uid = oauth['uid'] - current_user.provider = oauth['provider'] - current_user.save - redirect_to profile_path + # Add new authentication method + current_user.identities.find_or_create_by(extern_uid: oauth['uid'], provider: oauth['provider']) + redirect_to profile_account_path, notice: 'Authentication method updated' else - @user = Gitlab::OAuth::User.find(oauth) + @user = Gitlab::OAuth::User.new(oauth) + @user.save - # Create user if does not exist - # and allow_single_sign_on is true - if Gitlab.config.omniauth['allow_single_sign_on'] && !@user - @user, errors = Gitlab::OAuth::User.create(oauth) - end - - if @user && !errors - sign_in_and_redirect(@user) + # Only allow properly saved users to login. + if @user.persisted? && @user.valid? + sign_in_and_redirect(@user.gl_user) else - if errors - error_message = errors.map{ |attribute, message| "#{attribute} #{message}" }.join(", ") - redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return - else - flash[:notice] = "There's no such user!" - end - redirect_to new_user_session_path + error_message = + if @user.gl_user.errors.any? + @user.gl_user.errors.map do |attribute, message| + "#{attribute} #{message}" + end.join(", ") + else + '' + end + + redirect_to omniauth_error_path(oauth['provider'], error: error_message) and return end end + rescue Gitlab::OAuth::ForbiddenAction => e + flash[:notice] = e.message + redirect_to new_user_session_path end def oauth diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb index 988ede3007..dcbbe5baa4 100644 --- a/app/controllers/passwords_controller.rb +++ b/app/controllers/passwords_controller.rb @@ -5,12 +5,12 @@ class PasswordsController < Devise::PasswordsController resource_found = resource_class.find_by_email(email) if resource_found && resource_found.ldap_user? flash[:alert] = "Cannot reset password for LDAP user." - respond_with({}, :location => after_sending_reset_password_instructions_path_for(resource_name)) and return + respond_with({}, location: after_sending_reset_password_instructions_path_for(resource_name)) and return end self.resource = resource_class.send_reset_password_instructions(resource_params) if successfully_sent?(resource) - respond_with({}, :location => after_sending_reset_password_instructions_path_for(resource_name)) + respond_with({}, location: after_sending_reset_password_instructions_path_for(resource_name)) else respond_with(resource) end diff --git a/app/controllers/profiles/accounts_controller.rb b/app/controllers/profiles/accounts_controller.rb index fe121691a1..9bd34fe226 100644 --- a/app/controllers/profiles/accounts_controller.rb +++ b/app/controllers/profiles/accounts_controller.rb @@ -4,4 +4,10 @@ class Profiles::AccountsController < ApplicationController def show @user = current_user end + + def unlink + provider = params[:provider] + current_user.identities.find_by(provider: provider).destroy + redirect_to profile_account_path + end end diff --git a/app/controllers/profiles/emails_controller.rb b/app/controllers/profiles/emails_controller.rb index f3f0e69b83..954c98c0d9 100644 --- a/app/controllers/profiles/emails_controller.rb +++ b/app/controllers/profiles/emails_controller.rb @@ -3,6 +3,7 @@ class Profiles::EmailsController < ApplicationController def index @primary = current_user.email + @public_email = current_user.public_email @emails = current_user.emails end @@ -18,6 +19,10 @@ class Profiles::EmailsController < ApplicationController @email = current_user.emails.find(params[:id]) @email.destroy + current_user.set_notification_email + current_user.set_public_email + current_user.save if current_user.notification_email_changed? or current_user.public_email_changed? + respond_to do |format| format.html { redirect_to profile_emails_url } format.js { render nothing: true } diff --git a/app/controllers/profiles/groups_controller.rb b/app/controllers/profiles/groups_controller.rb deleted file mode 100644 index 9a4d088651..0000000000 --- a/app/controllers/profiles/groups_controller.rb +++ /dev/null @@ -1,23 +0,0 @@ -class Profiles::GroupsController < ApplicationController - layout "profile" - - def index - @user_groups = current_user.users_groups.page(params[:page]).per(20) - end - - def leave - @users_group = group.users_groups.where(user_id: current_user.id).first - if can?(current_user, :destroy, @users_group) - @users_group.destroy - redirect_to(profile_groups_path, info: "You left #{group.name} group.") - else - return render_403 - end - end - - private - - def group - @group ||= Group.find_by(path: params[:id]) - end -end diff --git a/app/controllers/profiles/keys_controller.rb b/app/controllers/profiles/keys_controller.rb index 88414b1356..4e2bd0a9b4 100644 --- a/app/controllers/profiles/keys_controller.rb +++ b/app/controllers/profiles/keys_controller.rb @@ -3,7 +3,7 @@ class Profiles::KeysController < ApplicationController skip_before_filter :authenticate_user!, only: [:get_keys] def index - @keys = current_user.keys.order('id DESC') + @keys = current_user.keys end def show diff --git a/app/controllers/profiles/notifications_controller.rb b/app/controllers/profiles/notifications_controller.rb index 5c492aeb49..3fdcbbab61 100644 --- a/app/controllers/profiles/notifications_controller.rb +++ b/app/controllers/profiles/notifications_controller.rb @@ -2,25 +2,43 @@ class Profiles::NotificationsController < ApplicationController layout 'profile' def show + @user = current_user @notification = current_user.notification - @users_projects = current_user.users_projects - @users_groups = current_user.users_groups + @project_members = current_user.project_members + @group_members = current_user.group_members end def update type = params[:notification_type] @saved = if type == 'global' - current_user.notification_level = params[:notification_level] - current_user.save + current_user.update_attributes(user_params) elsif type == 'group' - users_group = current_user.users_groups.find(params[:notification_id]) - users_group.notification_level = params[:notification_level] - users_group.save + group_member = current_user.group_members.find(params[:notification_id]) + group_member.notification_level = params[:notification_level] + group_member.save else - users_project = current_user.users_projects.find(params[:notification_id]) - users_project.notification_level = params[:notification_level] - users_project.save + project_member = current_user.project_members.find(params[:notification_id]) + project_member.notification_level = params[:notification_level] + project_member.save end + + respond_to do |format| + format.html do + if @saved + flash[:notice] = "Notification settings saved" + else + flash[:alert] = "Failed to save new settings" + end + + redirect_to :back + end + + format.js + end + end + + def user_params + params.require(:user).permit(:notification_email, :notification_level) end end diff --git a/app/controllers/profiles/passwords_controller.rb b/app/controllers/profiles/passwords_controller.rb index 1191ce47eb..0c614969a3 100644 --- a/app/controllers/profiles/passwords_controller.rb +++ b/app/controllers/profiles/passwords_controller.rb @@ -11,7 +11,7 @@ class Profiles::PasswordsController < ApplicationController end def create - unless @user.valid_password?(user_params[:current_password]) + unless @user.password_automatically_set || @user.valid_password?(user_params[:current_password]) redirect_to new_profile_password_path, alert: 'You must provide a valid current password' return end @@ -21,7 +21,8 @@ class Profiles::PasswordsController < ApplicationController result = @user.update_attributes( password: new_password, - password_confirmation: new_password_confirmation + password_confirmation: new_password_confirmation, + password_automatically_set: false ) if result @@ -39,8 +40,9 @@ class Profiles::PasswordsController < ApplicationController password_attributes = user_params.select do |key, value| %w(password password_confirmation).include?(key.to_s) end + password_attributes[:password_automatically_set] = false - unless @user.valid_password?(user_params[:current_password]) + unless @user.password_automatically_set || @user.valid_password?(user_params[:current_password]) redirect_to edit_profile_password_path, alert: 'You must provide a valid current password' return end diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb index e877f9b904..7f76906066 100644 --- a/app/controllers/profiles_controller.rb +++ b/app/controllers/profiles_controller.rb @@ -13,13 +13,20 @@ class ProfilesController < ApplicationController def design end + def applications + @applications = current_user.oauth_applications + @authorized_tokens = current_user.oauth_authorized_tokens + @authorized_apps = @authorized_tokens.map(&:application).uniq + end + def update user_params.except!(:email) if @user.ldap_user? if @user.update_attributes(user_params) flash[:notice] = "Profile was successfully updated" else - flash[:alert] = "Failed to update profile" + messages = @user.errors.full_messages.uniq.join('. ') + flash[:alert] = "Failed to update profile. #{messages}" end respond_to do |format| @@ -37,7 +44,7 @@ class ProfilesController < ApplicationController end def history - @events = current_user.recent_events.page(params[:page]).per(20) + @events = current_user.recent_events.page(params[:page]).per(PER_PAGE) end def update_username @@ -60,9 +67,10 @@ class ProfilesController < ApplicationController def user_params params.require(:user).permit( - :email, :password, :password_confirmation, :bio, :name, :username, - :skype, :linkedin, :twitter, :website_url, :color_scheme_id, :theme_id, - :avatar, :hide_no_ssh_key, + :email, :password, :password_confirmation, :bio, :name, + :username, :skype, :linkedin, :twitter, :website_url, + :color_scheme_id, :theme_id, :avatar, :hide_no_ssh_key, + :hide_no_password, :location, :public_email ) end end diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index 7e4580017d..4719933394 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -8,7 +8,8 @@ class Projects::ApplicationController < ApplicationController # for non-signed users if !current_user id = params[:project_id] || params[:id] - @project = Project.find_with_namespace(id) + project_with_namespace = "#{params[:namespace_id]}/#{id}" + @project = Project.find_with_namespace(project_with_namespace) return if @project && @project.public? end @@ -26,7 +27,10 @@ class Projects::ApplicationController < ApplicationController def require_branch_head unless @repository.branch_names.include?(@ref) - redirect_to project_tree_path(@project, @ref), notice: "This action is not allowed unless you are on top of a branch" + redirect_to( + namespace_project_tree_path(@project.namespace, @project, @ref), + notice: "This action is not allowed unless you are on top of a branch" + ) end end end diff --git a/app/controllers/projects/avatars_controller.rb b/app/controllers/projects/avatars_controller.rb new file mode 100644 index 0000000000..a482b90880 --- /dev/null +++ b/app/controllers/projects/avatars_controller.rb @@ -0,0 +1,29 @@ +class Projects::AvatarsController < Projects::ApplicationController + layout 'project' + + before_filter :project + + def show + @blob = @project.repository.blob_at_branch('master', @project.avatar_in_git) + if @blob + headers['X-Content-Type-Options'] = 'nosniff' + send_data( + @blob.data, + type: @blob.mime_type, + disposition: 'inline', + filename: @blob.name + ) + else + not_found! + end + end + + def destroy + @project.remove_avatar! + + @project.save + @project.reset_events_cache + + redirect_to edit_project_path(@project) + end +end diff --git a/app/controllers/projects/base_tree_controller.rb b/app/controllers/projects/base_tree_controller.rb deleted file mode 100644 index 5e30593443..0000000000 --- a/app/controllers/projects/base_tree_controller.rb +++ /dev/null @@ -1,8 +0,0 @@ -class Projects::BaseTreeController < Projects::ApplicationController - include ExtractsPath - - before_filter :authorize_read_project! - before_filter :authorize_code_access! - before_filter :require_non_empty_project -end - diff --git a/app/controllers/projects/blame_controller.rb b/app/controllers/projects/blame_controller.rb index a3c4130167..a87b8270a2 100644 --- a/app/controllers/projects/blame_controller.rb +++ b/app/controllers/projects/blame_controller.rb @@ -2,13 +2,12 @@ class Projects::BlameController < Projects::ApplicationController include ExtractsPath - # Authorize - before_filter :authorize_read_project! - before_filter :authorize_code_access! before_filter :require_non_empty_project + before_filter :assign_ref_vars + before_filter :authorize_download_code! def show - @blob = @repository.blob_at(@commit.id, @path) - @blame = Gitlab::Git::Blame.new(project.repository, @commit.id, @path) + @blame = Gitlab::Git::Blame.new(@repository, @commit.id, @path) + @blob = @blame.blob end end diff --git a/app/controllers/projects/blob_controller.rb b/app/controllers/projects/blob_controller.rb index 7009e3b1bc..4b7eb4df29 100644 --- a/app/controllers/projects/blob_controller.rb +++ b/app/controllers/projects/blob_controller.rb @@ -1,26 +1,93 @@ # Controller for viewing a file's blame class Projects::BlobController < Projects::ApplicationController include ExtractsPath + include ActionView::Helpers::SanitizeHelper - # Authorize - before_filter :authorize_read_project! - before_filter :authorize_code_access! - before_filter :require_non_empty_project - before_filter :authorize_push!, only: [:destroy] + # Raised when given an invalid file path + class InvalidPathError < StandardError; end - before_filter :blob + before_filter :require_non_empty_project, except: [:new, :create] + before_filter :authorize_download_code! + before_filter :authorize_push_code!, only: [:destroy] + before_filter :assign_blob_vars + before_filter :commit, except: [:new, :create] + before_filter :blob, except: [:new, :create] + before_filter :from_merge_request, only: [:edit, :update] + before_filter :after_edit_path, only: [:edit, :update] + before_filter :require_branch_head, only: [:edit, :update] + + def new + commit unless @repository.empty? + end + + def create + file_path = File.join(@path, File.basename(params[:file_name])) + result = Files::CreateService.new( + @project, + current_user, + params.merge(new_branch: sanitized_new_branch_name), + @ref, + file_path + ).execute + + if result[:status] == :success + flash[:notice] = "Your changes have been successfully committed" + ref = sanitized_new_branch_name.presence || @ref + redirect_to namespace_project_blob_path(@project.namespace, @project, File.join(ref, file_path)) + else + flash[:alert] = result[:message] + render :new + end + end def show end + def edit + @last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha + end + + def update + result = Files::UpdateService. + new( + @project, + current_user, + params.merge(new_branch: sanitized_new_branch_name), + @ref, + @path + ).execute + + if result[:status] == :success + flash[:notice] = "Your changes have been successfully committed" + + if from_merge_request + from_merge_request.reload_code + end + + redirect_to after_edit_path + else + flash[:alert] = result[:message] + render :edit + end + end + + def preview + @content = params[:content] + diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', include_diff_info: true) + @diff_lines = Gitlab::Diff::Parser.new.parse(diffy.diff.scan(/.*\n/)) + + render layout: false + end + def destroy result = Files::DeleteService.new(@project, current_user, params, @ref, @path).execute if result[:status] == :success flash[:notice] = "Your changes have been successfully committed" - redirect_to project_tree_path(@project, @ref) + redirect_to namespace_project_tree_path(@project.namespace, @project, + @ref) else - flash[:alert] = result[:error] + flash[:alert] = result[:message] render :show end end @@ -47,10 +114,50 @@ class Projects::BlobController < Projects::ApplicationController if @blob @blob - elsif tree.entries.any? - redirect_to project_tree_path(@project, File.join(@ref, @path)) and return else + if tree = @repository.tree(@commit.id, @path) + if tree.entries.any? + redirect_to namespace_project_tree_path(@project.namespace, @project, File.join(@ref, @path)) and return + end + end + return not_found! end end + + def commit + @commit = @repository.commit(@ref) + + return not_found! unless @commit + end + + def assign_blob_vars + @id = params[:id] + @ref, @path = extract_ref(@id) + + + rescue InvalidPathError + not_found! + end + + def after_edit_path + @after_edit_path ||= + if from_merge_request + diffs_namespace_project_merge_request_path(from_merge_request.target_project.namespace, from_merge_request.target_project, from_merge_request) + + "#file-path-#{hexdigest(@path)}" + elsif sanitized_new_branch_name.present? + namespace_project_blob_path(@project.namespace, @project, File.join(sanitized_new_branch_name, @path)) + else + namespace_project_blob_path(@project.namespace, @project, @id) + end + end + + def from_merge_request + # If blob edit was initiated from merge request page + @from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id]) + end + + def sanitized_new_branch_name + @new_branch ||= sanitize(strip_tags(params[:new_branch])) + end end diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb index 3c8e7ec73f..f049e96e61 100644 --- a/app/controllers/projects/branches_controller.rb +++ b/app/controllers/projects/branches_controller.rb @@ -1,15 +1,14 @@ class Projects::BranchesController < Projects::ApplicationController + include ActionView::Helpers::SanitizeHelper # Authorize - before_filter :authorize_read_project! before_filter :require_non_empty_project - - before_filter :authorize_code_access! - before_filter :authorize_push!, only: [:create, :destroy] + before_filter :authorize_download_code! + before_filter :authorize_push_code!, only: [:create, :destroy] def index @sort = params[:sort] || 'name' @branches = @repository.branches_sorted_by(@sort) - @branches = Kaminari.paginate_array(@branches).page(params[:page]).per(30) + @branches = Kaminari.paginate_array(@branches).page(params[:page]).per(PER_PAGE) end def recent @@ -17,17 +16,30 @@ class Projects::BranchesController < Projects::ApplicationController end def create - @branch = CreateBranchService.new.execute(project, params[:branch_name], params[:ref], current_user) + branch_name = sanitize(strip_tags(params[:branch_name])) + ref = sanitize(strip_tags(params[:ref])) + result = CreateBranchService.new(project, current_user). + execute(branch_name, ref) - redirect_to project_tree_path(@project, @branch.name) + if result[:status] == :success + @branch = result[:branch] + redirect_to namespace_project_tree_path(@project.namespace, @project, + @branch.name) + else + @error = result[:message] + render action: 'new' + end end def destroy - DeleteBranchService.new.execute(project, params[:id], current_user) + DeleteBranchService.new(project, current_user).execute(params[:id]) @branch_name = params[:id] respond_to do |format| - format.html { redirect_to project_branches_path(@project) } + format.html do + redirect_to namespace_project_branches_path(@project.namespace, + @project) + end format.js end end diff --git a/app/controllers/projects/commit_controller.rb b/app/controllers/projects/commit_controller.rb index c344297ba8..87e39f1363 100644 --- a/app/controllers/projects/commit_controller.rb +++ b/app/controllers/projects/commit_controller.rb @@ -3,32 +3,18 @@ # Not to be confused with CommitsController, plural. class Projects::CommitController < Projects::ApplicationController # Authorize - before_filter :authorize_read_project! - before_filter :authorize_code_access! before_filter :require_non_empty_project + before_filter :authorize_download_code! before_filter :commit def show return git_not_found! unless @commit - @line_notes = project.notes.for_commit_id(commit.id).inline - - @branches = begin - project.repository.branch_names_contains(commit.id) - rescue Grit::Git::GitTimeout - [] - end - - begin - @diffs = @commit.diffs - rescue Grit::Git::GitTimeout - @diffs = [] - @diff_timeout = true - end - - @note = project.build_commit_note(commit) - @notes_count = project.notes.for_commit_id(commit.id).count - @notes = project.notes.for_commit_id(@commit.id).not_inline.fresh + @line_notes = @project.notes.for_commit_id(commit.id).inline + @diffs = @commit.diffs + @note = @project.build_commit_note(commit) + @notes_count = @project.notes.for_commit_id(commit.id).count + @notes = @project.notes.for_commit_id(@commit.id).not_inline.fresh @noteable = @commit @comments_allowed = @reply_allowed = true @comments_target = { @@ -43,7 +29,13 @@ class Projects::CommitController < Projects::ApplicationController end end + def branches + @branches = @project.repository.branch_names_contains(commit.id) + @tags = @project.repository.tag_names_contains(commit.id) + render layout: false + end + def commit - @commit ||= project.repository.commit(params[:id]) + @commit ||= @project.repository.commit(params[:id]) end end diff --git a/app/controllers/projects/commits_controller.rb b/app/controllers/projects/commits_controller.rb index 038645aa49..4b6ab43747 100644 --- a/app/controllers/projects/commits_controller.rb +++ b/app/controllers/projects/commits_controller.rb @@ -3,10 +3,9 @@ require "base64" class Projects::CommitsController < Projects::ApplicationController include ExtractsPath - # Authorize - before_filter :authorize_read_project! - before_filter :authorize_code_access! before_filter :require_non_empty_project + before_filter :assign_ref_vars + before_filter :authorize_download_code! def show @repo = @project.repository @@ -14,10 +13,10 @@ class Projects::CommitsController < Projects::ApplicationController @commits = @repo.commits(@ref, @path, @limit, @offset) @note_counts = Note.where(commit_id: @commits.map(&:id)). - group(:commit_id).count + group(:commit_id).count respond_to do |format| - format.html # index.html.erb + format.html format.json { pager_json("projects/commits/_commits", @commits.size) } format.atom { render layout: false } end diff --git a/app/controllers/projects/compare_controller.rb b/app/controllers/projects/compare_controller.rb index 7a671e8455..146808fa56 100644 --- a/app/controllers/projects/compare_controller.rb +++ b/app/controllers/projects/compare_controller.rb @@ -1,8 +1,7 @@ class Projects::CompareController < Projects::ApplicationController # Authorize - before_filter :authorize_read_project! - before_filter :authorize_code_access! before_filter :require_non_empty_project + before_filter :authorize_download_code! def index end @@ -26,6 +25,7 @@ class Projects::CompareController < Projects::ApplicationController end def create - redirect_to project_compare_path(@project, params[:from], params[:to]) + redirect_to namespace_project_compare_path(@project.namespace, @project, + params[:from], params[:to]) end end diff --git a/app/controllers/projects/deploy_keys_controller.rb b/app/controllers/projects/deploy_keys_controller.rb index d20937ea8e..6fba3ce299 100644 --- a/app/controllers/projects/deploy_keys_controller.rb +++ b/app/controllers/projects/deploy_keys_controller.rb @@ -8,7 +8,14 @@ class Projects::DeployKeysController < Projects::ApplicationController def index @enabled_keys = @project.deploy_keys - @available_keys = available_keys - @enabled_keys + + @available_keys = accessible_keys - @enabled_keys + @available_project_keys = current_user.project_deploy_keys - @enabled_keys + @available_public_keys = DeployKey.are_public - @enabled_keys + + # Public keys that are already used by another accessible project are already + # in @available_project_keys. + @available_public_keys -= @available_project_keys end def show @@ -25,38 +32,31 @@ class Projects::DeployKeysController < Projects::ApplicationController @key = DeployKey.new(deploy_key_params) if @key.valid? && @project.deploy_keys << @key - redirect_to project_deploy_keys_path(@project) + redirect_to namespace_project_deploy_keys_path(@project.namespace, + @project) else render "new" end end - def destroy - @key = @project.deploy_keys.find(params[:id]) - @key.destroy - - respond_to do |format| - format.html { redirect_to project_deploy_keys_url } - format.js { render nothing: true } - end - end - def enable - project.deploy_keys << available_keys.find(params[:id]) + @key = accessible_keys.find(params[:id]) + @project.deploy_keys << @key - redirect_to project_deploy_keys_path(@project) + redirect_to namespace_project_deploy_keys_path(@project.namespace, + @project) end def disable - @project.deploy_keys_projects.where(deploy_key_id: params[:id]).last.destroy + @project.deploy_keys_projects.find_by(deploy_key_id: params[:id]).destroy - redirect_to project_deploy_keys_path(@project) + redirect_to :back end protected - def available_keys - @available_keys ||= current_user.accessible_deploy_keys + def accessible_keys + @accessible_keys ||= current_user.accessible_deploy_keys end def deploy_key_params diff --git a/app/controllers/projects/edit_tree_controller.rb b/app/controllers/projects/edit_tree_controller.rb deleted file mode 100644 index ca83b21f42..0000000000 --- a/app/controllers/projects/edit_tree_controller.rb +++ /dev/null @@ -1,59 +0,0 @@ -class Projects::EditTreeController < Projects::BaseTreeController - before_filter :require_branch_head - before_filter :blob - before_filter :authorize_push! - before_filter :from_merge_request - before_filter :after_edit_path - - def show - @last_commit = Gitlab::Git::Commit.last_for_path(@repository, @ref, @path).sha - end - - def update - result = Files::UpdateService.new(@project, current_user, params, @ref, @path).execute - - if result[:status] == :success - flash[:notice] = "Your changes have been successfully committed" - - if from_merge_request - from_merge_request.reload_code - end - - redirect_to after_edit_path - else - flash[:alert] = result[:error] - render :show - end - end - - def preview - @content = params[:content] - - diffy = Diffy::Diff.new(@blob.data, @content, diff: '-U 3', - include_diff_info: true) - @diff = Gitlab::DiffParser.new(diffy.diff.scan(/.*\n/)) - - render layout: false - end - - private - - def blob - @blob ||= @repository.blob_at(@commit.id, @path) - end - - def after_edit_path - @after_edit_path ||= - if from_merge_request - diffs_project_merge_request_path(from_merge_request.target_project, from_merge_request) + - "#file-path-#{hexdigest(@path)}" - else - project_blob_path(@project, @id) - end - end - - def from_merge_request - # If blob edit was initiated from merge request page - @from_merge_request ||= MergeRequest.find_by(id: params[:from_merge_request_id]) - end -end diff --git a/app/controllers/projects/forks_controller.rb b/app/controllers/projects/forks_controller.rb new file mode 100644 index 0000000000..21a151a426 --- /dev/null +++ b/app/controllers/projects/forks_controller.rb @@ -0,0 +1,25 @@ +class Projects::ForksController < Projects::ApplicationController + # Authorize + before_filter :require_non_empty_project + before_filter :authorize_download_code! + + def new + @namespaces = current_user.manageable_namespaces + @namespaces.delete(@project.namespace) + end + + def create + namespace = Namespace.find(params[:namespace_key]) + @forked_project = ::Projects::ForkService.new(project, current_user, namespace: namespace).execute + + if @forked_project.saved? && @forked_project.forked? + redirect_to( + namespace_project_path(@forked_project.namespace, @forked_project), + notice: 'Project was successfully forked.' + ) + else + @title = 'Fork project' + render :error + end + end +end diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb index 252d47d939..6e54af356e 100644 --- a/app/controllers/projects/graphs_controller.rb +++ b/app/controllers/projects/graphs_controller.rb @@ -1,25 +1,39 @@ class Projects::GraphsController < Projects::ApplicationController # Authorize - before_filter :authorize_read_project! - before_filter :authorize_code_access! before_filter :require_non_empty_project + before_filter :authorize_download_code! def show respond_to do |format| format.html - format.js do + format.json do fetch_graph end end end + def commits + @commits = @project.repository.commits(nil, nil, 2000, 0, true) + @commits_graph = Gitlab::Graphs::Commits.new(@commits) + @commits_per_week_days = @commits_graph.commits_per_week_days + @commits_per_time = @commits_graph.commits_per_time + @commits_per_month = @commits_graph.commits_per_month + end + private def fetch_graph - @log = @project.repository.graph_log.to_json - @success = true - rescue => ex + @commits = @project.repository.commits(nil, nil, 6000, 0, true) @log = [] - @success = false + + @commits.each do |commit| + @log << { + author_name: commit.author_name, + author_email: commit.author_email, + date: commit.committed_date.strftime("%Y-%m-%d") + } + end + + render json: @log.to_json end end diff --git a/app/controllers/projects/hooks_controller.rb b/app/controllers/projects/hooks_controller.rb index cab8fd76e6..ba95bb13e1 100644 --- a/app/controllers/projects/hooks_controller.rb +++ b/app/controllers/projects/hooks_controller.rb @@ -16,7 +16,7 @@ class Projects::HooksController < Projects::ApplicationController @hook.save if @hook.valid? - redirect_to project_hooks_path(@project) + redirect_to namespace_project_hooks_path(@project.namespace, @project) else @hooks = @project.hooks.select(&:persisted?) render :index @@ -26,6 +26,7 @@ class Projects::HooksController < Projects::ApplicationController def test if !@project.empty_repo? status = TestHookService.new.execute(hook, current_user) + if status flash[:notice] = 'Hook successfully executed.' else @@ -42,7 +43,7 @@ class Projects::HooksController < Projects::ApplicationController def destroy hook.destroy - redirect_to project_hooks_path(@project) + redirect_to namespace_project_hooks_path(@project.namespace, @project) end private diff --git a/app/controllers/projects/imports_controller.rb b/app/controllers/projects/imports_controller.rb new file mode 100644 index 0000000000..b64491b466 --- /dev/null +++ b/app/controllers/projects/imports_controller.rb @@ -0,0 +1,51 @@ +class Projects::ImportsController < Projects::ApplicationController + # Authorize + before_filter :authorize_admin_project! + before_filter :require_no_repo + before_filter :redirect_if_progress, except: :show + + def new + end + + def create + @project.import_url = params[:project][:import_url] + + if @project.save + @project.reload + + if @project.import_failed? + @project.import_retry + else + @project.import_start + end + end + + redirect_to namespace_project_import_path(@project.namespace, @project) + end + + def show + unless @project.import_in_progress? + if @project.import_finished? + redirect_to(project_path(@project)) and return + else + redirect_to new_namespace_project_import_path(@project.namespace, + @project) && return + end + end + end + + private + + def require_no_repo + if @project.repository_exists? && !@project.import_in_progress? + redirect_to(namespace_project_path(@project.namespace, @project)) and return + end + end + + def redirect_if_progress + if @project.import_in_progress? + redirect_to namespace_project_import_path(@project.namespace, @project) && + return + end + end +end diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb index bde90466ea..88302276b5 100644 --- a/app/controllers/projects/issues_controller.rb +++ b/app/controllers/projects/issues_controller.rb @@ -1,6 +1,6 @@ class Projects::IssuesController < Projects::ApplicationController before_filter :module_enabled - before_filter :issue, only: [:edit, :update, :show] + before_filter :issue, only: [:edit, :update, :show, :toggle_subscription] # Allow read any issue before_filter :authorize_read_issue! @@ -18,17 +18,9 @@ class Projects::IssuesController < Projects::ApplicationController def index terms = params['issue_search'] - - @issues = issues_filtered - @issues = @issues.where("title LIKE ? OR description LIKE ?", "%#{terms}%", "%#{terms}%") if terms.present? - @issues = @issues.page(params[:page]).per(20) - - assignee_id, milestone_id = params[:assignee_id], params[:milestone_id] - @assignee = @project.team.find(assignee_id) if assignee_id.present? && !assignee_id.to_i.zero? - @milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero? - sort_param = params[:sort] || 'newest' - @sort = sort_param.humanize unless sort_param.empty? - @assignees = User.where(id: @project.issues.pluck(:assignee_id)) + @issues = get_issues_collection + @issues = @issues.full_search(terms) if terms.present? + @issues = @issues.page(params[:page]).per(PER_PAGE) respond_to do |format| format.html @@ -68,7 +60,7 @@ class Projects::IssuesController < Projects::ApplicationController respond_to do |format| format.html do if @issue.valid? - redirect_to project_issue_path(@project, @issue) + redirect_to issue_path(@issue) else render :new end @@ -86,7 +78,7 @@ class Projects::IssuesController < Projects::ApplicationController format.js format.html do if @issue.valid? - redirect_to [@project, @issue] + redirect_to issue_path(@issue) else render :edit end @@ -101,10 +93,16 @@ class Projects::IssuesController < Projects::ApplicationController end def bulk_update - result = Issues::BulkUpdateService.new(project, current_user, params).execute + result = Issues::BulkUpdateService.new(project, current_user, bulk_update_params).execute redirect_to :back, notice: "#{result[:count]} issues updated" end + def toggle_subscription + @issue.toggle_subscription(current_user) + + render nothing: true + end + protected def issue @@ -127,12 +125,6 @@ class Projects::IssuesController < Projects::ApplicationController return render_404 unless @project.issues_enabled end - def issues_filtered - params[:scope] = 'all' if params[:scope].blank? - params[:state] = 'opened' if params[:state].blank? - @issues = IssuesFinder.new.execute(current_user, params.merge(project_id: @project.id)) - end - # Since iids are implemented only in 6.1 # user may navigate to issue page using old global ids. # @@ -142,7 +134,7 @@ class Projects::IssuesController < Projects::ApplicationController issue = @project.issues.find_by(id: params[:id]) if issue - redirect_to project_issue_path(@project, issue) + redirect_to issue_path(issue) return else raise ActiveRecord::RecordNotFound.new @@ -152,7 +144,16 @@ class Projects::IssuesController < Projects::ApplicationController def issue_params params.require(:issue).permit( :title, :assignee_id, :position, :description, - :milestone_id, :state_event, label_ids: [] + :milestone_id, :state_event, :task_num, label_ids: [] + ) + end + + def bulk_update_params + params.require(:update).permit( + :issues_ids, + :assignee_id, + :milestone_id, + :state_event ) end end diff --git a/app/controllers/projects/labels_controller.rb b/app/controllers/projects/labels_controller.rb index 87d1c94203..207a01ed3b 100644 --- a/app/controllers/projects/labels_controller.rb +++ b/app/controllers/projects/labels_controller.rb @@ -7,7 +7,7 @@ class Projects::LabelsController < Projects::ApplicationController respond_to :js, :html def index - @labels = @project.labels.order_by_name.page(params[:page]).per(20) + @labels = @project.labels.page(params[:page]).per(PER_PAGE) end def new @@ -18,7 +18,7 @@ class Projects::LabelsController < Projects::ApplicationController @label = @project.labels.create(label_params) if @label.valid? - redirect_to project_labels_path(@project) + redirect_to namespace_project_labels_path(@project.namespace, @project) else render 'new' end @@ -29,7 +29,7 @@ class Projects::LabelsController < Projects::ApplicationController def update if @label.update_attributes(label_params) - redirect_to project_labels_path(@project) + redirect_to namespace_project_labels_path(@project.namespace, @project) else render 'edit' end @@ -39,11 +39,12 @@ class Projects::LabelsController < Projects::ApplicationController Gitlab::IssuesLabels.generate(@project) if params[:redirect] == 'issues' - redirect_to project_issues_path(@project) + redirect_to namespace_project_issues_path(@project.namespace, @project) elsif params[:redirect] == 'merge_requests' - redirect_to project_merge_requests_path(@project) + redirect_to namespace_project_merge_requests_path(@project.namespace, + @project) else - redirect_to project_labels_path(@project) + redirect_to namespace_project_labels_path(@project.namespace, @project) end end @@ -51,8 +52,11 @@ class Projects::LabelsController < Projects::ApplicationController @label.destroy respond_to do |format| - format.html { redirect_to project_labels_path(@project), notice: 'Label was removed' } - format.js { render nothing: true } + format.html do + redirect_to(namespace_project_labels_path(@project.namespace, @project), + notice: 'Label was removed') + end + format.js end end diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb index 8dca040069..47ce846735 100644 --- a/app/controllers/projects/merge_requests_controller.rb +++ b/app/controllers/projects/merge_requests_controller.rb @@ -2,7 +2,7 @@ require 'gitlab/satellite/satellite' class Projects::MergeRequestsController < Projects::ApplicationController before_filter :module_enabled - before_filter :merge_request, only: [:edit, :update, :show, :diffs, :automerge, :automerge_check, :ci_status] + before_filter :merge_request, only: [:edit, :update, :show, :diffs, :automerge, :automerge_check, :ci_status, :toggle_subscription] before_filter :closes_issues, only: [:edit, :update, :show, :diffs] before_filter :validates_merge_request, only: [:show, :diffs] before_filter :define_show_vars, only: [:show, :diffs] @@ -17,26 +17,28 @@ class Projects::MergeRequestsController < Projects::ApplicationController before_filter :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort] def index - params[:sort] ||= 'newest' - params[:scope] = 'all' if params[:scope].blank? - params[:state] = 'opened' if params[:state].blank? + terms = params['issue_search'] + @merge_requests = get_merge_requests_collection + @merge_requests = @merge_requests.full_search(terms) if terms.present? + @merge_requests = @merge_requests.page(params[:page]).per(PER_PAGE) - @merge_requests = MergeRequestsFinder.new.execute(current_user, params.merge(project_id: @project.id)) - @merge_requests = @merge_requests.page(params[:page]).per(20) - - @sort = params[:sort].humanize - assignee_id, milestone_id = params[:assignee_id], params[:milestone_id] - @assignee = @project.team.find(assignee_id) if assignee_id.present? && !assignee_id.to_i.zero? - @milestone = @project.milestones.find(milestone_id) if milestone_id.present? && !milestone_id.to_i.zero? - @assignees = User.where(id: @project.merge_requests.pluck(:assignee_id)) + respond_to do |format| + format.html + format.json do + render json: { + html: view_to_html_string("projects/merge_requests/_merge_requests") + } + end + end end def show @note_counts = Note.where(commit_id: @merge_request.commits.map(&:id)). - group(:commit_id).count + group(:commit_id).count respond_to do |format| format.html + format.json { render json: @merge_request } format.diff { render text: @merge_request.to_diff(current_user) } format.patch { render text: @merge_request.to_patch(current_user) } end @@ -87,7 +89,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController @merge_request = MergeRequests::CreateService.new(project, current_user, merge_request_params).execute if @merge_request.valid? - redirect_to project_merge_request_path(@merge_request.target_project, @merge_request), notice: 'Merge request was successfully created.' + redirect_to(merge_request_path(@merge_request)) else @source_project = @merge_request.source_project @target_project = @merge_request.target_project @@ -102,7 +104,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController respond_to do |format| format.js format.html do - redirect_to [@merge_request.target_project, @merge_request], notice: 'Merge request was successfully updated.' + redirect_to([@merge_request.target_project.namespace.becomes(Namespace), + @merge_request.target_project, @merge_request]) + end + format.json do + render json: { + saved: @merge_request.valid?, + assignee_avatar_url: @merge_request.assignee.try(:avatar_url) + } end end else @@ -114,15 +123,15 @@ class Projects::MergeRequestsController < Projects::ApplicationController if @merge_request.unchecked? @merge_request.check_if_can_be_merged end - render json: {merge_status: @merge_request.merge_status_name} + + render json: { merge_status: @merge_request.merge_status_name } end def automerge return access_denied! unless allowed_to_merge? if @merge_request.open? && @merge_request.can_be_merged? - @merge_request.should_remove_source_branch = params[:should_remove_source_branch] - @merge_request.automerge!(current_user, params[:merge_commit_message]) + AutoMergeWorker.perform_async(@merge_request.id, current_user.id, params) @status = true else @status = false @@ -143,7 +152,6 @@ class Projects::MergeRequestsController < Projects::ApplicationController def update_branches @target_project = selected_target_project @target_branches = @target_project.repository.branch_names - @target_branches respond_to do |format| format.js @@ -151,12 +159,27 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def ci_status - status = @merge_request.source_project.ci_service.commit_status(merge_request.last_commit.sha) - response = {status: status} + 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.respond_to?(:commit_coverage) + coverage = ci_service.commit_coverage(merge_request.last_commit.sha, merge_request.source_branch) + end + + response = { + status: status, + coverage: coverage + } render json: response end + def toggle_subscription + @merge_request.toggle_subscription(current_user) + + render nothing: true + end + protected def selected_target_project @@ -217,6 +240,11 @@ class Projects::MergeRequestsController < Projects::ApplicationController @allowed_to_merge = allowed_to_merge? @show_merge_controls = @merge_request.open? && @commits.any? && @allowed_to_merge @source_branch = @merge_request.source_project.repository.find_branch(@merge_request.source_branch).try(:name) + + if @merge_request.locked_long_ago? + @merge_request.unlock_mr + @merge_request.close + end end def allowed_to_merge? @@ -229,20 +257,14 @@ class Projects::MergeRequestsController < Projects::ApplicationController end def allowed_to_push_code?(project, branch) - action = if project.protected_branch?(branch) - :push_code_to_protected_branches - else - :push_code - end - - can?(current_user, action, project) + ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch) end def merge_request_params params.require(:merge_request).permit( :title, :assignee_id, :source_project_id, :source_branch, :target_project_id, :target_branch, :milestone_id, - :state_event, :description, label_ids: [] + :state_event, :description, :task_num, label_ids: [] ) end end diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb index d338cdedfa..b49b549547 100644 --- a/app/controllers/projects/milestones_controller.rb +++ b/app/controllers/projects/milestones_controller.rb @@ -11,14 +11,14 @@ class Projects::MilestonesController < Projects::ApplicationController respond_to :html def index - @milestones = case params[:f] + @milestones = case params[:state] when 'all'; @project.milestones.order("state, due_date DESC") when 'closed'; @project.milestones.closed.order("due_date DESC") else @project.milestones.active.order("due_date ASC") end @milestones = @milestones.includes(:project) - @milestones = @milestones.page(params[:page]).per(20) + @milestones = @milestones.page(params[:page]).per(PER_PAGE) end def new @@ -40,7 +40,8 @@ class Projects::MilestonesController < Projects::ApplicationController @milestone = Milestones::CreateService.new(project, current_user, milestone_params).execute if @milestone.save - redirect_to project_milestone_path(@project, @milestone) + redirect_to namespace_project_milestone_path(@project.namespace, + @project, @milestone) else render "new" end @@ -53,7 +54,8 @@ class Projects::MilestonesController < Projects::ApplicationController format.js format.html do if @milestone.valid? - redirect_to [@project, @milestone] + redirect_to namespace_project_milestone_path(@project.namespace, + @project, @milestone) else render :edit end @@ -67,7 +69,7 @@ class Projects::MilestonesController < Projects::ApplicationController @milestone.destroy respond_to do |format| - format.html { redirect_to project_milestones_path } + format.html { redirect_to namespace_project_milestones_path } format.js { render nothing: true } end end @@ -103,7 +105,9 @@ class Projects::MilestonesController < Projects::ApplicationController end def module_enabled - return render_404 unless @project.issues_enabled + unless @project.issues_enabled || @project.merge_requests_enabled + return render_404 + end end def milestone_params diff --git a/app/controllers/projects/network_controller.rb b/app/controllers/projects/network_controller.rb index 9832495c64..83d1c1daca 100644 --- a/app/controllers/projects/network_controller.rb +++ b/app/controllers/projects/network_controller.rb @@ -2,10 +2,9 @@ class Projects::NetworkController < Projects::ApplicationController include ExtractsPath include ApplicationHelper - # Authorize - before_filter :authorize_read_project! - before_filter :authorize_code_access! before_filter :require_non_empty_project + before_filter :assign_ref_vars + before_filter :authorize_download_code! def show respond_to do |format| diff --git a/app/controllers/projects/new_tree_controller.rb b/app/controllers/projects/new_tree_controller.rb deleted file mode 100644 index 3a51a78ef6..0000000000 --- a/app/controllers/projects/new_tree_controller.rb +++ /dev/null @@ -1,20 +0,0 @@ -class Projects::NewTreeController < Projects::BaseTreeController - before_filter :require_branch_head - before_filter :authorize_push! - - def show - end - - def update - file_path = File.join(@path, File.basename(params[:file_name])) - result = Files::CreateService.new(@project, current_user, params, @ref, file_path).execute - - if result[:status] == :success - flash[:notice] = "Your changes have been successfully committed" - redirect_to project_blob_path(@project, File.join(@ref, file_path)) - else - flash[:alert] = result[:error] - render :show - end - end -end diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb index 2154b6ed2e..868629a0bc 100644 --- a/app/controllers/projects/notes_controller.rb +++ b/app/controllers/projects/notes_controller.rb @@ -3,10 +3,10 @@ class Projects::NotesController < Projects::ApplicationController before_filter :authorize_read_note! before_filter :authorize_write_note!, only: [:create] before_filter :authorize_admin_note!, only: [:update, :destroy] + before_filter :find_current_user_notes, except: [:destroy, :delete_attachment] def index current_fetched_at = Time.now.to_i - @notes = NotesFinder.new.execute(project, current_user, params) notes_json = { notes: [], last_fetched_at: current_fetched_at } @@ -30,8 +30,10 @@ class Projects::NotesController < Projects::ApplicationController end def update - note.update_attributes(note_params) - note.reset_events_cache + if note.editable? + note.update_attributes(note_params) + note.reset_events_cache + end respond_to do |format| format.json { render_note_json(note) } @@ -40,8 +42,10 @@ class Projects::NotesController < Projects::ApplicationController end def destroy - note.destroy - note.reset_events_cache + if note.editable? + note.destroy + note.reset_events_cache + end respond_to do |format| format.js { render nothing: true } @@ -57,10 +61,6 @@ class Projects::NotesController < Projects::ApplicationController end end - def preview - render text: view_context.markdown(params[:note]) - end - private def note @@ -116,4 +116,10 @@ class Projects::NotesController < Projects::ApplicationController :attachment, :line_code, :commit_id ) end + + private + + def find_current_user_notes + @notes = NotesFinder.new.execute(project, current_user, params) + end end diff --git a/app/controllers/projects/project_members_controller.rb b/app/controllers/projects/project_members_controller.rb new file mode 100644 index 0000000000..72967a26ff --- /dev/null +++ b/app/controllers/projects/project_members_controller.rb @@ -0,0 +1,98 @@ +class Projects::ProjectMembersController < Projects::ApplicationController + # Authorize + before_filter :authorize_admin_project!, except: :leave + + layout "project_settings" + + def index + @project_members = @project.project_members + @project_members = @project_members.non_invite unless can?(current_user, :admin_project, @project) + + if params[:search].present? + users = @project.users.search(params[:search]).to_a + @project_members = @project_members.where(user_id: users) + end + + @project_members = @project_members.order('access_level DESC') + + @group = @project.group + if @group + @group_members = @group.group_members + @group_members = @group_members.non_invite unless can?(current_user, :admin_group, @group) + + if params[:search].present? + users = @group.users.search(params[:search]).to_a + @group_members = @group_members.where(user_id: users) + end + + @group_members = @group_members.order('access_level DESC').limit(20) + end + + @project_member = @project.project_members.new + end + + def new + @project_member = @project.project_members.new + end + + def create + @project.team.add_users(params[:user_ids].split(','), params[:access_level], current_user) + + redirect_to namespace_project_project_members_path(@project.namespace, @project) + end + + def update + @project_member = @project.project_members.find(params[:id]) + @project_member.update_attributes(member_params) + end + + def destroy + @project_member = @project.project_members.find(params[:id]) + @project_member.destroy + + respond_to do |format| + format.html do + redirect_to namespace_project_project_members_path(@project.namespace, @project) + end + format.js { render nothing: true } + end + end + + def resend_invite + redirect_path = namespace_project_project_members_path(@project.namespace, @project) + + @project_member = @project.project_members.find(params[:id]) + + if @project_member.invite? + @project_member.resend_invite + + redirect_to redirect_path, notice: 'The invitation was successfully resent.' + else + redirect_to redirect_path, alert: 'The invitation has already been accepted.' + end + end + + def leave + @project.project_members.find_by(user_id: current_user).destroy + + respond_to do |format| + format.html { redirect_to :back } + format.js { render nothing: true } + end + end + + def apply_import + giver = Project.find(params[:source_project_id]) + status = @project.team.import(giver, current_user) + notice = status ? "Successfully imported" : "Import failed" + + redirect_to(namespace_project_project_members_path(project.namespace, project), + notice: notice) + end + + protected + + def member_params + params.require(:project_member).permit(:user_id, :access_level) + end +end diff --git a/app/controllers/projects/protected_branches_controller.rb b/app/controllers/projects/protected_branches_controller.rb index bd31b1d3c5..ac36ac6fcd 100644 --- a/app/controllers/projects/protected_branches_controller.rb +++ b/app/controllers/projects/protected_branches_controller.rb @@ -12,14 +12,33 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController def create @project.protected_branches.create(protected_branch_params) - redirect_to project_protected_branches_path(@project) + redirect_to namespace_project_protected_branches_path(@project.namespace, + @project) + end + + def update + protected_branch = @project.protected_branches.find(params[:id]) + + if protected_branch && + protected_branch.update_attributes( + developers_can_push: params[:developers_can_push] + ) + + respond_to do |format| + format.json { render json: protected_branch, status: :ok } + end + else + respond_to do |format| + format.json { render json: protected_branch.errors, status: :unprocessable_entity } + end + end end def destroy @project.protected_branches.find(params[:id]).destroy respond_to do |format| - format.html { redirect_to project_protected_branches_path } + format.html { redirect_to namespace_project_protected_branches_path } format.js { render nothing: true } end end @@ -27,6 +46,6 @@ class Projects::ProtectedBranchesController < Projects::ApplicationController private def protected_branch_params - params.require(:protected_branch).permit(:name) + params.require(:protected_branch).permit(:name, :developers_can_push) end end diff --git a/app/controllers/projects/raw_controller.rb b/app/controllers/projects/raw_controller.rb index 5ec9c576a6..b1a029ce69 100644 --- a/app/controllers/projects/raw_controller.rb +++ b/app/controllers/projects/raw_controller.rb @@ -2,10 +2,9 @@ class Projects::RawController < Projects::ApplicationController include ExtractsPath - # Authorize - before_filter :authorize_read_project! - before_filter :authorize_code_access! before_filter :require_non_empty_project + before_filter :assign_ref_vars + before_filter :authorize_download_code! def show @blob = @repository.blob_at(@commit.id, @path) @@ -36,4 +35,3 @@ class Projects::RawController < Projects::ApplicationController end end end - diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb index 7997c726fb..ec3b2b8d75 100644 --- a/app/controllers/projects/refs_controller.rb +++ b/app/controllers/projects/refs_controller.rb @@ -1,22 +1,23 @@ class Projects::RefsController < Projects::ApplicationController include ExtractsPath - # Authorize - before_filter :authorize_read_project! - before_filter :authorize_code_access! before_filter :require_non_empty_project + before_filter :assign_ref_vars + before_filter :authorize_download_code! def switch respond_to do |format| format.html do new_path = if params[:destination] == "tree" - project_tree_path(@project, (@id)) + namespace_project_tree_path(@project.namespace, @project, + (@id)) elsif params[:destination] == "blob" - project_blob_path(@project, (@id)) + namespace_project_blob_path(@project.namespace, @project, + (@id)) elsif params[:destination] == "graph" - project_network_path(@project, @id, @options) + namespace_project_network_path(@project.namespace, @project, @id, @options) else - project_commits_path(@project, @id) + namespace_project_commits_path(@project.namespace, @project, @id) end redirect_to new_path @@ -32,19 +33,19 @@ class Projects::RefsController < Projects::ApplicationController def logs_tree @offset = if params[:offset].present? - params[:offset].to_i - else - 0 - end + params[:offset].to_i + else + 0 + end @limit = 25 @path = params[:path] contents = [] - contents += tree.trees - contents += tree.blobs - contents += tree.submodules + contents.push(*tree.trees) + contents.push(*tree.blobs) + contents.push(*tree.submodules) @logs = contents[@offset, @limit].to_a.map do |content| file = @path ? File.join(@path, content.name) : content.name @@ -54,5 +55,10 @@ class Projects::RefsController < Projects::ApplicationController commit: last_commit } end + + respond_to do |format| + format.html { render_404 } + format.js + end end end diff --git a/app/controllers/projects/repositories_controller.rb b/app/controllers/projects/repositories_controller.rb index f30eaadd92..96defb0c72 100644 --- a/app/controllers/projects/repositories_controller.rb +++ b/app/controllers/projects/repositories_controller.rb @@ -1,27 +1,28 @@ class Projects::RepositoriesController < Projects::ApplicationController # Authorize - before_filter :authorize_read_project! - before_filter :authorize_code_access! - before_filter :require_non_empty_project + before_filter :require_non_empty_project, except: :create + before_filter :authorize_download_code! + before_filter :authorize_admin_project!, only: :create - def stats - @stats = Gitlab::Git::Stats.new(@repository.raw, @repository.root_ref) - @graph = @stats.graph + def create + @project.create_repository + + redirect_to project_path(@project) end def archive - unless can?(current_user, :download_code, @project) - render_404 and return + begin + file_path = ArchiveRepositoryService.new(@project, params[:ref], params[:format]).execute + rescue + return head :not_found end - file_path = ArchiveRepositoryService.new.execute(@project, params[:ref], params[:format]) - if file_path # Send file to user response.headers["Content-Length"] = File.open(file_path).size.to_s send_file file_path else - render_404 + redirect_to request.fullpath end end end diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb index b143dec3a9..9a484c109b 100644 --- a/app/controllers/projects/services_controller.rb +++ b/app/controllers/projects/services_controller.rb @@ -9,7 +9,7 @@ class Projects::ServicesController < Projects::ApplicationController def index @project.build_missing_services - @services = @project.services.reload + @services = @project.services.visible.reload end def edit @@ -17,18 +17,25 @@ class Projects::ServicesController < Projects::ApplicationController def update if @service.update_attributes(service_params) - redirect_to edit_project_service_path(@project, @service.to_param) + redirect_to( + edit_namespace_project_service_path(@project.namespace, @project, + @service.to_param, notice: + 'Successfully updated.') + ) else render 'edit' end end def test - data = GitPushService.new.sample_data(project, current_user) + data = Gitlab::PushDataBuilder.build_sample(project, current_user) + if @service.execute(data) + message = { notice: 'We sent a request to the provided URL' } + else + message = { alert: 'We tried to send a request to the provided URL but an error occured' } + end - @service.execute(data) - - redirect_to :back + redirect_to :back, message end private @@ -40,7 +47,13 @@ class Projects::ServicesController < Projects::ApplicationController def service_params params.require(:service).permit( :title, :token, :type, :active, :api_key, :subdomain, - :room, :recipients, :project_url + :room, :recipients, :project_url, :webhook, + :user_key, :device, :priority, :sound, :bamboo_url, :username, :password, + :build_key, :server, :teamcity_url, :build_type, + :description, :issues_url, :new_issue_url, :restrict_to_branch, :channel, + :colorize_messages, :channels, + :push_events, :issues_events, :merge_requests_events, :tag_push_events, + :note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url ) end end diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb index 2502697311..ed26840037 100644 --- a/app/controllers/projects/snippets_controller.rb +++ b/app/controllers/projects/snippets_controller.rb @@ -17,7 +17,10 @@ class Projects::SnippetsController < Projects::ApplicationController respond_to :html def index - @snippets = @project.snippets.fresh.non_expired + @snippets = SnippetsFinder.new.execute(current_user, { + filter: :by_project, + project: @project + }) end def new @@ -25,25 +28,22 @@ class Projects::SnippetsController < Projects::ApplicationController end def create - @snippet = @project.snippets.build(snippet_params) - @snippet.author = current_user - - if @snippet.save - redirect_to project_snippet_path(@project, @snippet) - else - respond_with(@snippet) - end + @snippet = CreateSnippetService.new(@project, current_user, + snippet_params).execute + respond_with(@snippet, + location: namespace_project_snippet_path(@project.namespace, + @project, @snippet)) end def edit end def update - if @snippet.update_attributes(snippet_params) - redirect_to project_snippet_path(@project, @snippet) - else - respond_with(@snippet) - end + UpdateSnippetService.new(project, current_user, @snippet, + snippet_params).execute + respond_with(@snippet, + location: namespace_project_snippet_path(@project.namespace, + @project, @snippet)) end def show @@ -57,15 +57,15 @@ class Projects::SnippetsController < Projects::ApplicationController @snippet.destroy - redirect_to project_snippets_path(@project) + redirect_to namespace_project_snippets_path(@project.namespace, @project) end def raw send_data( @snippet.content, - type: "text/plain", + type: 'text/plain; charset=utf-8', disposition: 'inline', - filename: @snippet.file_name + filename: @snippet.sanitized_file_name ) end @@ -88,6 +88,6 @@ class Projects::SnippetsController < Projects::ApplicationController end def snippet_params - params.require(:project_snippet).permit(:title, :content, :file_name, :private) + params.require(:project_snippet).permit(:title, :content, :file_name, :private, :visibility_level) end end diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index e03a9f4d66..83f4937bce 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -1,34 +1,37 @@ class Projects::TagsController < Projects::ApplicationController # Authorize - before_filter :authorize_read_project! before_filter :require_non_empty_project - - before_filter :authorize_code_access! - before_filter :authorize_push!, only: [:create] + before_filter :authorize_download_code! + before_filter :authorize_push_code!, only: [:create] before_filter :authorize_admin_project!, only: [:destroy] def index sorted = VersionSorter.rsort(@repository.tag_names) - @tags = Kaminari.paginate_array(sorted).page(params[:page]).per(30) + @tags = Kaminari.paginate_array(sorted).page(params[:page]).per(PER_PAGE) end def create - @tag = CreateTagService.new.execute(@project, params[:tag_name], - params[:ref], current_user) + result = CreateTagService.new(@project, current_user). + execute(params[:tag_name], params[:ref], params[:message]) - redirect_to project_tags_path(@project) + if result[:status] == :success + @tag = result[:tag] + redirect_to namespace_project_tags_path(@project.namespace, @project) + else + @error = result[:message] + render action: 'new' + end end def destroy - tag = @repository.find_tag(params[:id]) - - if tag && @repository.rm_tag(tag.name) - Event.create_ref_event(@project, current_user, tag, 'rm', 'refs/tags') - end + DeleteTagService.new(project, current_user).execute(params[:id]) respond_to do |format| - format.html { redirect_to project_tags_path } - format.js { render nothing: true } + format.html do + redirect_to namespace_project_tags_path(@project.namespace, + @project) + end + format.js end end end diff --git a/app/controllers/projects/team_members_controller.rb b/app/controllers/projects/team_members_controller.rb deleted file mode 100644 index 1de5bac9ee..0000000000 --- a/app/controllers/projects/team_members_controller.rb +++ /dev/null @@ -1,74 +0,0 @@ -class Projects::TeamMembersController < Projects::ApplicationController - # Authorize - before_filter :authorize_admin_project!, except: :leave - - layout "project_settings" - - def index - @group = @project.group - @users_projects = @project.users_projects.order('project_access DESC') - end - - def new - @user_project_relation = project.users_projects.new - end - - def create - users = User.where(id: params[:user_ids].split(',')) - - @project.team << [users, params[:project_access]] - - if params[:redirect_to] - redirect_to params[:redirect_to] - else - redirect_to project_team_index_path(@project) - end - end - - def update - @user_project_relation = project.users_projects.find_by(user_id: member) - @user_project_relation.update_attributes(member_params) - - unless @user_project_relation.valid? - flash[:alert] = "User should have at least one role" - end - redirect_to project_team_index_path(@project) - end - - def destroy - @user_project_relation = project.users_projects.find_by(user_id: member) - @user_project_relation.destroy - - respond_to do |format| - format.html { redirect_to project_team_index_path(@project) } - format.js { render nothing: true } - end - end - - def leave - project.users_projects.find_by(user_id: current_user).destroy - - respond_to do |format| - format.html { redirect_to :back } - format.js { render nothing: true } - end - end - - def apply_import - giver = Project.find(params[:source_project_id]) - status = @project.team.import(giver) - notice = status ? "Successfully imported" : "Import failed" - - redirect_to project_team_index_path(project), notice: notice - end - - protected - - def member - @member ||= User.find_by(username: params[:id]) - end - - def member_params - params.require(:team_member).permit(:user_id, :project_access) - end -end diff --git a/app/controllers/projects/tree_controller.rb b/app/controllers/projects/tree_controller.rb index 4d033b3684..b23010bf59 100644 --- a/app/controllers/projects/tree_controller.rb +++ b/app/controllers/projects/tree_controller.rb @@ -1,10 +1,18 @@ # Controller for viewing a repository's file structure -class Projects::TreeController < Projects::BaseTreeController - def show +class Projects::TreeController < Projects::ApplicationController + include ExtractsPath + before_filter :require_non_empty_project, except: [:new, :create] + before_filter :assign_ref_vars + before_filter :authorize_download_code! + + def show if tree.entries.empty? if @repository.blob_at(@commit.id, @path) - redirect_to project_blob_path(@project, File.join(@ref, @path)) and return + redirect_to( + namespace_project_blob_path(@project.namespace, @project, + File.join(@ref, @path)) + ) and return else return not_found! end diff --git a/app/controllers/projects/uploads_controller.rb b/app/controllers/projects/uploads_controller.rb new file mode 100644 index 0000000000..276dced865 --- /dev/null +++ b/app/controllers/projects/uploads_controller.rb @@ -0,0 +1,56 @@ +class Projects::UploadsController < Projects::ApplicationController + layout 'project' + + # We want to skip these filters for only the `show` action if `image?` is true, + # but `skip_before_filter` doesn't work with both `only` and `if`, so we accomplish the same like this. + skipped_filters = [:authenticate_user!, :reject_blocked!, :project, :repository] + skip_before_filter *skipped_filters, only: [:show] + before_filter *skipped_filters, only: [:show], unless: :image? + + def create + link_to_file = ::Projects::UploadService.new(project, params[:file]). + execute + + respond_to do |format| + if link_to_file + format.json do + render json: { link: link_to_file } + end + else + format.json do + render json: 'Invalid file.', status: :unprocessable_entity + end + end + end + end + + def show + return not_found! if uploader.nil? || !uploader.file.exists? + + disposition = uploader.image? ? 'inline' : 'attachment' + send_file uploader.file.path, disposition: disposition + end + + def uploader + return @uploader if defined?(@uploader) + + namespace = params[:namespace_id] + id = params[:project_id] + + file_project = Project.find_with_namespace("#{namespace}/#{id}") + + if file_project.nil? + @uploader = nil + return + end + + @uploader = FileUploader.new(file_project, params[:secret]) + @uploader.retrieve_from_store!(params[:filename]) + + @uploader + end + + def image? + uploader && uploader.file.exists? && uploader.image? + end +end diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb index 0e03956e73..aeb7f0699f 100644 --- a/app/controllers/projects/wikis_controller.rb +++ b/app/controllers/projects/wikis_controller.rb @@ -5,9 +5,10 @@ class Projects::WikisController < Projects::ApplicationController before_filter :authorize_write_wiki!, only: [:edit, :create, :history] before_filter :authorize_admin_wiki!, only: :destroy before_filter :load_project_wiki + include WikiHelper def pages - @wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page]).per(30) + @wiki_pages = Kaminari.paginate_array(@project_wiki.pages).page(params[:page]).per(PER_PAGE) end def show @@ -16,16 +17,16 @@ class Projects::WikisController < Projects::ApplicationController if @page render 'show' elsif file = @project_wiki.find_file(params[:id], params[:version_id]) - if file.on_disk? - send_file file.on_disk_path, disposition: 'inline' - else - send_data( - file.raw_data, - type: file.mime_type, - disposition: 'inline', - filename: file.name - ) - end + if file.on_disk? + send_file file.on_disk_path, disposition: 'inline' + else + send_data( + file.raw_data, + type: file.mime_type, + disposition: 'inline', + filename: file.name + ) + end else return render('empty') unless can?(current_user, :write_wiki, @project) @page = WikiPage.new(@project_wiki) @@ -45,7 +46,10 @@ class Projects::WikisController < Projects::ApplicationController return render('empty') unless can?(current_user, :write_wiki, @project) if @page.update(content, format, message) - redirect_to [@project, @page], notice: 'Wiki was successfully updated.' + redirect_to( + namespace_project_wiki_path(@project.namespace, @project, @page), + notice: 'Wiki was successfully updated.' + ) else render 'edit' end @@ -55,7 +59,10 @@ class Projects::WikisController < Projects::ApplicationController @page = WikiPage.new(@project_wiki) if @page.create(wiki_params) - redirect_to project_wiki_path(@project, @page), notice: 'Wiki was successfully updated.' + redirect_to( + namespace_project_wiki_path(@project.namespace, @project, @page), + notice: 'Wiki was successfully updated.' + ) else render action: "edit" end @@ -65,7 +72,10 @@ class Projects::WikisController < Projects::ApplicationController @page = @project_wiki.find_page(params[:id]) unless @page - redirect_to(project_wiki_path(@project, :home), notice: "Page not found") + redirect_to( + namespace_project_wiki_path(@project.namespace, @project, :home), + notice: "Page not found" + ) end end @@ -73,7 +83,10 @@ class Projects::WikisController < Projects::ApplicationController @page = @project_wiki.find_page(params[:id]) @page.delete if @page - redirect_to project_wiki_path(@project, :home), notice: "Page was successfully deleted" + redirect_to( + namespace_project_wiki_path(@project.namespace, @project, :home), + notice: "Page was successfully deleted" + ) end def git_access @@ -88,7 +101,7 @@ class Projects::WikisController < Projects::ApplicationController @project_wiki.wiki rescue ProjectWiki::CouldNotCreateWikiError => ex flash[:notice] = "Could not create Wiki Repository at this time. Please try again later." - redirect_to @project + redirect_to project_path(@project) return false end diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index f23afaf28f..0f28794b73 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -1,30 +1,34 @@ class ProjectsController < ApplicationController + prepend_before_filter :render_go_import, only: [:show] skip_before_filter :authenticate_user!, only: [:show] before_filter :project, except: [:new, :create] before_filter :repository, except: [:new, :create] # Authorize - before_filter :authorize_read_project!, except: [:index, :new, :create] - before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive, :retry_import] - before_filter :require_non_empty_project, only: [:blob, :tree, :graph] + before_filter :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive] + before_filter :set_title, only: [:new, :create] + before_filter :event_filter, only: :show layout 'navless', only: [:new, :create, :fork] - before_filter :set_title, only: [:new, :create] def new @project = Project.new end def edit - render 'edit', layout: "project_settings" + render 'edit', layout: 'project_settings' end def create @project = ::Projects::CreateService.new(current_user, project_params).execute - flash[:notice] = 'Project was successfully created.' if @project.saved? - respond_to do |format| - format.js + if @project.saved? + redirect_to( + project_path(@project), + notice: 'Project was successfully created.' + ) + else + render 'new' end end @@ -34,138 +38,112 @@ class ProjectsController < ApplicationController respond_to do |format| if status flash[:notice] = 'Project was successfully updated.' - format.html { redirect_to edit_project_path(@project), notice: 'Project was successfully updated.' } + format.html do + redirect_to( + edit_project_path(@project), + notice: 'Project was successfully updated.' + ) + end format.js else - format.html { render "edit", layout: "project_settings" } + format.html { render 'edit', layout: 'project_settings' } format.js end end end def transfer - ::Projects::TransferService.new(project, current_user, project_params).execute + transfer_params = params.permit(:new_namespace_id) + ::Projects::TransferService.new(project, current_user, transfer_params).execute + if @project.errors[:namespace_id].present? + flash[:alert] = @project.errors[:namespace_id].first + end end def show if @project.import_in_progress? - redirect_to import_project_path(@project) + redirect_to namespace_project_import_path(@project.namespace, @project) return end - return authenticate_user! unless @project.public? || current_user - limit = (params[:limit] || 20).to_i - @events = @project.events.recent - @events = event_filter.apply_filter(@events) - @events = @events.limit(limit).offset(params[:offset] || 0) @show_star = !(current_user && current_user.starred?(@project)) respond_to do |format| format.html do - if @project.empty_repo? - render "projects/empty", layout: user_layout + if @project.repository_exists? + if @project.empty_repo? + render 'projects/empty', layout: user_layout + else + @last_push = current_user.recent_push(@project.id) if current_user + render :show, layout: user_layout + end else - @last_push = current_user.recent_push(@project.id) if current_user - render :show, layout: user_layout + render 'projects/no_repo', layout: user_layout end end - format.json { pager_json("events/_events", @events.count) } + + format.json do + @events = @project.events.recent + @events = event_filter.apply_filter(@events).with_associations + @events = @events.limit(limit).offset(params[:offset] || 0) + pager_json('events/_events', @events.count) + end end end - def import - if project.import_finished? - redirect_to @project - return - end - end - - def retry_import - unless @project.import_failed? - redirect_to import_project_path(@project) - end - - @project.import_url = project_params[:import_url] - - if @project.save - @project.reload - @project.import_retry - end - - redirect_to import_project_path(@project) - end - def destroy - return access_denied! unless can?(current_user, :remove_project, project) + return access_denied! unless can?(current_user, :remove_project, @project) ::Projects::DestroyService.new(@project, current_user, {}).execute - respond_to do |format| - format.html { redirect_to root_path } - end - end - - def fork - @forked_project = ::Projects::ForkService.new(project, current_user).execute - respond_to do |format| format.html do - if @forked_project.saved? && @forked_project.forked? - redirect_to(@forked_project, notice: 'Project was successfully forked.') + flash[:alert] = 'Project deleted.' + + if request.referer.include?('/admin') + redirect_to admin_namespaces_projects_path else - @title = 'Fork project' - render "fork" + redirect_to dashboard_path end end - format.js end end def autocomplete_sources note_type = params['type'] note_id = params['type_id'] - participants = ::Projects::ParticipantsService.new(@project).execute(note_type, note_id) + autocomplete = ::Projects::AutocompleteService.new(@project) + participants = ::Projects::ParticipantsService.new(@project, current_user).execute(note_type, note_id) + @suggestions = { - emojis: Emoji.names.map { |e| { name: e, path: view_context.image_url("emoji/#{e}.png") } }, - issues: @project.issues.select([:iid, :title, :description]), - mergerequests: @project.merge_requests.select([:iid, :title, :description]), + emojis: autocomplete_emojis, + issues: autocomplete.issues, + mergerequests: autocomplete.merge_requests, members: participants } respond_to do |format| - format.json { render :json => @suggestions } + format.json { render json: @suggestions } end end def archive - return access_denied! unless can?(current_user, :archive_project, project) - project.archive! + return access_denied! unless can?(current_user, :archive_project, @project) + @project.archive! respond_to do |format| - format.html { redirect_to @project } + format.html { redirect_to project_path(@project) } end end def unarchive - return access_denied! unless can?(current_user, :archive_project, project) - project.unarchive! + return access_denied! unless can?(current_user, :archive_project, @project) + @project.unarchive! respond_to do |format| - format.html { redirect_to @project } - end - end - - def upload_image - link_to_image = ::Projects::ImageService.new(repository, params, root_url).execute - - respond_to do |format| - if link_to_image - format.json { render json: { link: link_to_image } } - else - format.json { render json: "Invalid file.", status: :unprocessable_entity } - end + format.html { redirect_to project_path(@project) } end end @@ -175,30 +153,46 @@ class ProjectsController < ApplicationController render json: { star_count: @project.star_count } end + def markdown_preview + render text: view_context.markdown(params[:md_text]) + end + private - def upload_path - base_dir = FileUploader.generate_dir - File.join(repository.path_with_namespace, base_dir) - end - - def accepted_images - %w(png jpg jpeg gif) - end - def set_title @title = 'New Project' end def user_layout - current_user ? "projects" : "public_projects" + current_user ? 'projects' : 'public_projects' end def project_params params.require(:project).permit( :name, :path, :description, :issues_tracker, :tag_list, :issues_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, :default_branch, - :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id + :wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar ) end + + def autocomplete_emojis + Rails.cache.fetch("autocomplete-emoji-#{Gemojione::VERSION}") do + Emoji.emojis.map do |name, emoji| + { + name: name, + path: view_context.image_url("emoji/#{emoji["unicode"]}.png") + } + end + end + end + + def render_go_import + return unless params["go-get"] == "1" + + @namespace = params[:namespace_id] + @id = params[:project_id] || params[:id] + @id = @id.gsub(/\.git\Z/, "") + + render "go_import", layout: false + end end diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb index 9e70978992..38d116a4ee 100644 --- a/app/controllers/registrations_controller.rb +++ b/app/controllers/registrations_controller.rb @@ -1,6 +1,10 @@ class RegistrationsController < Devise::RegistrationsController before_filter :signup_enabled? + def new + redirect_to(new_user_session_path) + end + def destroy current_user.destroy @@ -15,18 +19,20 @@ class RegistrationsController < Devise::RegistrationsController super end - def after_sign_up_path_for resource + def after_sign_up_path_for(_resource) new_user_session_path end - def after_inactive_sign_up_path_for resource + def after_inactive_sign_up_path_for(_resource) new_user_session_path end private def signup_enabled? - redirect_to new_user_session_path unless Gitlab.config.gitlab.signup_enabled + unless current_application_settings.signup_enabled? + redirect_to(new_user_session_path) + end end def sign_up_params diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index 8df84e9884..c5828d0b2d 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -2,21 +2,52 @@ class SearchController < ApplicationController include SearchHelper def show - @project = Project.find_by(id: params[:project_id]) if params[:project_id].present? - @group = Group.find_by(id: params[:group_id]) if params[:group_id].present? + return if params[:search].nil? || params[:search].blank? - if @project - return access_denied! unless can?(current_user, :download_code, @project) - - @search_results = Search::ProjectService.new(@project, current_user, params).execute - else - @search_results = Search::GlobalService.new(current_user, params).execute + if params[:project_id].present? + @project = Project.find_by(id: params[:project_id]) + @project = nil unless can?(current_user, :download_code, @project) end + + if params[:group_id].present? + @group = Group.find_by(id: params[:group_id]) + @group = nil unless can?(current_user, :read_group, @group) + end + + @scope = params[:scope] + @show_snippets = params[:snippets].eql? 'true' + + @search_results = + if @project + unless %w(blobs notes issues merge_requests wiki_blobs). + include?(@scope) + @scope = 'blobs' + end + + Search::ProjectService.new(@project, current_user, params).execute + elsif @show_snippets + unless %w(snippet_blobs snippet_titles).include?(@scope) + @scope = 'snippet_blobs' + end + + Search::SnippetService.new(current_user, params).execute + else + unless %w(projects issues merge_requests).include?(@scope) + @scope = 'projects' + end + Search::GlobalService.new(current_user, params).execute + end + @objects = @search_results.objects(@scope, params[:page]) end def autocomplete term = params[:term] - @project = Project.find(params[:project_id]) if params[:project_id].present? + + if params[:project_id].present? + @project = Project.find_by(id: params[:project_id]) + @project = nil unless can?(current_user, :read_project, @project) + end + @ref = params[:project_ref] if params[:project_ref].present? render json: search_autocomplete_opts(term).to_json diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb index 1bdba75c5e..3f11d7afe6 100644 --- a/app/controllers/sessions_controller.rb +++ b/app/controllers/sessions_controller.rb @@ -1,16 +1,16 @@ class SessionsController < Devise::SessionsController - def new - redirect_path = if request.referer.present? && (params['redirect_to_referer'] == 'yes') - referer_uri = URI(request.referer) - if referer_uri.host == Gitlab.config.gitlab.host - referer_uri.path - else - request.fullpath - end - else - request.fullpath - end + redirect_path = + if request.referer.present? && (params['redirect_to_referer'] == 'yes') + referer_uri = URI(request.referer) + if referer_uri.host == Gitlab.config.gitlab.host + referer_uri.path + else + request.fullpath + end + else + request.fullpath + end # Prevent a 'you are already signed in' message directly after signing: # we should never redirect to '/users/sign_in' after signing in successfully. @@ -18,10 +18,20 @@ class SessionsController < Devise::SessionsController store_location_for(:redirect, redirect_path) end + if Gitlab.config.ldap.enabled + @ldap_servers = Gitlab::LDAP::Config.servers + end + super end def create - super + super do |resource| + # User has successfully signed in, so clear any unused reset tokens + if resource.reset_password_token.present? + resource.update_attributes(reset_password_token: nil, + reset_password_sent_at: nil) + end + end end end diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index e75db61e68..cd52556b20 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -9,12 +9,14 @@ class SnippetsController < ApplicationController before_filter :set_title + skip_before_filter :authenticate_user!, only: [:index, :user_index, :show, :raw] + respond_to :html - layout 'navless' + layout :determine_layout def index - @snippets = Snippet.are_public.fresh.non_expired.page(params[:page]).per(20) + @snippets = SnippetsFinder.new.execute(current_user, filter: :all).page(params[:page]).per(PER_PAGE) end def user_index @@ -22,22 +24,11 @@ class SnippetsController < ApplicationController render_404 and return unless @user - @snippets = @user.snippets.fresh.non_expired - - if @user == current_user - @snippets = case params[:scope] - when 'are_public' then - @snippets.are_public - when 'are_private' then - @snippets.are_private - else - @snippets - end - else - @snippets = @snippets.are_public - end - - @snippets = @snippets.page(params[:page]).per(20) + @snippets = SnippetsFinder.new.execute(current_user, { + filter: :by_user, + user: @user, + scope: params[:scope] }). + page(params[:page]).per(PER_PAGE) if @user == current_user render 'current_user_index' @@ -51,25 +42,19 @@ class SnippetsController < ApplicationController end def create - @snippet = PersonalSnippet.new(snippet_params) - @snippet.author = current_user + @snippet = CreateSnippetService.new(nil, current_user, + snippet_params).execute - if @snippet.save - redirect_to snippet_path(@snippet) - else - respond_with @snippet - end + respond_with @snippet.becomes(Snippet) end def edit end def update - if @snippet.update_attributes(snippet_params) - redirect_to snippet_path(@snippet) - else - respond_with @snippet - end + UpdateSnippetService.new(nil, current_user, @snippet, + snippet_params).execute + respond_with @snippet.becomes(Snippet) end def show @@ -86,16 +71,23 @@ class SnippetsController < ApplicationController def raw send_data( @snippet.content, - type: "text/plain", + type: 'text/plain; charset=utf-8', disposition: 'inline', - filename: @snippet.file_name + filename: @snippet.sanitized_file_name ) end protected def snippet - @snippet ||= PersonalSnippet.where('author_id = :user_id or private is false', user_id: current_user.id).find(params[:id]) + @snippet ||= if current_user + PersonalSnippet.where("author_id = ? OR visibility_level IN (?)", + current_user.id, + [Snippet::PUBLIC, Snippet::INTERNAL]). + find(params[:id]) + else + PersonalSnippet.are_public.find(params[:id]) + end end def authorize_modify_snippet! @@ -108,9 +100,14 @@ class SnippetsController < ApplicationController def set_title @title = 'Snippets' + @title_url = snippets_path end def snippet_params - params.require(:personal_snippet).permit(:title, :content, :file_name, :private) + params.require(:personal_snippet).permit(:title, :content, :file_name, :private, :visibility_level) + end + + def determine_layout + current_user ? 'navless' : 'public_users' end end diff --git a/app/controllers/uploads_controller.rb b/app/controllers/uploads_controller.rb new file mode 100644 index 0000000000..c5f3da54ea --- /dev/null +++ b/app/controllers/uploads_controller.rb @@ -0,0 +1,71 @@ +class UploadsController < ApplicationController + skip_before_filter :authenticate_user! + before_filter :find_model, :authorize_access! + + def show + uploader = @model.send(upload_mount) + + unless uploader.file_storage? + return redirect_to uploader.url + end + + unless uploader.file && uploader.file.exists? + return not_found! + end + + disposition = uploader.image? ? 'inline' : 'attachment' + send_file uploader.file.path, disposition: disposition + end + + private + + def find_model + unless upload_model && upload_mount + return not_found! + end + + @model = upload_model.find(params[:id]) + end + + def authorize_access! + authorized = + case @model + when Project + can?(current_user, :read_project, @model) + when Group + can?(current_user, :read_group, @model) + when Note + can?(current_user, :read_project, @model.project) + else + # No authentication required for user avatars. + true + end + + return if authorized + + if current_user + not_found! + else + authenticate_user! + end + end + + def upload_model + upload_models = { + user: User, + project: Project, + note: Note, + group: Group + } + + upload_models[params[:model].to_sym] + end + + def upload_mount + upload_mounts = %w(avatar attachment file) + + if upload_mounts.include?(params[:mounted_as]) + params[:mounted_as] + end + end +end diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 0b442f5383..679d6897ce 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -1,28 +1,54 @@ class UsersController < ApplicationController - skip_before_filter :authenticate_user!, only: [:show] + skip_before_filter :authenticate_user! + before_filter :set_user layout :determine_layout def show - @user = User.find_by_username!(params[:username]) - - unless current_user || @user.public_profile? - return authenticate_user! - end - - # Projects user can view - authorized_projects_ids = ProjectsFinder.new.execute(current_user).pluck(:id) + @contributed_projects = contributed_projects.joined(@user). + reject(&:forked?) @projects = @user.personal_projects. - where(id: authorized_projects_ids) + where(id: authorized_projects_ids).includes(:namespace) # Collect only groups common for both users @groups = @user.groups & GroupsFinder.new.execute(current_user) - # Get user activity feed for projects common for both users - @events = @user.recent_events. - where(project_id: authorized_projects_ids).limit(20) - @title = @user.name + @title_url = user_path(@user) + + respond_to do |format| + format.html + + format.atom do + load_events + render layout: false + end + + format.json do + load_events + pager_json("events/_events", @events.count) + 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 + + def calendar_activities + @calendar_date = Date.parse(params[:date]) rescue nil + @events = [] + + if @calendar_date + @events = contributions_calendar.events_by_date(@calendar_date) + end + + render 'calendar_activities', layout: false end def determine_layout @@ -32,4 +58,40 @@ class UsersController < ApplicationController 'public_users' end end + + private + + def set_user + @user = User.find_by_username!(params[:username]) + + unless current_user || @user.public_profile? + return authenticate_user! + end + end + + def authorized_projects_ids + # Projects user can view + @authorized_projects_ids ||= + ProjectsFinder.new.execute(current_user).pluck(:id) + end + + def contributed_projects + @contributed_projects = Project. + where(id: authorized_projects_ids & @user.contributed_projects_ids). + includes(:namespace) + end + + def contributions_calendar + @contributions_calendar ||= Gitlab::ContributionsCalendar. + new(contributed_projects.reject(&:forked?), @user) + end + + def load_events + # Get user activity feed for projects common for both users + @events = @user.recent_events. + where(project_id: authorized_projects_ids). + with_associations + + @events = @events.limit(20).offset(params[:offset] || 0) + end end diff --git a/app/controllers/users_groups_controller.rb b/app/controllers/users_groups_controller.rb deleted file mode 100644 index a35a12a866..0000000000 --- a/app/controllers/users_groups_controller.rb +++ /dev/null @@ -1,48 +0,0 @@ -class UsersGroupsController < ApplicationController - before_filter :group - - # Authorize - before_filter :authorize_admin_group! - - layout 'group' - - def create - @group.add_users(params[:user_ids].split(','), params[:group_access]) - - redirect_to members_group_path(@group), notice: 'Users were successfully added.' - end - - def update - @member = @group.users_groups.find(params[:id]) - @member.update_attributes(member_params) - end - - def destroy - @users_group = @group.users_groups.find(params[:id]) - if can?(current_user, :destroy, @users_group) # May fail if last owner. - @users_group.destroy - respond_to do |format| - format.html { redirect_to members_group_path(@group), notice: 'User was successfully removed from group.' } - format.js { render nothing: true } - end - else - return render_403 - end - end - - protected - - def group - @group ||= Group.find_by(path: params[:group_id]) - end - - def authorize_admin_group! - unless can?(current_user, :manage_group, group) - return render_404 - end - end - - def member_params - params.require(:users_group).permit(:group_access, :user_id) - end -end diff --git a/app/finders/README.md b/app/finders/README.md index 47823c51ef..1f46518d23 100644 --- a/app/finders/README.md +++ b/app/finders/README.md @@ -1,7 +1,7 @@ # Finders -This type of classes responsible for collectiong items based on different conditions. -To prevent lookup methods in models like this: +This type of classes responsible for collection items based on different conditions. +To prevent lookup methods in models like this: ```ruby class Project @@ -13,10 +13,10 @@ end issues = project.issues_for_user_filtered_by(user, params) ``` -Better use this: +Better use this: ```ruby issues = IssuesFinder.new.execute(project, user, filter) ``` -It will help keep models thiner +It will help keep models thiner. diff --git a/app/finders/base_finder.rb b/app/finders/issuable_finder.rb similarity index 81% rename from app/finders/base_finder.rb rename to app/finders/issuable_finder.rb index ec5f5919d7..2c0702073d 100644 --- a/app/finders/base_finder.rb +++ b/app/finders/issuable_finder.rb @@ -1,4 +1,4 @@ -# BaseFinder +# IssuableFinder # # Used to filter Issues and MergeRequests collections by set of params # @@ -16,7 +16,11 @@ # label_name: string # sort: string # -class BaseFinder +require_relative 'projects_finder' + +class IssuableFinder + NONE = '0' + attr_accessor :current_user, :params def execute(current_user, params) @@ -31,6 +35,7 @@ class BaseFinder items = by_search(items) items = by_milestone(items) items = by_assignee(items) + items = by_author(items) items = by_label(items) items = sort(items) end @@ -41,12 +46,12 @@ class BaseFinder table_name = klass.table_name if project - if project.public? || (current_user && current_user.can?(:read_project, project)) + if Ability.abilities.allowed?(current_user, :read_project, project) project.send(table_name) else [] end - elsif current_user && params[:authorized_only].presence + elsif current_user && params[:authorized_only].presence && !current_user_related? klass.of_projects(current_user.authorized_projects).references(:project) else klass.of_projects(ProjectsFinder.new.execute(current_user)).references(:project) @@ -109,7 +114,7 @@ class BaseFinder def by_milestone(items) if params[:milestone_id].present? - items = items.where(milestone_id: (params[:milestone_id] == '0' ? nil : params[:milestone_id])) + items = items.where(milestone_id: (params[:milestone_id] == NONE ? nil : params[:milestone_id])) end items @@ -117,7 +122,15 @@ class BaseFinder def by_assignee(items) if params[:assignee_id].present? - items = items.where(assignee_id: (params[:assignee_id] == '0' ? nil : params[:assignee_id])) + items = items.where(assignee_id: (params[:assignee_id] == NONE ? nil : params[:assignee_id])) + end + + items + end + + def by_author(items) + if params[:author_id].present? + items = items.where(author_id: (params[:author_id] == NONE ? nil : params[:author_id])) end items @@ -140,4 +153,8 @@ class BaseFinder def project Project.where(id: params[:project_id]).first if params[:project_id].present? end + + def current_user_related? + params[:scope] == 'created-by-me' || params[:scope] == 'authored' || params[:scope] == 'assigned-to-me' + end end diff --git a/app/finders/issues_finder.rb b/app/finders/issues_finder.rb index 8e0c606249..20a2b0ce8f 100644 --- a/app/finders/issues_finder.rb +++ b/app/finders/issues_finder.rb @@ -15,7 +15,7 @@ # label_name: string # sort: string # -class IssuesFinder < BaseFinder +class IssuesFinder < IssuableFinder def klass Issue end diff --git a/app/finders/merge_requests_finder.rb b/app/finders/merge_requests_finder.rb index 3727149c8f..b258216d0d 100644 --- a/app/finders/merge_requests_finder.rb +++ b/app/finders/merge_requests_finder.rb @@ -15,7 +15,7 @@ # label_name: string # sort: string # -class MergeRequestsFinder < BaseFinder +class MergeRequestsFinder < IssuableFinder def klass MergeRequest end diff --git a/app/finders/notes_finder.rb b/app/finders/notes_finder.rb index bef82d7f0f..ab252821b5 100644 --- a/app/finders/notes_finder.rb +++ b/app/finders/notes_finder.rb @@ -7,20 +7,21 @@ class NotesFinder # Default to 0 to remain compatible with old clients last_fetched_at = Time.at(params.fetch(:last_fetched_at, 0).to_i) - notes = case target_type - when "commit" - project.notes.for_commit_id(target_id).not_inline.fresh - when "issue" - project.issues.find(target_id).notes.inc_author.fresh - when "merge_request" - project.merge_requests.find(target_id).mr_and_commit_notes.inc_author.fresh - when "snippet", "project_snippet" - project.snippets.find(target_id).notes.fresh - else - raise 'invalid target_type' - end + notes = + case target_type + when "commit" + project.notes.for_commit_id(target_id).not_inline + when "issue" + project.issues.find(target_id).notes.inc_author + when "merge_request" + project.merge_requests.find(target_id).mr_and_commit_notes.inc_author + when "snippet", "project_snippet" + project.snippets.find(target_id).notes + else + raise 'invalid target_type' + end # Use overlapping intervals to avoid worrying about race conditions - notes.where('updated_at > ?', last_fetched_at - FETCH_OVERLAP) + notes.where('updated_at > ?', last_fetched_at - FETCH_OVERLAP).fresh end end diff --git a/app/finders/projects_finder.rb b/app/finders/projects_finder.rb index 26898bad49..c81bb51583 100644 --- a/app/finders/projects_finder.rb +++ b/app/finders/projects_finder.rb @@ -19,10 +19,8 @@ class ProjectsFinder # Return ALL group projects group.projects else - projects_members = UsersProject.where( - project_id: group.projects, - user_id: current_user - ) + projects_members = ProjectMember.in_projects(group.projects). + with_user(current_user) if projects_members.any? # User is a project member @@ -34,7 +32,7 @@ class ProjectsFinder # group.projects.where( "projects.id IN (?) OR projects.visibility_level IN (?)", - projects_members.pluck(:project_id), + projects_members.pluck(:source_id), Project.public_and_internal_levels ) else diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb new file mode 100644 index 0000000000..07b5759443 --- /dev/null +++ b/app/finders/snippets_finder.rb @@ -0,0 +1,63 @@ +class SnippetsFinder + def execute(current_user, params = {}) + filter = params[:filter] + + case filter + when :all then + snippets(current_user).fresh.non_expired + when :by_user then + by_user(current_user, params[:user], params[:scope]) + when :by_project + by_project(current_user, params[:project]) + end + end + + private + + def snippets(current_user) + if current_user + Snippet.public_and_internal + else + # Not authenticated + # + # Return only: + # public snippets + Snippet.are_public + end + end + + def by_user(current_user, user, scope) + snippets = user.snippets.fresh.non_expired + + return snippets.are_public unless current_user + + if user == current_user + case scope + when 'are_internal' then + snippets.are_internal + when 'are_private' then + snippets.are_private + when 'are_public' then + snippets.are_public + else + snippets + end + else + snippets.public_and_internal + end + end + + def by_project(current_user, project) + snippets = project.snippets.fresh.non_expired + + if current_user + if project.team.member?(current_user.id) + snippets + else + snippets.public_and_internal + end + else + snippets.are_public + end + end +end diff --git a/app/finders/trending_projects_finder.rb b/app/finders/trending_projects_finder.rb index 32d7968924..a79bd47d98 100644 --- a/app/finders/trending_projects_finder.rb +++ b/app/finders/trending_projects_finder.rb @@ -8,7 +8,7 @@ class TrendingProjectsFinder # for period of time - ex. month projects.joins(:notes).where('notes.created_at > ?', start_date). select("projects.*, count(notes.id) as ncount"). - group("projects.id").order("ncount DESC") + group("projects.id").reorder("ncount DESC") end private diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb index 96e5d43a36..bb8d568380 100644 --- a/app/helpers/appearances_helper.rb +++ b/app/helpers/appearances_helper.rb @@ -14,4 +14,8 @@ module AppearancesHelper def brand_text nil end + + def brand_header_logo + image_tag 'logo-white.png' + end end diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb index e6d50bea4d..2b41d42161 100644 --- a/app/helpers/application_helper.rb +++ b/app/helpers/application_helper.rb @@ -5,8 +5,9 @@ module ApplicationHelper COLOR_SCHEMES = { 1 => 'white', 2 => 'dark', - 3 => 'solarized-dark', - 4 => 'monokai', + 3 => 'solarized-light', + 4 => 'solarized-dark', + 5 => 'monokai', } COLOR_SCHEMES.default = 'white' @@ -49,12 +50,39 @@ module ApplicationHelper args.any? { |v| v.to_s.downcase == action_name } end - def group_icon(group_path) - group = Group.find_by(path: group_path) - if group && group.avatar.present? - group.avatar.url - else - image_path('no_group_avatar.png') + def project_icon(project_id, options = {}) + project = + if project_id.is_a?(Project) + project = project_id + else + Project.find_with_namespace(project_id) + end + + if project.avatar_url + image_tag project.avatar_url, options + else # generated icon + project_identicon(project, options) + end + end + + def project_identicon(project, options = {}) + allowed_colors = { + red: 'FFEBEE', + purple: 'F3E5F5', + indigo: 'E8EAF6', + blue: 'E3F2FD', + teal: 'E0F2F1', + orange: 'FBE9E7', + gray: 'EEEEEE' + } + + options[:class] ||= '' + options[:class] << ' identicon' + bg_key = project.id % 7 + style = "background-color: ##{ allowed_colors.values[bg_key] }; color: #555" + + content_tag(:div, class: options[:class], style: style) do + project.name[0, 1].upcase end end @@ -81,24 +109,24 @@ module ApplicationHelper if project.repo_exists? time_ago_with_tooltip(project.repository.commit.committed_date) else - "Never" + 'Never' end rescue - "Never" + 'Never' end def grouped_options_refs repository = @project.repository options = [ - ["Branches", repository.branch_names], - ["Tags", VersionSorter.rsort(repository.tag_names)] + ['Branches', repository.branch_names], + ['Tags', VersionSorter.rsort(repository.tag_names)] ] # If reference is commit id - we should add it to branch/tag selectbox if(@ref && !options.flatten.include?(@ref) && - @ref =~ /^[0-9a-zA-Z]{6,52}$/) - options << ["Commit", [@ref]] + @ref =~ /\A[0-9a-zA-Z]{6,52}\z/) + options << ['Commit', [@ref]] end grouped_options_for_select(options, @ref || @project.default_branch) @@ -114,6 +142,10 @@ module ApplicationHelper Gitlab::Theme.css_class_by_id(current_user.try(:theme_id)) end + def theme_type + Gitlab::Theme.type_css_class_by_id(current_user.try(:theme_id)) + end + def user_color_scheme_class COLOR_SCHEMES[current_user.try(:color_scheme_id)] if defined?(current_user) end @@ -142,27 +174,15 @@ module ApplicationHelper Digest::SHA1.hexdigest string end - def authbutton(provider, size = 64) - file_name = "#{provider.to_s.split('_').first}_#{size}.png" - image_tag(image_path("authbuttons/#{file_name}"), alt: "Sign in with #{provider.to_s.titleize}") - end - def simple_sanitize(str) sanitize(str, tags: %w(a span)) end - def image_url(source) - # prevent relative_root_path being added twice (it's part of root_url and path_to_image) - root_url.sub(/#{root_path}$/, path_to_image(source)) - end - - alias_method :url_to_image, :image_url - def body_data_page path = controller.controller_path.split('/') namespace = path.first if path.second - [namespace, controller.controller_name, controller.action_name].compact.join(":") + [namespace, controller.controller_name, controller.action_name].compact.join(':') end # shortcut for gitlab config @@ -177,43 +197,24 @@ module ApplicationHelper def search_placeholder if @project && @project.persisted? - "Search in this project" + 'Search in this project' + elsif @snippet || @snippets || @show_snippets + 'Search snippets' elsif @group && @group.persisted? - "Search in this group" + 'Search in this group' else - "Search" + 'Search' end end - def first_line(str) - lines = str.split("\n") - line = lines.first - line += "..." if lines.size > 1 - line - end - def broadcast_message BroadcastMessage.current end - def highlight_js(&block) - string = capture(&block) - - content_tag :div, class: "highlighted-data #{user_color_scheme_class}" do - content_tag :div, class: 'highlight' do - content_tag :pre do - content_tag :code do - string.html_safe - end - end - end - end - end - def time_ago_with_tooltip(date, placement = 'top', html_class = 'time_ago') capture_haml do haml_tag :time, date.to_s, - class: html_class, datetime: date.getutc.iso8601, title: date.stamp("Aug 21, 2011 9:23pm"), + class: html_class, datetime: date.getutc.iso8601, title: date.stamp('Aug 21, 2011 9:23pm'), data: { toggle: 'tooltip', placement: placement } haml_tag :script, "$('." + html_class + "').timeago().tooltip()" @@ -235,39 +236,94 @@ module ApplicationHelper Gitlab::MarkdownHelper.gitlab_markdown?(filename) end - def spinner(text = nil, visible = false) - css_class = "loading" - css_class << " hide" unless visible - - content_tag :div, class: css_class do - content_tag(:i, nil, class: 'icon-spinner icon-spin') + text - end - end - - def link_to(name = nil, options = nil, html_options = nil, &block) - begin - uri = URI(options) - host = uri.host - absolute_uri = uri.absolute? - rescue URI::InvalidURIError, ArgumentError - host = nil - absolute_uri = nil - end - - # Add "nofollow" only to external links - if host && host != Gitlab.config.gitlab.host && absolute_uri - if html_options - if html_options[:rel] - html_options[:rel] << " nofollow" - else - html_options.merge!(rel: "nofollow") - end - else - html_options = Hash.new - html_options[:rel] = "nofollow" + # Overrides ActionView::Helpers::UrlHelper#link_to to add `rel="nofollow"` to + # external links + def link_to(name = nil, options = nil, html_options = {}) + if options.kind_of?(String) + if !options.start_with?('#', '/') + html_options = add_nofollow(options, html_options) end end super end + + # Add `"rel=nofollow"` to external links + # + # link - String link to check + # html_options - Hash of `html_options` passed to `link_to` + # + # Returns `html_options`, adding `rel: nofollow` for external links + def add_nofollow(link, html_options = {}) + begin + uri = URI(link) + + if uri && uri.absolute? && uri.host != Gitlab.config.gitlab.host + rel = html_options.fetch(:rel, '') + html_options[:rel] = (rel + ' nofollow').strip + end + rescue URI::Error + # noop + end + + html_options + end + + def escaped_autolink(text) + auto_link ERB::Util.html_escape(text), link: :urls + end + + def promo_host + 'about.gitlab.com' + end + + def promo_url + 'https://' + promo_host + end + + def page_filter_path(options = {}) + without = options.delete(:without) + + exist_opts = { + state: params[:state], + scope: params[:scope], + label_name: params[:label_name], + milestone_id: params[:milestone_id], + assignee_id: params[:assignee_id], + author_id: params[:author_id], + sort: params[:sort], + } + + options = exist_opts.merge(options) + + if without.present? + without.each do |key| + options.delete(key) + end + end + + path = request.path + path << "?#{options.to_param}" + path + end + + def outdated_browser? + browser.ie? && browser.version.to_i < 10 + end + + def path_to_key(key, admin = false) + if admin + admin_user_key_path(@user, key) + else + profile_key_path(key) + end + end + + def nav_sidebar_class + if nav_menu_collapsed? + "page-sidebar-collapsed" + else + "page-sidebar-expanded" + end + end end diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb new file mode 100644 index 0000000000..241d6075c9 --- /dev/null +++ b/app/helpers/application_settings_helper.rb @@ -0,0 +1,38 @@ +module ApplicationSettingsHelper + def gravatar_enabled? + current_application_settings.gravatar_enabled? + end + + def twitter_sharing_enabled? + current_application_settings.twitter_sharing_enabled? + end + + def signup_enabled? + current_application_settings.signup_enabled? + end + + def signin_enabled? + current_application_settings.signin_enabled? + end + + def extra_sign_in_text + current_application_settings.sign_in_text + end + + # Return a group of checkboxes that use Bootstrap's button plugin for a + # toggle button effect. + def restricted_level_checkboxes(help_block_id) + Gitlab::VisibilityLevel.options.map do |name, level| + checked = restricted_visibility_levels(true).include?(level) + css_class = 'btn btn-primary' + css_class += ' active' if checked + checkbox_name = 'application_setting[restricted_visibility_levels][]' + + label_tag(checkbox_name, class: css_class) do + check_box_tag(checkbox_name, level, checked, + autocomplete: 'off', + 'aria-describedby' => help_block_id) + name + end + end + end +end diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb index 11fbf1baae..4ea838ca44 100644 --- a/app/helpers/blob_helper.rb +++ b/app/helpers/blob_helper.rb @@ -1,18 +1,72 @@ module BlobHelper - def highlightjs_class(blob_name) - if blob_name.include?('.') - ext = blob_name.split('.').last - return 'language-' + ext - else - if no_highlight_files.include?(blob_name.downcase) - 'no-highlight' - else - blob_name.downcase - end + def highlight(blob_name, blob_content, nowrap = false) + formatter = Rugments::Formatters::HTML.new( + nowrap: nowrap, + cssclass: 'code highlight', + lineanchors: true, + lineanchorsid: 'LC' + ) + + begin + lexer = Rugments::Lexer.guess(filename: blob_name, source: blob_content) + rescue Rugments::Lexer::AmbiguousGuess + lexer = Rugments::Lexers::PlainText end + + formatter.format(lexer.lex(blob_content)).html_safe end def no_highlight_files - %w(credits changelog copying copyright license authors) + %w(credits changelog news copying copyright license authors) + end + + def edit_blob_link(project, ref, path, options = {}) + blob = + begin + project.repository.blob_at(ref, path) + rescue + nil + end + + if blob && blob.text? + text = 'Edit' + after = options[:after] || '' + from_mr = options[:from_merge_request_id] + link_opts = {} + link_opts[:from_merge_request_id] = from_mr if from_mr + cls = 'btn btn-small' + if allowed_tree_edit?(project, ref) + link_to(text, + namespace_project_edit_blob_path(project.namespace, project, + tree_join(ref, path), + link_opts), + class: cls + ) + else + content_tag :span, text, class: cls + ' disabled' + end + after.html_safe + else + '' + end + end + + def leave_edit_message + "Leave edit mode?\nAll unsaved changes will be lost." + end + + def editing_preview_title(filename) + if Gitlab::MarkdownHelper.previewable?(filename) + 'Preview' + else + 'Preview changes' + end + end + + # Return an image icon depending on the file mode and extension + # + # mode - File unix mode + # mode - File name + def blob_icon(mode, name) + icon("#{file_type_icon_class('file', mode, name)} fw") end end diff --git a/app/helpers/branches_helper.rb b/app/helpers/branches_helper.rb index 2ec2cc9615..d6eaa7d57b 100644 --- a/app/helpers/branches_helper.rb +++ b/app/helpers/branches_helper.rb @@ -11,12 +11,7 @@ module BranchesHelper def can_push_branch?(project, branch_name) return false unless project.repository.branch_names.include?(branch_name) - action = if project.protected_branch?(branch_name) - :push_code_to_protected_branches - else - :push_code - end - - current_user.can?(action, project) + + ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(branch_name) end end diff --git a/app/helpers/commits_helper.rb b/app/helpers/commits_helper.rb index f61aa25915..d13d80be29 100644 --- a/app/helpers/commits_helper.rb +++ b/app/helpers/commits_helper.rb @@ -16,38 +16,6 @@ module CommitsHelper commit_person_link(commit, options.merge(source: :committer)) end - def each_diff_line(diff, index) - Gitlab::DiffParser.new(diff.diff.lines.to_a, diff.new_path) - .each do |full_line, type, line_code, line_new, line_old| - yield(full_line, type, line_code, line_new, line_old) - end - end - - def each_diff_line_near(diff, index, expected_line_code) - max_number_of_lines = 16 - - prev_match_line = nil - prev_lines = [] - - each_diff_line(diff, index) do |full_line, type, line_code, line_new, line_old| - line = [full_line, type, line_code, line_new, line_old] - if line_code != expected_line_code - if type == "match" - prev_lines.clear - prev_match_line = line - else - prev_lines.push(line) - prev_lines.shift if prev_lines.length >= max_number_of_lines - end - else - yield(prev_match_line) if !prev_match_line.nil? - prev_lines.each { |ln| yield(ln) } - yield(line) - break - end - end - end - def image_diff_class(diff) if diff.deleted_file "deleted" @@ -63,30 +31,32 @@ module CommitsHelper escape_javascript(render "projects/commits/#{template}", commit: commit, project: project) unless commit.nil? end - def diff_line_content(line) - if line.blank? - "  " - else - line - end - end - # Breadcrumb links for a Project and, if applicable, a tree path def commits_breadcrumbs return unless @project && @ref # Add the root project link and the arrow icon crumbs = content_tag(:li) do - link_to(@project.path, project_commits_path(@project, @ref)) + link_to( + @project.path, + namespace_project_commits_path(@project.namespace, @project, @ref) + ) end if @path parts = @path.split('/') parts.each_with_index do |part, i| - crumbs += content_tag(:li) do + crumbs << content_tag(:li) do # The text is just the individual part, but the link needs all the parts before it - link_to part, project_commits_path(@project, tree_join(@ref, parts[0..i].join('/'))) + link_to( + part, + namespace_project_commits_path( + @project.namespace, + @project, + tree_join(@ref, parts[0..i].join('/')) + ) + ) end end end @@ -102,94 +72,55 @@ module CommitsHelper # Returns the sorted alphabetically links to branches, separated by a comma def commit_branches_links(project, branches) - branches.sort.map { |branch| link_to(branch, project_tree_path(project, branch)) }.join(", ").html_safe + branches.sort.map do |branch| + link_to( + namespace_project_tree_path(project.namespace, project, branch) + ) do + content_tag :span, class: 'label label-gray' do + icon('code-fork') + ' ' + branch + end + end + end.join(" ").html_safe end - def parallel_diff_lines(project, commit, diff, file) - old_file = project.repository.blob_at(commit.parent_id, diff.old_path) if commit.parent_id - deleted_lines = {} - added_lines = {} - each_diff_line(diff, 0) do |line, type, line_code, line_new, line_old| - if type == "old" - deleted_lines[line_old] = { line_code: line_code, type: type, line: line } - elsif type == "new" - added_lines[line_new] = { line_code: line_code, type: type, line: line } + # Returns the sorted links to tags, separated by a comma + def commit_tags_links(project, tags) + sorted = VersionSorter.rsort(tags) + sorted.map do |tag| + link_to( + namespace_project_commits_path(project.namespace, project, + project.repository.find_tag(tag).name) + ) do + content_tag :span, class: 'label label-gray' do + icon('tag') + ' ' + tag + end end - end - max_length = old_file ? [old_file.loc, file.loc].max : file.loc - - offset1 = 0 - offset2 = 0 - old_lines = [] - new_lines = [] - - max_length.times do |line_index| - line_index1 = line_index - offset1 - line_index2 = line_index - offset2 - deleted_line = deleted_lines[line_index1 + 1] - added_line = added_lines[line_index2 + 1] - old_line = old_file.lines[line_index1] if old_file - new_line = file.lines[line_index2] - - if deleted_line && added_line - elsif deleted_line - new_line = nil - offset2 += 1 - elsif added_line - old_line = nil - offset1 += 1 - end - - old_lines[line_index] = DiffLine.new - new_lines[line_index] = DiffLine.new - - # old - if line_index == 0 && diff.new_file - old_lines[line_index].type = :file_created - old_lines[line_index].content = 'File was created' - elsif deleted_line - old_lines[line_index].type = :deleted - old_lines[line_index].content = old_line - old_lines[line_index].num = line_index1 + 1 - old_lines[line_index].code = deleted_line[:line_code] - elsif old_line - old_lines[line_index].type = :no_change - old_lines[line_index].content = old_line - old_lines[line_index].num = line_index1 + 1 - else - old_lines[line_index].type = :added - end - - # new - if line_index == 0 && diff.deleted_file - new_lines[line_index].type = :file_deleted - new_lines[line_index].content = "File was deleted" - elsif added_line - new_lines[line_index].type = :added - new_lines[line_index].num = line_index2 + 1 - new_lines[line_index].content = new_line - new_lines[line_index].code = added_line[:line_code] - elsif new_line - new_lines[line_index].type = :no_change - new_lines[line_index].num = line_index2 + 1 - new_lines[line_index].content = new_line - else - new_lines[line_index].type = :deleted - end - end - - return old_lines, new_lines + end.join(" ").html_safe end def link_to_browse_code(project, commit) if current_controller?(:projects, :commits) if @repo.blob_at(commit.id, @path) - return link_to "Browse File »", project_blob_path(project, tree_join(commit.id, @path)), class: "pull-right" + return link_to( + "Browse File »", + namespace_project_blob_path(project.namespace, project, + tree_join(commit.id, @path)), + class: "pull-right" + ) elsif @path.present? - return link_to "Browse Dir »", project_tree_path(project, tree_join(commit.id, @path)), class: "pull-right" + return link_to( + "Browse Dir »", + namespace_project_tree_path(project.namespace, project, + tree_join(commit.id, @path)), + class: "pull-right" + ) end end - link_to "Browse Code »", project_tree_path(project, commit), class: "pull-right" + link_to( + "Browse Code »", + namespace_project_tree_path(project.namespace, project, commit), + class: "pull-right" + ) end protected @@ -203,19 +134,21 @@ module CommitsHelper # avatar: true will prepend the avatar image # size: size of the avatar image in px def commit_person_link(commit, options = {}) - source_name = commit.send "#{options[:source]}_name".to_sym - source_email = commit.send "#{options[:source]}_email".to_sym + user = commit.send(options[:source]) + + source_name = clean(commit.send "#{options[:source]}_name".to_sym) + source_email = clean(commit.send "#{options[:source]}_email".to_sym) - user = User.find_for_commit(source_email, source_name) - person_name = user.nil? ? source_name : user.name - person_email = user.nil? ? source_email : user.email + person_name = user.try(:name) || source_name + person_email = user.try(:email) || source_email - text = if options[:avatar] - avatar = image_tag(avatar_icon(person_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "") - %Q{#{avatar} #{person_name}} - else - person_name - end + text = + if options[:avatar] + avatar = image_tag(avatar_icon(person_email, options[:size]), class: "avatar #{"s#{options[:size]}" if options[:size]}", width: options[:size], alt: "") + %Q{#{avatar} #{person_name}} + else + person_name + end options = { class: "commit-#{options[:source]}-link has_tooltip", @@ -229,19 +162,22 @@ module CommitsHelper end end - def diff_file_mode_changed?(diff) - diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode - end - - def unfold_bottom_class(bottom) - (bottom) ? 'js-unfold-bottom' : '' - end - def view_file_btn(commit_sha, diff, project) - link_to project_blob_path(project, tree_join(commit_sha, diff.new_path)), - class: 'btn btn-small view-file js-view-file' do + link_to( + namespace_project_blob_path(project.namespace, project, + tree_join(commit_sha, diff.new_path)), + class: 'btn btn-small view-file js-view-file' + ) do raw('View file @') + content_tag(:span, commit_sha[0..6], class: 'commit-short-id') end end + + def truncate_sha(sha) + Commit.truncate_sha(sha) + end + + def clean(string) + Sanitize.clean(string, remove_contents: true) + end end diff --git a/app/helpers/compare_helper.rb b/app/helpers/compare_helper.rb index 5ff19b8829..01847c6b80 100644 --- a/app/helpers/compare_helper.rb +++ b/app/helpers/compare_helper.rb @@ -1,7 +1,7 @@ module CompareHelper def compare_to_mr_button? @project.merge_requests_enabled && - params[:from].present? && + params[:from].present? && params[:to].present? && @repository.branch_names.include?(params[:from]) && @repository.branch_names.include?(params[:to]) && @@ -10,6 +10,13 @@ module CompareHelper end def compare_mr_path - new_project_merge_request_path(@project, merge_request: {source_branch: params[:to], target_branch: params[:from]}) + new_namespace_project_merge_request_path( + @project.namespace, + @project, + merge_request: { + source_branch: params[:to], + target_branch: params[:from] + } + ) end end diff --git a/app/helpers/dashboard_helper.rb b/app/helpers/dashboard_helper.rb index c4e33e3308..c25b54eadc 100644 --- a/app/helpers/dashboard_helper.rb +++ b/app/helpers/dashboard_helper.rb @@ -1,76 +1,9 @@ module DashboardHelper - def filter_path(entity, options={}) - exist_opts = { - state: params[:state], - scope: params[:scope], - project_id: params[:project_id], - } - - options = exist_opts.merge(options) - - path = request.path - path << "?#{options.to_param}" - path + def assigned_issues_dashboard_path + issues_dashboard_path(assignee_id: current_user.id) end - def entities_per_project(project, entity) - case entity.to_sym - when :issue then @issues.where(project_id: project.id) - when :merge_request then @merge_requests.where(target_project_id: project.id) - else - [] - end.count - end - - def projects_dashboard_filter_path(options={}) - exist_opts = { - sort: params[:sort], - scope: params[:scope], - group: params[:group], - } - - options = exist_opts.merge(options) - - path = request.path - path << "?#{options.to_param}" - path - end - - def assigned_entities_count(current_user, entity, scope = nil) - items = current_user.send("assigned_" + entity.pluralize).opened - - if scope.kind_of?(Group) - items = items.of_group(scope) - elsif scope.kind_of?(Project) - items = items.of_projects(scope) - end - - items.count - end - - def authored_entities_count(current_user, entity, scope = nil) - items = current_user.send(entity.pluralize).opened - - if scope.kind_of?(Group) - items = items.of_group(scope) - elsif scope.kind_of?(Project) - items = items.of_projects(scope) - end - - items.count - end - - def authorized_entities_count(current_user, entity, scope = nil) - items = entity.classify.constantize.opened - - if scope.kind_of?(Group) - items = items.of_group(scope) - elsif scope.kind_of?(Project) - items = items.of_projects(scope) - else - items = items.of_projects(current_user.authorized_projects) - end - - items.count + def assigned_mrs_dashboard_path + merge_requests_dashboard_path(assignee_id: current_user.id) end end diff --git a/app/helpers/diff_helper.rb b/app/helpers/diff_helper.rb index ee4d4fbdff..4f42972a4d 100644 --- a/app/helpers/diff_helper.rb +++ b/app/helpers/diff_helper.rb @@ -1,14 +1,20 @@ module DiffHelper - def safe_diff_files(diffs) + def allowed_diff_size if diff_hard_limit_enabled? - diffs.first(Commit::DIFF_HARD_LIMIT_FILES) + Commit::DIFF_HARD_LIMIT_FILES else - diffs.first(Commit::DIFF_SAFE_FILES) + Commit::DIFF_SAFE_FILES end end - def show_diff_size_warninig?(diffs) - safe_diff_files(diffs).size < diffs.size + def safe_diff_files(diffs) + diffs.first(allowed_diff_size).map do |diff| + Gitlab::Diff::File.new(diff) + end + end + + def show_diff_size_warning?(diffs) + diffs.size > allowed_diff_size end def diff_hard_limit_enabled? @@ -19,4 +25,133 @@ module DiffHelper false end end + + def generate_line_code(file_path, line) + Gitlab::Diff::LineCode.generate(file_path, line.new_pos, line.old_pos) + end + + def parallel_diff(diff_file, index) + lines = [] + skip_next = false + + # Building array of lines + # + # [ + # left_type, left_line_number, left_line_content, left_line_code, + # right_line_type, right_line_number, right_line_content, right_line_code + # ] + # + diff_file.diff_lines.each do |line| + + full_line = line.text + type = line.type + line_code = generate_line_code(diff_file.file_path, line) + line_new = line.new_pos + line_old = line.old_pos + + next_line = diff_file.next_line(line.index) + + if next_line + next_line_code = generate_line_code(diff_file.file_path, next_line) + next_type = next_line.type + next_line = next_line.text + end + + if type == 'match' || type.nil? + # line in the right panel is the same as in the left one + line = [type, line_old, full_line, line_code, type, line_new, full_line, line_code] + lines.push(line) + elsif type == 'old' + if next_type == 'new' + # Left side has text removed, right side has text added + line = [type, line_old, full_line, line_code, next_type, line_new, next_line, next_line_code] + lines.push(line) + skip_next = true + elsif next_type == 'old' || next_type.nil? + # Left side has text removed, right side doesn't have any change + # No next line code, no new line number, no new line text + line = [type, line_old, full_line, line_code, next_type, nil, " ", nil] + lines.push(line) + end + elsif type == 'new' + if skip_next + # Change has been already included in previous line so no need to do it again + skip_next = false + next + else + # Change is only on the right side, left side has no change + line = [nil, nil, " ", line_code, type, line_new, full_line, line_code] + lines.push(line) + end + end + end + lines + end + + def unfold_bottom_class(bottom) + (bottom) ? 'js-unfold-bottom' : '' + end + + def diff_line_content(line) + if line.blank? + "  " + else + line + end + end + + def line_comments + @line_comments ||= @line_notes.select(&:active?).group_by(&:line_code) + end + + def organize_comments(type_left, type_right, line_code_left, line_code_right) + comments_left = comments_right = nil + + unless type_left.nil? && type_right == 'new' + comments_left = line_comments[line_code_left] + end + + unless type_left.nil? && type_right.nil? + comments_right = line_comments[line_code_right] + end + + [comments_left, comments_right] + end + + def inline_diff_btn + params_copy = params.dup + params_copy[:view] = 'inline' + # Always use HTML to handle case where JSON diff rendered this button + params_copy.delete(:format) + + link_to url_for(params_copy), id: "commit-diff-viewtype", class: (params[:view] != 'parallel' ? 'btn btn-sm active' : 'btn btn-sm') do + 'Inline' + end + end + + def parallel_diff_btn + params_copy = params.dup + params_copy[:view] = 'parallel' + # Always use HTML to handle case where JSON diff rendered this button + params_copy.delete(:format) + + link_to url_for(params_copy), id: "commit-diff-viewtype", class: (params[:view] == 'parallel' ? 'btn active btn-sm' : 'btn btn-sm') do + 'Side-by-side' + end + end + + def submodule_link(blob, ref) + tree, commit = submodule_links(blob, ref) + commit_id = if commit.nil? + blob.id[0..10] + else + link_to "#{blob.id[0..10]}", commit + end + + [ + content_tag(:span, link_to(truncate(blob.name, length: 40), tree)), + '@', + content_tag(:span, commit_id, class: 'monospace'), + ].join(' ').html_safe + end end diff --git a/app/helpers/emails_helper.rb b/app/helpers/emails_helper.rb new file mode 100644 index 0000000000..0df3ecc90b --- /dev/null +++ b/app/helpers/emails_helper.rb @@ -0,0 +1,38 @@ +module EmailsHelper + + # Google Actions + # https://developers.google.com/gmail/markup/reference/go-to-action + def email_action(url) + name = action_title(url) + if name + data = { + "@context" => "http://schema.org", + "@type" => "EmailMessage", + "action" => { + "@type" => "ViewAction", + "name" => name, + "url" => url, + } + } + + content_tag :script, type: 'application/ld+json' do + data.to_json.html_safe + end + end + end + + def action_title(url) + return unless url + ["merge_requests", "issues", "commit"].each do |action| + if url.split("/").include?(action) + return "View #{action.humanize.singularize}" + end + end + end + + def color_email_diff(diffcontent) + formatter = Rugments::Formatters::HTML.new(cssclass: "highlight", inline_theme: :github) + lexer = Rugments::Lexers::Diff.new + raw formatter.format(lexer.lex(diffcontent)) + end +end diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb index c7e8fdad7a..c9fd0f0362 100644 --- a/app/helpers/events_helper.rb +++ b/app/helpers/events_helper.rb @@ -10,79 +10,104 @@ module EventsHelper end def event_action_name(event) - target = if event.target_type - event.target_type.titleize.downcase - else - 'project' - end + target = if event.target_type + if event.note? + event.note_target_type + else + event.target_type.titleize.downcase + end + else + 'project' + end [event.action_name, target].join(" ") end - def event_filter_link key, tooltip + def event_filter_link(key, tooltip) key = key.to_s - inactive = if @event_filter.active? key - nil - else - 'inactive' - end + active = if @event_filter.active? key + 'active' + end - content_tag :div, class: "filter_icon #{inactive}" do - link_to request.path, class: 'has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => tooltip do - content_tag :i, nil, class: icon_for_event[key] + content_tag :li, class: "filter_icon #{active}" do + link_to request.path, class: 'has_tooltip event_filter_link', id: "#{key}_event_filter", 'data-original-title' => 'Filter by ' + tooltip.downcase do + icon(icon_for_event[key]) + content_tag(:span, ' ' + tooltip) end end end def icon_for_event { - EventFilter.push => "icon-upload-alt", - EventFilter.merged => "icon-check", - EventFilter.comments => "icon-comments", - EventFilter.team => "icon-user", + EventFilter.push => 'upload', + EventFilter.merged => 'check-square-o', + EventFilter.comments => 'comments', + EventFilter.team => 'user', } end def event_feed_title(event) - if event.issue? - "#{event.author_name} #{event.action_name} issue ##{event.target_iid}: #{event.issue_title} at #{event.project_name}" - elsif event.merge_request? - "#{event.author_name} #{event.action_name} MR ##{event.target_iid}: #{event.merge_request_title} at #{event.project_name}" - elsif event.push? - "#{event.author_name} #{event.push_action_name} #{event.ref_type} #{event.ref_name} at #{event.project_name}" - elsif event.membership_changed? - "#{event.author_name} #{event.action_name} #{event.project_name}" - elsif event.note? - "#{event.author_name} commented on #{event.note_target_type} ##{truncate event.note_target_iid} at #{event.project_name}" - else - "" + words = [] + words << event.author_name + words << event_action_name(event) + + if event.push? + words << event.ref_type + 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 << "at" + elsif event.target + words << "##{event.target_iid}:" + words << event.target.title if event.target.respond_to?(:title) + words << "at" end + + words << event.project_name + + words.join(" ") end def event_feed_url(event) if event.issue? - project_issue_url(event.project, event.issue) + namespace_project_issue_url(event.project.namespace, event.project, + event.issue) elsif event.merge_request? - project_merge_request_url(event.project, event.merge_request) + namespace_project_merge_request_url(event.project.namespace, + event.project, event.merge_request) + elsif event.note? && event.note_commit? + namespace_project_commit_url(event.project.namespace, event.project, + event.note_target) elsif event.note? if event.note_target if event.note_commit? - project_commit_path(event.project, event.note_commit_id, anchor: dom_id(event.target)) + namespace_project_commit_path(event.project.namespace, event.project, + event.note_commit_id, + anchor: dom_id(event.target)) elsif event.note_project_snippet? - project_snippet_path(event.project, event.note_target) + namespace_project_snippet_path(event.project.namespace, + event.project, event.note_target) else event_note_target_path(event) end end elsif event.push? - if event.push_with_commits? + if event.push_with_commits? && event.md_ref? if event.commits_count > 1 - project_compare_url(event.project, from: event.commit_from, to: event.commit_to) + namespace_project_compare_url(event.project.namespace, event.project, + from: event.commit_from, to: + event.commit_to) else - project_commit_url(event.project, id: event.commit_to) + namespace_project_commit_url(event.project.namespace, event.project, + id: event.commit_to) end else - project_commits_url(event.project, event.ref_name) + namespace_project_commits_url(event.project.namespace, event.project, + event.ref_name) end end end @@ -101,20 +126,30 @@ module EventsHelper def event_note_target_path(event) if event.note? && event.note_commit? - project_commit_path(event.project, event.note_target) + namespace_project_commit_path(event.project.namespace, event.project, + event.note_target) else - polymorphic_path([event.project, event.note_target], anchor: dom_id(event.target)) + polymorphic_path([event.project.namespace.becomes(Namespace), + event.project, event.note_target], + anchor: dom_id(event.target)) end end def event_note_title_html(event) if event.note_target if event.note_commit? - link_to project_commit_path(event.project, event.note_commit_id, anchor: dom_id(event.target)), class: "commit_short_id" do + link_to( + namespace_project_commit_path(event.project.namespace, event.project, + event.note_commit_id, + anchor: dom_id(event.target)), + class: "commit_short_id" + ) do "#{event.note_target_type} #{event.note_short_commit_id}" end elsif event.note_project_snippet? - link_to(project_snippet_path(event.project, event.note_target)) do + link_to(namespace_project_snippet_path(event.project.namespace, + event.project, + event.note_target)) do "#{event.note_target_type} ##{truncate event.note_target_id}" end else @@ -130,9 +165,8 @@ module EventsHelper end def event_note(text) - text = first_line(text) - text = truncate(text, length: 150) - sanitize(markdown(text), tags: %w(a img b pre p)) + text = first_line_in_markdown(text, 150) + sanitize(text, tags: %w(a img b pre code p span)) end def event_commit_title(message) @@ -140,4 +174,26 @@ module EventsHelper rescue "--broken encoding" end + + def event_to_atom(xml, event) + if event.proper? + 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.strftime("%Y-%m-%dT%H:%M:%SZ") + xml.media :thumbnail, width: "40", height: "40", 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 end diff --git a/app/helpers/explore_helper.rb b/app/helpers/explore_helper.rb new file mode 100644 index 0000000000..7616fe6bad --- /dev/null +++ b/app/helpers/explore_helper.rb @@ -0,0 +1,17 @@ +module ExploreHelper + def explore_projects_filter_path(options={}) + exist_opts = { + sort: params[:sort], + scope: params[:scope], + group: params[:group], + tag: params[:tag], + visibility_level: params[:visibility_level], + } + + options = exist_opts.merge(options) + + path = request.path + path << "?#{options.to_param}" + path + end +end diff --git a/app/helpers/external_wiki_helper.rb b/app/helpers/external_wiki_helper.rb new file mode 100644 index 0000000000..838b85afdf --- /dev/null +++ b/app/helpers/external_wiki_helper.rb @@ -0,0 +1,11 @@ +module ExternalWikiHelper + def get_project_wiki_path(project) + external_wiki_service = project.services. + select { |service| service.to_param == 'external_wiki' }.first + if external_wiki_service.present? && external_wiki_service.active? + external_wiki_service.properties['external_wiki_url'] + else + namespace_project_wiki_path(project.namespace, project, :home) + end + end +end diff --git a/app/helpers/git_helper.rb b/app/helpers/git_helper.rb new file mode 100644 index 0000000000..0968495523 --- /dev/null +++ b/app/helpers/git_helper.rb @@ -0,0 +1,5 @@ +module GitHelper + def strip_gpg_signature(text) + text.gsub(/-----BEGIN PGP SIGNATURE-----(.*)-----END PGP SIGNATURE-----/m, "") + end +end diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb index e4aa90154f..aa1de2f50e 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 =~ /^\ true else @@ -104,13 +124,14 @@ module GitlabMarkdownHelper end def ignored_protocols - ["http://","https://", "ftp://", "mailto:"] + ["http://","https://", "ftp://", "mailto:", "smb://"] end - def rebuild_path(path) - path.gsub!(/(#.*)/, "") + def rebuild_path(file_path) + file_path = file_path.dup + file_path.gsub!(/(#.*)/, "") id = $1 || "" - file_path = relative_file_path(path) + file_path = relative_file_path(file_path) file_path = sanitize_slashes(file_path) [ @@ -118,7 +139,7 @@ module GitlabMarkdownHelper @project.path_with_namespace, path_with_ref(file_path), file_path - ].compact.join("/").gsub(/^\/*|\/*$/, '') + id + ].compact.join("/").gsub(/\A\/*|\/*\z/, '') + id end def sanitize_slashes(path) @@ -164,7 +185,7 @@ module GitlabMarkdownHelper def file_exists?(path) return false if path.nil? - return @repository.blob_at(current_sha, path).present? || @repository.tree(current_sha, path).entries.any? + @repository.blob_at(current_sha, path).present? || @repository.tree(current_sha, path).entries.any? end # Check if the path is pointing to a directory(tree) or a file(blob) @@ -172,7 +193,7 @@ module GitlabMarkdownHelper def local_path(path) return "tree" if @repository.tree(current_sha, path).entries.any? return "raw" if @repository.blob_at(current_sha, path).image? - return "blob" + "blob" end def current_sha @@ -191,4 +212,64 @@ module GitlabMarkdownHelper def correct_ref @ref ? @ref : "master" end + + private + + # Return +text+, truncated to +max_chars+ characters, excluding any HTML + # tags. + def truncate_visible(text, max_chars) + doc = Nokogiri::HTML.fragment(text) + content_length = 0 + truncated = false + + doc.traverse do |node| + if node.text? || node.content.empty? + if truncated + node.remove + next + end + + # Handle line breaks within a node + if node.content.strip.lines.length > 1 + node.content = "#{node.content.lines.first.chomp}..." + truncated = true + end + + num_remaining = max_chars - content_length + if node.content.length > num_remaining + node.content = node.content.truncate(num_remaining) + truncated = true + end + content_length += node.content.length + end + + truncated = truncate_if_block(node, truncated) + end + + doc.to_html + end + + # Used by #truncate_visible. If +node+ is the first block element, and the + # text hasn't already been truncated, then append "..." to the node contents + # and return true. Otherwise return false. + def truncate_if_block(node, truncated) + if node.element? && node.description.block? && !truncated + node.content = "#{node.content}..." if node.next_sibling + true + else + truncated + end + end + + def cross_project_reference(project, entity) + path = project.path_with_namespace + + if entity.kind_of?(Issue) + [path, entity.iid].join('#') + elsif entity.kind_of?(MergeRequest) + [path, entity.iid].join('!') + else + raise 'Not supported type' + end + end end diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb new file mode 100644 index 0000000000..9703c8d9e9 --- /dev/null +++ b/app/helpers/gitlab_routing_helper.rb @@ -0,0 +1,55 @@ +# Shorter routing method for project and project items +# Since update to rails 4.1.9 we are now allowed to use `/` in project routing +# so we use nested routing for project resources which include project and +# project namespace. To avoid writing long methods every time we define shortcuts for +# some of routing. +# +# For example instead of this: +# +# namespace_project_merge_request_path(merge_request.project.namespace, merge_request.projects, merge_request) +# +# We can simply use shortcut: +# +# merge_request_path(merge_request) +# +module GitlabRoutingHelper + def project_path(project, *args) + namespace_project_path(project.namespace, project, *args) + end + + def edit_project_path(project, *args) + edit_namespace_project_path(project.namespace, project, *args) + end + + def issue_path(entity, *args) + namespace_project_issue_path(entity.project.namespace, entity.project, entity, *args) + end + + def merge_request_path(entity, *args) + namespace_project_merge_request_path(entity.project.namespace, entity.project, entity, *args) + end + + def milestone_path(entity, *args) + namespace_project_milestone_path(entity.project.namespace, entity.project, entity, *args) + end + + def project_url(project, *args) + namespace_project_url(project.namespace, project, *args) + end + + def edit_project_url(project, *args) + edit_namespace_project_url(project.namespace, project, *args) + end + + def issue_url(entity, *args) + namespace_project_issue_url(entity.project.namespace, entity.project, entity, *args) + end + + def merge_request_url(entity, *args) + namespace_project_merge_request_url(entity.project.namespace, entity.project, entity, *args) + end + + def project_snippet_url(entity, *args) + namespace_project_snippet_url(entity.project.namespace, entity.project, entity, *args) + end +end diff --git a/app/helpers/graph_helper.rb b/app/helpers/graph_helper.rb index 7cb1b6f8d1..e1dda20de8 100644 --- a/app/helpers/graph_helper.rb +++ b/app/helpers/graph_helper.rb @@ -1,10 +1,10 @@ module GraphHelper def get_refs(repo, commit) refs = "" - refs += commit.ref_names(repo).join(" ") + refs << commit.ref_names(repo).join(' ') # append note count - refs += "[#{@graph.notes[commit.id]}]" if @graph.notes[commit.id] > 0 + refs << "[#{@graph.notes[commit.id]}]" if @graph.notes[commit.id] > 0 refs end diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb index 0dc53dedeb..add0a776a6 100644 --- a/app/helpers/groups_helper.rb +++ b/app/helpers/groups_helper.rb @@ -1,12 +1,16 @@ module GroupsHelper - def remove_user_from_group_message(group, user) - "Are you sure you want to remove \"#{user.name}\" from \"#{group.name}\"?" + def remove_user_from_group_message(group, member) + if member.user + "Are you sure you want to remove \"#{member.user.name}\" from \"#{group.name}\"?" + else + "Are you sure you want to revoke the invitation for \"#{member.invite_email}\" to join \"#{group.name}\"?" + end end def leave_group_message(group) "Are you sure you want to leave \"#{group}\" group?" end - + def should_user_see_group_roles?(user, group) if user user.is_admin? || group.members.exists?(user_id: user.id) @@ -33,15 +37,23 @@ module GroupsHelper title end - def group_filter_path(entity, options={}) - exist_opts = { - status: params[:status] - } + def group_settings_page? + if current_controller?('groups') + current_action?('edit') || current_action?('projects') + else + false + end + end - options = exist_opts.merge(options) + def group_icon(group) + if group.is_a?(String) + group = Group.find_by(path: group) + end - path = request.path - path << "?#{options.to_param}" - path + if group && group.avatar.present? + group.avatar.url + else + image_path('no_group_avatar.png') + end end end diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index f0f771b4ba..a9030729b4 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -1,21 +1,83 @@ module IconsHelper + # Creates an icon tag given icon name(s) and possible icon modifiers. + # + # Right now this method simply delegates directly to `fa_icon` from the + # font-awesome-rails gem, but should we ever use a different icon pack in the + # future we won't have to change hundreds of method calls. + def icon(names, options = {}) + fa_icon(names, options) + end + + def spinner(text = nil, visible = false) + css_class = 'loading' + css_class << ' hide' unless visible + + content_tag :div, class: css_class do + icon('spinner spin') + text + end + end + def boolean_to_icon(value) if value.to_s == "true" - content_tag :i, nil, class: 'icon-circle cgreen' + icon('circle', class: 'cgreen') else - content_tag :i, nil, class: 'icon-off clgray' + icon('power-off', class: 'clgray') end end def public_icon - content_tag :i, nil, class: 'icon-globe' + icon('globe') end def internal_icon - content_tag :i, nil, class: 'icon-shield' + icon('shield') end def private_icon - content_tag :i, nil, class: 'icon-lock' + icon('lock') + end + + def file_type_icon_class(type, mode, name) + if type == 'folder' + icon_class = 'folder' + elsif mode == '120000' + icon_class = 'share' + else + # Guess which icon to choose based on file extension. + # If you think a file extension is missing, feel free to add it on PR + + case File.extname(name).downcase + when '.pdf' + icon_class = 'file-pdf-o' + when '.jpg', '.jpeg', '.jif', '.jfif', + '.jp2', '.jpx', '.j2k', '.j2c', + '.png', '.gif', '.tif', '.tiff', + '.svg', '.ico', '.bmp' + icon_class = 'file-image-o' + when '.zip', '.zipx', '.tar', '.gz', '.bz', '.bzip', + '.xz', '.rar', '.7z' + icon_class = 'file-archive-o' + when '.mp3', '.wma', '.ogg', '.oga', '.wav', '.flac', '.aac' + icon_class = 'file-audio-o' + when '.mp4', '.m4p', '.m4v', + '.mpg', '.mp2', '.mpeg', '.mpe', '.mpv', + '.mpg', '.mpeg', '.m2v', + '.avi', '.mkv', '.flv', '.ogv', '.mov', + '.3gp', '.3g2' + icon_class = 'file-video-o' + when '.doc', '.dot', '.docx', '.docm', '.dotx', '.dotm', '.docb' + icon_class = 'file-word-o' + when '.xls', '.xlt', '.xlm', '.xlsx', '.xlsm', '.xltx', '.xltm', + '.xlsb', '.xla', '.xlam', '.xll', '.xlw' + icon_class = 'file-excel-o' + when '.ppt', '.pot', '.pps', '.pptx', '.pptm', '.potx', '.potm', + '.ppam', '.ppsx', '.ppsm', '.sldx', '.sldm' + icon_class = 'file-powerpoint-o' + else + icon_class = 'file-text-o' + end + end + + icon_class end end diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb index 2031519c32..ad4a761272 100644 --- a/app/helpers/issues_helper.rb +++ b/app/helpers/issues_helper.rb @@ -1,5 +1,5 @@ module IssuesHelper - def issue_css_classes issue + def issue_css_classes(issue) classes = "issue" classes << " closed" if issue.closed? classes << " today" if issue.today? @@ -13,48 +13,40 @@ module IssuesHelper OpenStruct.new(id: 0, title: 'None (backlog)', name: 'Unassigned') end - def url_for_project_issues(project = @project) + def url_for_project_issues(project = @project, options = {}) return '' if project.nil? - if project.used_default_issues_tracker? || !external_issues_tracker_enabled? - project_issues_path(project) + if options[:only_path] + project.issues_tracker.project_path else - url = Gitlab.config.issues_tracker[project.issues_tracker]['project_url'] - url.gsub(':project_id', project.id.to_s). - gsub(':issues_tracker_id', project.issues_tracker_id.to_s) + project.issues_tracker.project_url end end - def url_for_new_issue(project = @project) + def url_for_new_issue(project = @project, options = {}) return '' if project.nil? - if project.used_default_issues_tracker? || !external_issues_tracker_enabled? - url = new_project_issue_path project_id: project + if options[:only_path] + project.issues_tracker.new_issue_path else - issues_tracker = Gitlab.config.issues_tracker[project.issues_tracker] - url = issues_tracker['new_issue_url'] - url.gsub(':project_id', project.id.to_s). - gsub(':issues_tracker_id', project.issues_tracker_id.to_s) + project.issues_tracker.new_issue_url end end - def url_for_issue(issue_iid, project = @project) + def url_for_issue(issue_iid, project = @project, options = {}) return '' if project.nil? - if project.used_default_issues_tracker? || !external_issues_tracker_enabled? - url = project_issue_url project_id: project, id: issue_iid + if options[:only_path] + project.issues_tracker.issue_path(issue_iid) else - url = Gitlab.config.issues_tracker[project.issues_tracker]['issues_url'] - url.gsub(':id', issue_iid.to_s). - gsub(':project_id', project.id.to_s). - gsub(':issues_tracker_id', project.issues_tracker_id.to_s) + project.issues_tracker.issue_url(issue_iid) end end def title_for_issue(issue_iid, project = @project) return '' if project.nil? - if project.used_default_issues_tracker? + if project.default_issues_tracker? issue = project.issues.where(iid: issue_iid).first return issue.title if issue end @@ -62,29 +54,28 @@ module IssuesHelper '' end - # Checks if issues_tracker setting exists in gitlab.yml - def external_issues_tracker_enabled? - Gitlab.config.issues_tracker && Gitlab.config.issues_tracker.values.any? + def issue_timestamp(issue) + # Shows the created at time and the updated at time if different + ts = "#{time_ago_with_tooltip(issue.created_at, 'bottom', 'note_created_ago')}" + if issue.updated_at != issue.created_at + ts << capture_haml do + haml_tag :span do + haml_concat '·' + haml_concat icon('edit', title: 'edited') + haml_concat time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_edited_ago') + end + end + end + ts.html_safe end def bulk_update_milestone_options - options_for_select(['None (backlog)']) + + options_for_select([['None (backlog)', -1]]) + options_from_collection_for_select(project_active_milestones, 'id', 'title', params[:milestone_id]) end - def bulk_update_assignee_options(project = @project) - options_for_select(['None (unassigned)']) + - options_from_collection_for_select(project.team.members, 'id', - 'name', params[:assignee_id]) - end - - def assignee_options(object, project = @project) - options_from_collection_for_select(project.team.members.sort_by(&:name), - 'id', 'name', object.assignee_id) - end - - def milestone_options object + def milestone_options(object) options_from_collection_for_select(object.project.milestones.active, 'id', 'title', object.milestone_id) end @@ -100,4 +91,21 @@ module IssuesHelper 'issue-box-open' end 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.strftime("%Y-%m-%dT%H:%M:%SZ") + xml.media :thumbnail, width: "40", height: "40", 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 end diff --git a/app/helpers/labels_helper.rb b/app/helpers/labels_helper.rb index 19d688c4bb..32ef2e7ca8 100644 --- a/app/helpers/labels_helper.rb +++ b/app/helpers/labels_helper.rb @@ -7,21 +7,34 @@ module LabelsHelper label_color = label.color || Label::DEFAULT_COLOR text_color = text_color_for_bg(label_color) - content_tag :span, class: 'label color-label', style: "background:#{label_color};color:#{text_color}" do + content_tag :span, class: 'label color-label', style: "background-color:#{label_color};color:#{text_color}" do label.name end end def suggested_colors [ - '#D9534F', - '#F0AD4E', + '#0033CC', '#428BCA', + '#44AD8E', + '#A8D695', '#5CB85C', + '#69D100', + '#004E00', '#34495E', '#7F8C8D', + '#A295D6', + '#5843AD', '#8E44AD', - '#FFECDB' + '#FFECDB', + '#AD4363', + '#D10069', + '#CC0033', + '#FF0000', + '#D9534F', + '#D1D100', + '#F0AD4E', + '#AD8D43' ] end @@ -34,4 +47,8 @@ module LabelsHelper "#FFF" end end + + def project_labels_options(project) + options_from_collection_for_select(project.labels, 'name', 'name', params[:label_name]) + end end diff --git a/app/helpers/merge_requests_helper.rb b/app/helpers/merge_requests_helper.rb index cc63db2035..54462fd00e 100644 --- a/app/helpers/merge_requests_helper.rb +++ b/app/helpers/merge_requests_helper.rb @@ -1,38 +1,41 @@ module MergeRequestsHelper def new_mr_path_from_push_event(event) target_project = event.project.forked_from_project || event.project - new_project_merge_request_path( + new_namespace_project_merge_request_path( + event.project.namespace, event.project, new_mr_from_push_event(event, target_project) ) end def new_mr_path_for_fork_from_push_event(event) - new_project_merge_request_path( + new_namespace_project_merge_request_path( + event.project.namespace, event.project, new_mr_from_push_event(event, event.project.forked_from_project) ) end def new_mr_from_push_event(event, target_project) - return :merge_request => { - source_project_id: event.project.id, - target_project_id: target_project.id, - source_branch: event.branch_name, - target_branch: target_project.repository.root_ref, - title: event.branch_name.titleize.humanize + { + merge_request: { + source_project_id: event.project.id, + target_project_id: target_project.id, + source_branch: event.branch_name, + target_branch: target_project.repository.root_ref + } } end - def mr_css_classes mr + def mr_css_classes(mr) classes = "merge-request" classes << " closed" if mr.closed? classes << " merged" if mr.merged? classes end - def ci_build_details_path merge_request - merge_request.source_project.ci_service.build_page(merge_request.last_commit.sha) + def ci_build_details_path(merge_request) + merge_request.source_project.ci_service.build_page(merge_request.last_commit.sha, merge_request.source_branch) end def merge_path_description(merge_request, separator) diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb new file mode 100644 index 0000000000..282bdf744d --- /dev/null +++ b/app/helpers/milestones_helper.rb @@ -0,0 +1,33 @@ +module MilestonesHelper + def milestones_filter_path(opts = {}) + if @project + namespace_project_milestones_path(@project.namespace, @project, opts) + elsif @group + group_milestones_path(@group, opts) + else + dashboard_milestones_path(opts) + end + end + + def milestone_progress_bar(milestone) + options = { + class: 'progress-bar progress-bar-success', + style: "width: #{milestone.percent_complete}%;" + } + + content_tag :div, class: 'progress' do + content_tag :div, nil, options + end + end + + def projects_milestones_options + milestones = + if @project + @project.milestones + else + Milestone.where(project_id: @projects) + end.active + + options_from_collection_for_select(milestones, 'id', 'title', params[:milestone_id]) + end +end diff --git a/app/helpers/namespaces_helper.rb b/app/helpers/namespaces_helper.rb index bf25dce230..b3132a1f3b 100644 --- a/app/helpers/namespaces_helper.rb +++ b/app/helpers/namespaces_helper.rb @@ -25,4 +25,12 @@ module NamespacesHelper hidden_field_tag(id, value, class: css_class) end + + def namespace_icon(namespace, size = 40) + if namespace.kind_of?(Group) + group_icon(namespace) + else + avatar_icon(namespace.owner.email, size) + end + end end diff --git a/app/helpers/nav_helper.rb b/app/helpers/nav_helper.rb new file mode 100644 index 0000000000..2b03269800 --- /dev/null +++ b/app/helpers/nav_helper.rb @@ -0,0 +1,5 @@ +module NavHelper + def nav_menu_collapsed? + cookies[:collapsed_nav] == 'true' + end +end diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb index 53ac5febd6..ab44fa6ee4 100644 --- a/app/helpers/notes_helper.rb +++ b/app/helpers/notes_helper.rb @@ -1,17 +1,21 @@ module NotesHelper - # Helps to distinguish e.g. commit notes in mr notes list + # 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?) end - def note_target_fields - hidden_field_tag(:target_type, @target_type) + - hidden_field_tag(:target_id, @target_id) + def note_target_fields(note) + hidden_field_tag(:target_type, note.noteable.class.name.underscore) + + hidden_field_tag(:target_id, note.noteable.id) 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}", project_commit_path(@project, note.noteable, anchor: note.line_code) + 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 @@ -20,8 +24,10 @@ module NotesHelper ts = "#{time_ago_with_tooltip(note.created_at, 'bottom', 'note_created_ago')}" if note.updated_at != note.created_at ts << capture_haml do - haml_tag :small do - haml_concat " (Edited #{time_ago_with_tooltip(note.updated_at, 'bottom', 'note_edited_ago')})" + haml_tag :span do + haml_concat '·' + haml_concat icon('edit', title: 'edited') + haml_concat time_ago_with_tooltip(note.updated_at, 'bottom', 'note_edited_ago') end end end @@ -52,8 +58,11 @@ module NotesHelper discussion_id: discussion_id } - link_to "", "javascript:;", class: "add-diff-note js-add-diff-note-button", - data: data, title: "Add a comment to this line" + button_tag(class: 'btn add-diff-note js-add-diff-note-button', + data: data, + title: 'Add a comment to this line') do + icon('comment-o') + end end def link_to_reply_diff(note) @@ -67,11 +76,10 @@ module NotesHelper discussion_id: note.discussion_id } - link_to "javascript:;", class: "btn reply-btn js-discussion-reply-button", - data: data, title: "Add a reply" do - link_text = "" - link_text < content_tag(:i, nil, class: 'icon-comment') - link_text << "Reply" - end + button_tag class: 'btn reply-btn js-discussion-reply-button', + data: data, title: 'Add a reply' do + link_text = icon('comment') + link_text << ' Reply' + end end end diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb index 6c43f97446..f771fe761e 100644 --- a/app/helpers/notifications_helper.rb +++ b/app/helpers/notifications_helper.rb @@ -1,13 +1,13 @@ module NotificationsHelper def notification_icon(notification) if notification.disabled? - content_tag :i, nil, class: 'icon-volume-off ns-mute' + icon('volume-off', class: 'ns-mute') elsif notification.participating? - content_tag :i, nil, class: 'icon-volume-down ns-part' + icon('volume-down', class: 'ns-part') elsif notification.watch? - content_tag :i, nil, class: 'icon-volume-up ns-watch' + icon('volume-up', class: 'ns-watch') else - content_tag :i, nil, class: 'icon-circle-blank ns-default' + icon('circle-o', class: 'ns-default') end end end diff --git a/app/helpers/oauth_helper.rb b/app/helpers/oauth_helper.rb index c0177dacbf..997b91de07 100644 --- a/app/helpers/oauth_helper.rb +++ b/app/helpers/oauth_helper.rb @@ -1,10 +1,10 @@ module OauthHelper def ldap_enabled? - Devise.omniauth_providers.include?(:ldap) + Gitlab.config.ldap.enabled end def default_providers - [:twitter, :github, :google_oauth2, :ldap] + [:twitter, :github, :gitlab, :bitbucket, :google_oauth2, :ldap] end def enabled_oauth_providers @@ -13,7 +13,22 @@ module OauthHelper def enabled_social_providers enabled_oauth_providers.select do |name| - [:twitter, :github, :google_oauth2].include?(name.to_sym) + [:twitter, :gitlab, :github, :bitbucket, :google_oauth2].include?(name.to_sym) end end + + def additional_providers + enabled_oauth_providers.reject{|provider| provider.to_s.starts_with?('ldap')} + end + + def oauth_image_tag(provider, size = 64) + file_name = "#{provider.to_s.split('_').first}_#{size}.png" + image_tag(image_path("authbuttons/#{file_name}"), alt: "Sign in with #{provider.to_s.titleize}") + end + + def oauth_active?(provider) + current_user.identities.exists?(provider: provider.to_s) + end + + extend self end diff --git a/app/helpers/profile_helper.rb b/app/helpers/profile_helper.rb index 297ae83d89..780c7cd513 100644 --- a/app/helpers/profile_helper.rb +++ b/app/helpers/profile_helper.rb @@ -1,19 +1,13 @@ module ProfileHelper - def oauth_active_class provider - if current_user.provider == provider.to_s - 'active' - end - end - def show_profile_username_tab? current_user.can_change_username? end def show_profile_social_tab? - enabled_social_providers.any? && !current_user.ldap_user? + enabled_social_providers.any? end def show_profile_remove_tab? - gitlab_config.signup_enabled && !current_user.ldap_user? + signup_enabled? end end diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb index 8350f5dc07..c2a7732e6f 100644 --- a/app/helpers/projects_helper.rb +++ b/app/helpers/projects_helper.rb @@ -1,10 +1,14 @@ module ProjectsHelper - def remove_from_project_team_message(project, user) - "You are going to remove #{user.name} from #{project.name} project team. Are you sure?" + def remove_from_project_team_message(project, member) + if member.user + "You are going to remove #{member.user.name} from #{project.name} project team. Are you sure?" + else + "You are going to revoke the invitation for #{member.invite_email} to join #{project.name} project team. Are you sure?" + end end - def link_to_project project - link_to project do + def link_to_project(project) + link_to [project.namespace.becomes(Namespace), project] do title = content_tag(:span, project.name, class: 'project-name') if project.namespace @@ -39,15 +43,23 @@ module ProjectsHelper end end - def project_title project + def project_title(project) if project.group content_tag :span do - link_to(simple_sanitize(project.group.name), group_path(project.group)) + " / " + project.name + link_to( + simple_sanitize(project.group.name), group_path(project.group) + ) + ' / ' + + link_to(simple_sanitize(project.name), + project_path(project)) end else owner = project.namespace.owner content_tag :span do - link_to(simple_sanitize(owner.name), user_path(owner)) + " / " + project.name + link_to( + simple_sanitize(owner.name), user_path(owner) + ) + ' / ' + + link_to(simple_sanitize(project.name), + project_path(project)) end end end @@ -56,6 +68,10 @@ module ProjectsHelper "You are going to remove #{project.name_with_namespace}.\n Removed project CANNOT be restored!\n Are you ABSOLUTELY sure?" end + def transfer_project_message(project) + "You are going to transfer #{project.name_with_namespace} to another owner. Are you ABSOLUTELY sure?" + end + def project_nav_tabs @nav_tabs ||= get_project_nav_tabs(@project, current_user) end @@ -64,76 +80,22 @@ module ProjectsHelper project_nav_tabs.include? name end - def selected_label?(label_name) - params[:label_name].to_s.split(',').include?(label_name) - end - - def labels_filter_path(label_name) - label_name = - if selected_label?(label_name) - params[:label_name].split(',').reject { |l| l == label_name }.join(',') - elsif params[:label_name].present? - "#{params[:label_name]},#{label_name}" - else - label_name - end - - project_filter_path(label_name: label_name) - end - - def label_filter_class(label_name) - if selected_label?(label_name) - 'label-filter-item active' - else - 'label-filter-item light' - end - end - - def project_filter_path(options={}) - exist_opts = { - state: params[:state], - scope: params[:scope], - label_name: params[:label_name], - milestone_id: params[:milestone_id], - assignee_id: params[:assignee_id], - sort: params[:sort], - } - - options = exist_opts.merge(options) - - path = request.path - path << "?#{options.to_param}" - path - end - def project_active_milestones @project.milestones.active.order("due_date, title ASC") end - def project_issues_trackers(current_tracker = nil) - values = Project.issues_tracker.values.map do |tracker_key| - if tracker_key.to_sym == :gitlab - ['GitLab', tracker_key] + def link_to_toggle_star(title, starred) + cls = 'star-btn btn btn-sm btn-default' + + toggle_text = + if starred + ' Unstar' else - [Gitlab.config.issues_tracker[tracker_key]['title'] || tracker_key, tracker_key] + ' Star' end - end - - options_for_select(values, current_tracker) - end - - def link_to_toggle_star(title, starred, signed_in) - cls = 'btn btn-block' - cls += ' disabled' unless signed_in toggle_html = content_tag('span', class: 'toggle') do - toggle_text = if starred - 'Unstar' - else - 'Star' - end - - content_tag('i', ' ', class: 'icon-star') + toggle_text + icon('star') + toggle_text end count_html = content_tag('span', class: 'count') do @@ -145,17 +107,38 @@ module ProjectsHelper class: cls, method: :post, remote: true, - data: {type: 'json'} + data: { type: 'json' } } + path = toggle_star_namespace_project_path(@project.namespace, @project) content_tag 'span', class: starred ? 'turn-on' : 'turn-off' do - link_to toggle_star_project_path(@project), link_opts do - toggle_html + count_html + link_to(path, link_opts) do + toggle_html + ' ' + count_html end end end + def link_to_toggle_fork + html = content_tag('span') do + icon('code-fork') + ' Fork' + end + + count_html = content_tag(:span, class: 'count') do + @project.forks_count.to_s + end + + html + count_html + end + + def project_for_deploy_key(deploy_key) + if deploy_key.projects.include?(@project) + @project + else + deploy_key.projects.find { |project| can?(current_user, :read_project, project) } + end + end + private def get_project_nav_tabs(project, current_user) @@ -177,6 +160,10 @@ module ProjectsHelper nav_tabs << feature if project.send :"#{feature}_enabled" end + if project.issues_enabled || project.merge_requests_enabled + nav_tabs << [:milestones, :labels] + end + nav_tabs.flatten end @@ -217,7 +204,13 @@ module ProjectsHelper "Issues - " + title end elsif current_controller?(:blob) - "#{@project.path}\/#{@blob.path} at #{@ref} - " + title + if current_action?(:new) || current_action?(:create) + "New file at #{@ref}" + elsif current_action?(:show) + "#{@blob.path} at #{@ref}" + elsif @blob + "Edit file #{@blob.path} at #{@ref}" + end elsif current_controller?(:commits) "Commits at #{@ref} - " + title elsif current_controller?(:merge_requests) @@ -257,8 +250,80 @@ module ProjectsHelper end def contribution_guide_url(project) - if project && project.repository.contribution_guide - project_blob_path(project, tree_join(project.default_branch, project.repository.contribution_guide.name)) + if project && contribution_guide = project.repository.contribution_guide + namespace_project_blob_path( + project.namespace, + project, + tree_join(project.default_branch, + contribution_guide.name) + ) + end + end + + def changelog_url(project) + if project && changelog = project.repository.changelog + namespace_project_blob_path( + project.namespace, + project, + tree_join(project.default_branch, + changelog.name) + ) + end + end + + def license_url(project) + if project && license = project.repository.license + namespace_project_blob_path( + project.namespace, + project, + tree_join(project.default_branch, + license.name) + ) + end + end + + def version_url(project) + if project && version = project.repository.version + namespace_project_blob_path( + project.namespace, + project, + tree_join(project.default_branch, + version.name) + ) + end + end + + def hidden_pass_url(original_url) + result = URI(original_url) + result.password = '*****' unless result.password.nil? + result + rescue + original_url + end + + def project_wiki_path_with_version(proj, page, version, is_newest) + url_params = is_newest ? {} : { version_id: version } + namespace_project_wiki_path(proj.namespace, proj, page, url_params) + end + + def project_status_css_class(status) + case status + when "started" + "active" + when "failed" + "danger" + when "finished" + "success" + end + end + + def service_field_value(type, value) + return value unless type == 'password' + + if value.present? + "***********" + else + nil end end end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index ecd8d3994d..c31a556ff7 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -23,9 +23,9 @@ module SearchHelper # Autocomplete results for various settings pages def default_autocomplete [ - { label: "My Profile settings", url: profile_path }, - { label: "My SSH Keys", url: profile_keys_path }, - { label: "My Dashboard", url: root_path }, + { label: "Profile settings", url: profile_path }, + { label: "SSH Keys", url: profile_keys_path }, + { label: "Dashboard", url: root_path }, { label: "Admin Section", url: admin_root_path }, ] end @@ -52,16 +52,16 @@ module SearchHelper ref = @ref || @project.repository.root_ref [ - { label: "#{prefix} - Files", url: project_tree_path(@project, ref) }, - { label: "#{prefix} - Commits", url: project_commits_path(@project, ref) }, - { label: "#{prefix} - Network", url: project_network_path(@project, ref) }, - { label: "#{prefix} - Graph", url: project_graph_path(@project, ref) }, - { label: "#{prefix} - Issues", url: project_issues_path(@project) }, - { label: "#{prefix} - Merge Requests", url: project_merge_requests_path(@project) }, - { label: "#{prefix} - Milestones", url: project_milestones_path(@project) }, - { label: "#{prefix} - Snippets", url: project_snippets_path(@project) }, - { label: "#{prefix} - Team", url: project_team_index_path(@project) }, - { label: "#{prefix} - Wiki", url: project_wikis_path(@project) }, + { label: "#{prefix} - Files", url: namespace_project_tree_path(@project.namespace, @project, ref) }, + { label: "#{prefix} - Commits", url: namespace_project_commits_path(@project.namespace, @project, ref) }, + { label: "#{prefix} - Network", url: namespace_project_network_path(@project.namespace, @project, ref) }, + { label: "#{prefix} - Graph", url: namespace_project_graph_path(@project.namespace, @project, ref) }, + { label: "#{prefix} - Issues", url: namespace_project_issues_path(@project.namespace, @project) }, + { label: "#{prefix} - Merge Requests", url: namespace_project_merge_requests_path(@project.namespace, @project) }, + { label: "#{prefix} - Milestones", url: namespace_project_milestones_path(@project.namespace, @project) }, + { label: "#{prefix} - Snippets", url: namespace_project_snippets_path(@project.namespace, @project) }, + { label: "#{prefix} - Members", url: namespace_project_project_members_path(@project.namespace, @project) }, + { label: "#{prefix} - Wiki", url: namespace_project_wikis_path(@project.namespace, @project) }, ] else [] @@ -80,10 +80,11 @@ module SearchHelper # Autocomplete results for the current user's projects def projects_autocomplete(term, limit = 5) - ProjectsFinder.new.execute(current_user).search_by_title(term).non_archived.limit(limit).map do |p| + ProjectsFinder.new.execute(current_user).search_by_title(term). + sorted_by_stars.non_archived.limit(limit).map do |p| { label: "project: #{search_result_sanitize(p.name_with_namespace)}", - url: project_path(p) + url: namespace_project_path(p.namespace, p) } end end @@ -91,4 +92,21 @@ module SearchHelper def search_result_sanitize(str) Sanitize.clean(str) end + + def search_filter_path(options={}) + exist_opts = { + search: params[:search], + project_id: params[:project_id], + group_id: params[:group_id], + scope: params[:scope] + } + + options = exist_opts.merge(options) + search_path(options) + end + + # Sanitize html generated after parsing markdown from issue description or comment + def search_md_sanitize(html) + sanitize(html, tags: %w(a p ol ul li pre code)) + end end diff --git a/app/helpers/selects_helper.rb b/app/helpers/selects_helper.rb index ab24367c45..bec8f2f1aa 100644 --- a/app/helpers/selects_helper.rb +++ b/app/helpers/selects_helper.rb @@ -4,17 +4,39 @@ module SelectsHelper css_class << "multiselect " if opts[:multiple] css_class << (opts[:class] || '') value = opts[:selected] || '' + placeholder = opts[:placeholder] || 'Search for a user' - hidden_field_tag(id, value, class: css_class) + null_user = opts[:null_user] || false + any_user = opts[:any_user] || false + email_user = opts[:email_user] || false + first_user = opts[:first_user] && current_user ? current_user.username : false + + html = { + class: css_class, + 'data-placeholder' => placeholder, + 'data-null-user' => null_user, + 'data-any-user' => any_user, + 'data-email-user' => email_user, + 'data-first-user' => first_user + } + + unless opts[:scope] == :all + if @project + html['data-project-id'] = @project.id + elsif @group + html['data-group-id'] = @group.id + end + end + + hidden_field_tag(id, value, html) end - def project_users_select_tag(id, opts = {}) - css_class = "ajax-project-users-select " + def groups_select_tag(id, opts = {}) + css_class = "ajax-groups-select " css_class << "multiselect " if opts[:multiple] css_class << (opts[:class] || '') value = opts[:selected] || '' - placeholder = opts[:placeholder] || 'Select user' - project_id = opts[:project_id] || @project.id - hidden_field_tag(id, value, class: css_class, 'data-placeholder' => placeholder, 'data-project-id' => project_id) + + hidden_field_tag(id, value, class: css_class) end end diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb index b0abc2cae3..906cb12cd4 100644 --- a/app/helpers/snippets_helper.rb +++ b/app/helpers/snippets_helper.rb @@ -11,7 +11,8 @@ module SnippetsHelper def reliable_snippet_path(snippet) if snippet.project_id? - project_snippet_path(snippet.project, snippet) + namespace_project_snippet_path(snippet.project.namespace, + snippet.project, snippet) else snippet_path(snippet) end diff --git a/app/helpers/sorting_helper.rb b/app/helpers/sorting_helper.rb new file mode 100644 index 0000000000..bb12d43f39 --- /dev/null +++ b/app/helpers/sorting_helper.rb @@ -0,0 +1,96 @@ +module SortingHelper + def sort_options_hash + { + sort_value_name => sort_title_name, + sort_value_recently_updated => sort_title_recently_updated, + sort_value_oldest_updated => sort_title_oldest_updated, + sort_value_recently_created => sort_title_recently_created, + sort_value_oldest_created => sort_title_oldest_created, + sort_value_milestone_soon => sort_title_milestone_soon, + sort_value_milestone_later => sort_title_milestone_later, + sort_value_largest_repo => sort_title_largest_repo, + sort_value_recently_signin => sort_title_recently_signin, + sort_value_oldest_signin => sort_title_oldest_signin, + } + end + + def sort_title_oldest_updated + 'Oldest updated' + end + + def sort_title_recently_updated + 'Recently updated' + end + + def sort_title_oldest_created + 'Oldest created' + end + + def sort_title_recently_created + 'Recently created' + end + + def sort_title_milestone_soon + 'Milestone due soon' + end + + def sort_title_milestone_later + 'Milestone due later' + end + + def sort_title_name + 'Name' + end + + def sort_title_largest_repo + 'Largest repository' + end + + def sort_title_recently_signin + 'Recent sign in' + end + + def sort_title_oldest_signin + 'Oldest sign in' + end + + def sort_value_oldest_updated + 'updated_asc' + end + + def sort_value_recently_updated + 'updated_desc' + end + + def sort_value_oldest_created + 'created_asc' + end + + def sort_value_recently_created + 'created_desc' + end + + def sort_value_milestone_soon + 'milestone_due_asc' + end + + def sort_value_milestone_later + 'milestone_due_desc' + end + + def sort_value_name + 'name_asc' + end + + def sort_value_largest_repo + 'repository_size_desc' + end + + def sort_value_recently_signin + 'recent_sign_in' + end + + def sort_value_oldest_signin + 'oldest_sign_in' + end +end diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb index 09e5c08e62..9954617c76 100644 --- a/app/helpers/submodule_helper.rb +++ b/app/helpers/submodule_helper.rb @@ -2,22 +2,25 @@ module SubmoduleHelper include Gitlab::ShellAdapter # links to files listing for submodule if submodule is a project on this server - def submodule_links(submodule_item) - url = @repository.submodule_url_for(@ref, submodule_item.path) + def submodule_links(submodule_item, ref = nil) + url = @repository.submodule_url_for(ref, submodule_item.path) - return url, nil unless url =~ /([^\/:]+\/[^\/]+\.git)\Z/ + return url, nil unless url =~ /([^\/:]+)\/([^\/]+\.git)\Z/ - project = $1 + namespace = $1 + project = $2 project.chomp!('.git') - if self_url?(url, project) - return project_path(project), project_tree_path(project, submodule_item.id) + if self_url?(url, namespace, project) + return namespace_project_path(namespace, project), + namespace_project_tree_path(namespace, project, + submodule_item.id) elsif relative_self_url?(url) relative_self_links(url, submodule_item.id) elsif github_dot_com_url?(url) - standard_links('github.com', project, submodule_item.id) + standard_links('github.com', namespace, project, submodule_item.id) elsif gitlab_dot_com_url?(url) - standard_links('gitlab.com', project, submodule_item.id) + standard_links('gitlab.com', namespace, project, submodule_item.id) else return url, nil end @@ -33,27 +36,39 @@ module SubmoduleHelper url =~ /gitlab\.com[\/:][^\/]+\/[^\/]+\Z/ end - def self_url?(url, project) - return true if url == [ Gitlab.config.gitlab.url, '/', project, '.git' ].join('') - url == gitlab_shell.url_to_repo(project) + def self_url?(url, namespace, project) + return true if url == [ Gitlab.config.gitlab.url, '/', namespace, '/', + project, '.git' ].join('') + url == gitlab_shell.url_to_repo([namespace, '/', project].join('')) end def relative_self_url?(url) # (./)?(../repo.git) || (./)?(../../project/repo.git) ) - url =~ /^((\.\/)?(\.\.\/))(?!(\.\.)|(.*\/)).*\.git\Z/ || url =~ /^((\.\/)?(\.\.\/){2})(?!(\.\.))([^\/]*)\/(?!(\.\.)|(.*\/)).*\.git\Z/ + url =~ /\A((\.\/)?(\.\.\/))(?!(\.\.)|(.*\/)).*\.git\z/ || url =~ /\A((\.\/)?(\.\.\/){2})(?!(\.\.))([^\/]*)\/(?!(\.\.)|(.*\/)).*\.git\z/ end - def standard_links(host, project, commit) - base = [ 'https://', host, '/', project ].join('') - return base, [ base, '/tree/', commit ].join('') + def standard_links(host, namespace, project, commit) + base = [ 'https://', host, '/', namespace, '/', project ].join('') + [base, [ base, '/tree/', commit ].join('')] end def relative_self_links(url, commit) - if url.scan(/(\.\.\/)/).size == 2 - base = url[/([^\/]*\/[^\/]*)\.git/, 1] - else - base = [ @project.group.path, '/', url[/([^\/]*)\.git/, 1] ].join('') + # Map relative links to a namespace and project + # For example: + # ../bar.git -> same namespace, repo bar + # ../foo/bar.git -> namespace foo, repo bar + # ../../foo/bar/baz.git -> namespace bar, repo baz + components = url.split('/') + base = components.pop.gsub(/.git$/, '') + namespace = components.pop.gsub(/^\.\.$/, '') + + if namespace.empty? + namespace = @project.group.path end - return project_path(base), project_tree_path(base, commit) + + [ + namespace_project_path(namespace, base), + namespace_project_tree_path(namespace, base, commit) + ] end end diff --git a/app/helpers/tab_helper.rb b/app/helpers/tab_helper.rb index 610175f844..a1d263d9d3 100644 --- a/app/helpers/tab_helper.rb +++ b/app/helpers/tab_helper.rb @@ -28,6 +28,10 @@ module TabHelper # nav_link(controller: [:tree, :refs]) { "Hello" } # # => '
  • Hello
  • ' # + # # Several paths + # nav_link(path: ['tree#show', 'profile#show']) { "Hello" } + # # => '
  • Hello
  • ' + # # # Shorthand path # nav_link(path: 'tree#show') { "Hello" } # # => '
  • Hello
  • ' @@ -38,25 +42,7 @@ module TabHelper # # Returns a list item element String def nav_link(options = {}, &block) - if path = options.delete(:path) - if path.respond_to?(:each) - c = path.map { |p| p.split('#').first } - a = path.map { |p| p.split('#').last } - else - c, a, _ = path.split('#') - end - else - c = options.delete(:controller) - a = options.delete(:action) - end - - if c && a - # When given both options, make sure BOTH are active - klass = current_controller?(*c) && current_action?(*a) ? 'active' : '' - else - # Otherwise check EITHER option - klass = current_controller?(*c) || current_action?(*a) ? 'active' : '' - end + klass = active_nav_link?(options) ? 'active' : '' # Add our custom class into the html_options, which may or may not exist # and which may or may not already have a :class key @@ -72,24 +58,53 @@ module TabHelper end end + def active_nav_link?(options) + if path = options.delete(:path) + unless path.respond_to?(:each) + path = [path] + end + + path.any? do |single_path| + current_path?(single_path) + end + else + c = options.delete(:controller) + a = options.delete(:action) + + if c && a + # When given both options, make sure BOTH are true + current_controller?(*c) && current_action?(*a) + else + # Otherwise check EITHER option + current_controller?(*c) || current_action?(*a) + end + end + end + + def current_path?(path) + c, a, _ = path.split('#') + current_controller?(c) && current_action?(a) + end + def project_tab_class return "active" if current_page?(controller: "/projects", action: :edit, id: @project) - if ['services', 'hooks', 'deploy_keys', 'team_members', 'protected_branches'].include? controller.controller_name - "active" + if ['services', 'hooks', 'deploy_keys', 'project_members', 'protected_branches'].include? controller.controller_name + "active" end end def branches_tab_class if current_controller?(:protected_branches) || current_controller?(:branches) || - current_page?(project_repository_path(@project)) + current_page?(namespace_project_repository_path(@project.namespace, + @project)) 'active' end end # Use nav_tab for save controller/action but different params - def nav_tab key, value, &block + def nav_tab(key, value, &block) o = {} o[:class] = "" diff --git a/app/helpers/tags_helper.rb b/app/helpers/tags_helper.rb index ebed6a8374..fb85544df2 100644 --- a/app/helpers/tags_helper.rb +++ b/app/helpers/tags_helper.rb @@ -1,12 +1,12 @@ module TagsHelper - def tag_path tag + def tag_path(tag) "/tags/#{tag}" end - def tag_list project + def tag_list(project) html = '' project.tag_list.each do |tag| - html += link_to tag, tag_path(tag) + html << link_to(tag, tag_path(tag)) end html.html_safe diff --git a/app/helpers/tree_helper.rb b/app/helpers/tree_helper.rb index d815257a4e..6dd9b6f017 100644 --- a/app/helpers/tree_helper.rb +++ b/app/helpers/tree_helper.rb @@ -10,13 +10,16 @@ module TreeHelper tree = "" # Render folders if we have any - tree += render partial: 'projects/tree/tree_item', collection: folders, locals: {type: 'folder'} if folders.present? + tree << render(partial: 'projects/tree/tree_item', collection: folders, + locals: { type: 'folder' }) if folders.present? # Render files if we have any - tree += render partial: 'projects/tree/blob_item', collection: files, locals: {type: 'file'} if files.present? + tree << render(partial: 'projects/tree/blob_item', collection: files, + locals: { type: 'file' }) if files.present? # Render submodules if we have any - tree += render partial: 'projects/tree/submodule_item', collection: submodules if submodules.present? + tree << render(partial: 'projects/tree/submodule_item', + collection: submodules) if submodules.present? tree.html_safe end @@ -31,17 +34,13 @@ module TreeHelper end end - # Return an image icon depending on the file type + # Return an image icon depending on the file type and mode # # type - String type of the tree item; either 'folder' or 'file' - def tree_icon(type) - icon_class = if type == 'folder' - 'icon-folder-close' - else - 'icon-file-alt' - end - - content_tag :i, nil, class: icon_class + # mode - File unix mode + # name - File name + def tree_icon(type, mode, name) + icon("#{file_type_icon_class(type, mode, name)} fw") end def tree_hex_class(content) @@ -53,20 +52,18 @@ module TreeHelper File.join(*args) end - def allowed_tree_edit? - return false unless @repository.branch_names.include?(@ref) + def allowed_tree_edit?(project = nil, ref = nil) + project ||= @project + ref ||= @ref + return false unless project.repository.branch_names.include?(ref) - if @project.protected_branch? @ref - can?(current_user, :push_code_to_protected_branches, @project) - else - can?(current_user, :push_code, @project) - end + ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref) end def tree_breadcrumbs(tree, max_links = 2) if @path.present? part_path = "" - parts = @path.split("\/") + parts = @path.split('/') yield('..', nil) if parts.count > max_links @@ -80,20 +77,18 @@ module TreeHelper end end - def up_dir_path tree + def up_dir_path file = File.join(@path, "..") tree_join(@ref, file) end - def leave_edit_message - "Leave edit mode?\nAll unsaved changes will be lost." - end - - def editing_preview_title(filename) - if gitlab_markdown?(filename) || markup?(filename) - 'Preview' + # returns the relative path of the first subdir that doesn't have only one directory descendant + def flatten_tree(tree) + subtree = Gitlab::Git::Tree.where(@repository, @commit.id, tree.path) + if subtree.count == 1 && subtree.first.dir? + return tree_join(tree.name, flatten_tree(subtree.first)) else - 'Diff' + return tree.name end end end diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb index 8b83b8ff64..0d573e72a8 100644 --- a/app/helpers/visibility_level_helper.rb +++ b/app/helpers/visibility_level_helper.rb @@ -28,6 +28,23 @@ module VisibilityLevelHelper end end + def snippet_visibility_level_description(level) + capture_haml do + haml_tag :span do + case level + when Gitlab::VisibilityLevel::PRIVATE + haml_concat "The snippet is visible only for me" + when Gitlab::VisibilityLevel::INTERNAL + haml_concat "The snippet is visible for any logged in user." + when Gitlab::VisibilityLevel::PUBLIC + haml_concat "The snippet can be accessed" + haml_concat "without any" + haml_concat "authentication." + end + end + end + end + def visibility_level_icon(level) case level when Gitlab::VisibilityLevel::PRIVATE @@ -43,7 +60,8 @@ module VisibilityLevelHelper Project.visibility_levels.key(level) end - def restricted_visibility_levels - current_user.is_admin? ? [] : gitlab_config.restricted_visibility_levels + def restricted_visibility_levels(show_all = false) + return [] if current_user.is_admin? && !show_all + current_application_settings.restricted_visibility_levels || [] end end diff --git a/app/helpers/wiki_helper.rb b/app/helpers/wiki_helper.rb new file mode 100644 index 0000000000..a3bc64c010 --- /dev/null +++ b/app/helpers/wiki_helper.rb @@ -0,0 +1,22 @@ +module WikiHelper + # Rails v4.1.9+ escapes all model IDs, converting slashes into %2F. The + # only way around this is to implement our own path generators. + def namespace_project_wiki_path(namespace, project, wiki_page, *args) + slug = + case wiki_page + when Symbol + wiki_page + else + wiki_page.slug + end + namespace_project_path(namespace, project) + "/wikis/#{slug}" + end + + def edit_namespace_project_wiki_path(namespace, project, wiki_page, *args) + namespace_project_wiki_path(namespace, project, wiki_page) + '/edit' + end + + def history_namespace_project_wiki_path(namespace, project, wiki_page, *args) + namespace_project_wiki_path(namespace, project, wiki_page) + '/history' + end +end diff --git a/app/mailers/emails/groups.rb b/app/mailers/emails/groups.rb index 1654fc55bc..1c43f95dc8 100644 --- a/app/mailers/emails/groups.rb +++ b/app/mailers/emails/groups.rb @@ -1,11 +1,52 @@ module Emails module Groups - def group_access_granted_email(user_group_id) - @membership = UsersGroup.find(user_group_id) - @group = @membership.group + def group_access_granted_email(group_member_id) + @group_member = GroupMember.find(group_member_id) + @group = @group_member.group + @target_url = group_url(@group) - mail(to: @membership.user.email, + @current_user = @group_member.user + + mail(to: @group_member.user.notification_email, subject: subject("Access to group was granted")) end + + def group_member_invited_email(group_member_id, token) + @group_member = GroupMember.find group_member_id + @group = @group_member.group + @token = token + + @target_url = group_url(@group) + @current_user = @group_member.user + + mail(to: @group_member.invite_email, + subject: "Invitation to join group #{@group.name}") + end + + def group_invite_accepted_email(group_member_id) + @group_member = GroupMember.find group_member_id + return if @group_member.created_by.nil? + + @group = @group_member.group + + @target_url = group_url(@group) + @current_user = @group_member.created_by + + mail(to: @group_member.created_by.notification_email, + subject: subject("Invitation accepted")) + end + + def group_invite_declined_email(group_id, invite_email, access_level, created_by_id) + return if created_by_id.nil? + + @group = Group.find(group_id) + @current_user = @created_by = User.find(created_by_id) + @access_level = access_level + @invite_email = invite_email + + @target_url = group_url(@group) + mail(to: @created_by.notification_email, + subject: subject("Invitation declined")) + end end end diff --git a/app/mailers/emails/issues.rb b/app/mailers/emails/issues.rb index e534623596..687bac3aa3 100644 --- a/app/mailers/emails/issues.rb +++ b/app/mailers/emails/issues.rb @@ -3,7 +3,7 @@ module Emails def new_issue_email(recipient_id, issue_id) @issue = Issue.find(issue_id) @project = @issue.project - @target_url = project_issue_url(@project, @issue) + @target_url = namespace_project_issue_url(@project.namespace, @project, @issue) mail_new_thread(@issue, from: sender(@issue.author_id), to: recipient(recipient_id), @@ -14,7 +14,7 @@ module Emails @issue = Issue.find(issue_id) @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id @project = @issue.project - @target_url = project_issue_url(@project, @issue) + @target_url = namespace_project_issue_url(@project.namespace, @project, @issue) mail_answer_thread(@issue, from: sender(updated_by_user_id), to: recipient(recipient_id), @@ -25,7 +25,7 @@ module Emails @issue = Issue.find issue_id @project = @issue.project @updated_by = User.find updated_by_user_id - @target_url = project_issue_url(@project, @issue) + @target_url = namespace_project_issue_url(@project.namespace, @project, @issue) mail_answer_thread(@issue, from: sender(updated_by_user_id), to: recipient(recipient_id), @@ -37,7 +37,7 @@ module Emails @issue_status = status @project = @issue.project @updated_by = User.find updated_by_user_id - @target_url = project_issue_url(@project, @issue) + @target_url = namespace_project_issue_url(@project.namespace, @project, @issue) mail_answer_thread(@issue, from: sender(updated_by_user_id), to: recipient(recipient_id), diff --git a/app/mailers/emails/merge_requests.rb b/app/mailers/emails/merge_requests.rb index 9ecdac87d7..512a8f7ea6 100644 --- a/app/mailers/emails/merge_requests.rb +++ b/app/mailers/emails/merge_requests.rb @@ -3,7 +3,9 @@ module Emails def new_merge_request_email(recipient_id, merge_request_id) @merge_request = MergeRequest.find(merge_request_id) @project = @merge_request.project - @target_url = project_merge_request_url(@project, @merge_request) + @target_url = namespace_project_merge_request_url(@project.namespace, + @project, + @merge_request) mail_new_thread(@merge_request, from: sender(@merge_request.author_id), to: recipient(recipient_id), @@ -14,7 +16,9 @@ module Emails @merge_request = MergeRequest.find(merge_request_id) @previous_assignee = User.find_by(id: previous_assignee_id) if previous_assignee_id @project = @merge_request.project - @target_url = project_merge_request_url(@project, @merge_request) + @target_url = namespace_project_merge_request_url(@project.namespace, + @project, + @merge_request) mail_answer_thread(@merge_request, from: sender(updated_by_user_id), to: recipient(recipient_id), @@ -25,7 +29,9 @@ module Emails @merge_request = MergeRequest.find(merge_request_id) @updated_by = User.find updated_by_user_id @project = @merge_request.project - @target_url = project_merge_request_url(@project, @merge_request) + @target_url = namespace_project_merge_request_url(@project.namespace, + @project, + @merge_request) mail_answer_thread(@merge_request, from: sender(updated_by_user_id), to: recipient(recipient_id), @@ -35,7 +41,9 @@ module Emails def merged_merge_request_email(recipient_id, merge_request_id, updated_by_user_id) @merge_request = MergeRequest.find(merge_request_id) @project = @merge_request.project - @target_url = project_merge_request_url(@project, @merge_request) + @target_url = namespace_project_merge_request_url(@project.namespace, + @project, + @merge_request) mail_answer_thread(@merge_request, from: sender(updated_by_user_id), to: recipient(recipient_id), @@ -47,7 +55,9 @@ module Emails @mr_status = status @project = @merge_request.project @updated_by = User.find updated_by_user_id - @target_url = project_merge_request_url(@project, @merge_request) + @target_url = namespace_project_merge_request_url(@project.namespace, + @project, + @merge_request) set_reference("merge_request_#{merge_request_id}") mail_answer_thread(@merge_request, from: sender(updated_by_user_id), @@ -56,7 +66,7 @@ module Emails end end - # Over rides default behavour to show source/target + # Over rides default behaviour to show source/target # Formats arguments into a String suitable for use as an email subject # # extra - Extra Strings to be inserted into the subject diff --git a/app/mailers/emails/notes.rb b/app/mailers/emails/notes.rb index ef9af726a6..ff251209e0 100644 --- a/app/mailers/emails/notes.rb +++ b/app/mailers/emails/notes.rb @@ -4,7 +4,9 @@ module Emails @note = Note.find(note_id) @commit = @note.noteable @project = @note.project - @target_url = project_commit_url(@project, @commit, anchor: "note_#{@note.id}") + @target_url = namespace_project_commit_url(@project.namespace, @project, + @commit, anchor: + "note_#{@note.id}") mail_answer_thread(@commit, from: sender(@note.author_id), to: recipient(recipient_id), @@ -15,7 +17,9 @@ module Emails @note = Note.find(note_id) @issue = @note.noteable @project = @note.project - @target_url = project_issue_url(@project, @issue, anchor: "note_#{@note.id}") + @target_url = namespace_project_issue_url(@project.namespace, @project, + @issue, anchor: + "note_#{@note.id}") mail_answer_thread(@issue, from: sender(@note.author_id), to: recipient(recipient_id), @@ -26,7 +30,10 @@ module Emails @note = Note.find(note_id) @merge_request = @note.noteable @project = @note.project - @target_url = project_merge_request_url(@project, @merge_request, anchor: "note_#{@note.id}") + @target_url = namespace_project_merge_request_url(@project.namespace, + @project, + @merge_request, anchor: + "note_#{@note.id}") mail_answer_thread(@merge_request, from: sender(@note.author_id), to: recipient(recipient_id), diff --git a/app/mailers/emails/profile.rb b/app/mailers/emails/profile.rb index f8a7d133d1..3a83b08310 100644 --- a/app/mailers/emails/profile.rb +++ b/app/mailers/emails/profile.rb @@ -1,24 +1,23 @@ module Emails module Profile - def new_user_email(user_id, password, token = nil) - @user = User.find(user_id) - @password = password + def new_user_email(user_id, token = nil) + @current_user = @user = User.find(user_id) @target_url = user_url(@user) @token = token - mail(to: @user.email, subject: subject("Account was created for you")) + mail(to: @user.notification_email, subject: subject("Account was created for you")) end def new_email_email(email_id) @email = Email.find(email_id) - @user = @email.user - mail(to: @user.email, subject: subject("Email was added to your account")) + @current_user = @user = @email.user + mail(to: @user.notification_email, subject: subject("Email was added to your account")) end def new_ssh_key_email(key_id) @key = Key.find(key_id) - @user = @key.user + @current_user = @user = @key.user @target_url = user_url(@user) - mail(to: @user.email, subject: subject("SSH key was added to your account")) + mail(to: @user.notification_email, subject: subject("SSH key was added to your account")) end end end diff --git a/app/mailers/emails/projects.rb b/app/mailers/emails/projects.rb index 6b13a1d746..0dbb2939bb 100644 --- a/app/mailers/emails/projects.rb +++ b/app/mailers/emails/projects.rb @@ -1,39 +1,141 @@ module Emails module Projects - def project_access_granted_email(user_project_id) - @users_project = UsersProject.find user_project_id - @project = @users_project.project - @target_url = project_url(@project) - mail(to: @users_project.user.email, + def project_access_granted_email(project_member_id) + @project_member = ProjectMember.find project_member_id + @project = @project_member.project + + @target_url = namespace_project_url(@project.namespace, @project) + @current_user = @project_member.user + + mail(to: @project_member.user.notification_email, subject: subject("Access to project was granted")) end + def project_member_invited_email(project_member_id, token) + @project_member = ProjectMember.find project_member_id + @project = @project_member.project + @token = token + + @target_url = namespace_project_url(@project.namespace, @project) + @current_user = @project_member.user + + mail(to: @project_member.invite_email, + subject: "Invitation to join project #{@project.name_with_namespace}") + end + + def project_invite_accepted_email(project_member_id) + @project_member = ProjectMember.find project_member_id + return if @project_member.created_by.nil? + + @project = @project_member.project + + @target_url = namespace_project_url(@project.namespace, @project) + @current_user = @project_member.created_by + + mail(to: @project_member.created_by.notification_email, + subject: subject("Invitation accepted")) + end + + def project_invite_declined_email(project_id, invite_email, access_level, created_by_id) + return if created_by_id.nil? + + @project = Project.find(project_id) + @current_user = @created_by = User.find(created_by_id) + @access_level = access_level + @invite_email = invite_email + + @target_url = namespace_project_url(@project.namespace, @project) + + mail(to: @created_by.notification_email, + subject: subject("Invitation declined")) + end + def project_was_moved_email(project_id, user_id) - @user = User.find user_id + @current_user = @user = User.find user_id @project = Project.find project_id - @target_url = project_url(@project) - mail(to: @user.email, + @target_url = namespace_project_url(@project.namespace, @project) + mail(to: @user.notification_email, subject: subject("Project was moved")) end - def repository_push_email(project_id, recipient, author_id, branch, compare) - @project = Project.find(project_id) - @author = User.find(author_id) - @compare = compare - @commits = Commit.decorate(compare.commits) - @diffs = compare.diffs - @branch = branch - if @commits.length > 1 - @target_url = project_compare_url(@project, from: @commits.first, to: @commits.last) - @subject = "#{@commits.length} new commits pushed to repository" - else - @target_url = project_commit_url(@project, @commits.first) - @subject = @commits.first.title + def repository_push_email(project_id, recipient, author_id: nil, + ref: nil, + action: nil, + compare: nil, + reverse_compare: false, + send_from_committer_email: false, + disable_diffs: false) + unless author_id && ref && action + raise ArgumentError, "missing keywords: author_id, ref, action" end - mail(from: sender(author_id), - to: recipient, - subject: subject(@subject)) + @project = Project.find(project_id) + @current_user = @author = User.find(author_id) + @reverse_compare = reverse_compare + @compare = compare + @ref_name = Gitlab::Git.ref_name(ref) + @ref_type = Gitlab::Git.tag_ref?(ref) ? "tag" : "branch" + @action = action + @disable_diffs = disable_diffs + + if @compare + @commits = Commit.decorate(compare.commits) + @diffs = compare.diffs + end + + @action_name = + case action + when :create + "pushed new" + when :delete + "deleted" + else + "pushed to" + end + + @subject = "[#{@project.path_with_namespace}]" + @subject << "[#{@ref_name}]" if action == :push + @subject << " " + + if action == :push + if @commits.length > 1 + @target_url = namespace_project_compare_url(@project.namespace, + @project, + from: Commit.new(@compare.base), + to: Commit.new(@compare.head)) + @subject << "Deleted " if @reverse_compare + @subject << "#{@commits.length} commits: #{@commits.first.title}" + else + @target_url = namespace_project_commit_url(@project.namespace, + @project, @commits.first) + + @subject << "Deleted 1 commit: " if @reverse_compare + @subject << @commits.first.title + end + else + unless action == :delete + @target_url = namespace_project_tree_url(@project.namespace, + @project, @ref_name) + end + + subject_action = @action_name.dup + subject_action[0] = subject_action[0].capitalize + @subject << "#{subject_action} #{@ref_type} #{@ref_name}" + end + + @disable_footer = true + + reply_to = + if send_from_committer_email && can_send_from_user_email?(@author) + @author.email + else + Gitlab.config.gitlab.email_reply_to + end + + mail(from: sender(author_id, send_from_committer_email), + reply_to: reply_to, + to: recipient, + subject: @subject) end end end diff --git a/app/mailers/notify.rb b/app/mailers/notify.rb index bd438bab89..2c0d451511 100644 --- a/app/mailers/notify.rb +++ b/app/mailers/notify.rb @@ -11,6 +11,10 @@ class Notify < ActionMailer::Base add_template_helper ApplicationHelper add_template_helper GitlabMarkdownHelper add_template_helper MergeRequestsHelper + add_template_helper EmailsHelper + + attr_accessor :current_user + helper_method :current_user, :can? default_url_options[:host] = Gitlab.config.gitlab.host default_url_options[:protocol] = Gitlab.config.gitlab.protocol @@ -18,30 +22,62 @@ class Notify < ActionMailer::Base default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root default from: Proc.new { default_sender_address.format } - default reply_to: "noreply@#{Gitlab.config.gitlab.host}" + default reply_to: Gitlab.config.gitlab.email_reply_to # Just send email with 2 seconds delay def self.delay delay_for(2.seconds) end + def test_email(recipient_email, subject, body) + mail(to: recipient_email, + subject: subject, + body: body.html_safe, + content_type: 'text/html' + ) + end + + # Splits "gitlab.corp.company.com" up into "gitlab.corp.company.com", + # "corp.company.com" and "company.com". + # Respects set tld length so "company.co.uk" won't match "somethingelse.uk" + def self.allowed_email_domains + domain_parts = Gitlab.config.gitlab.host.split(".") + allowed_domains = [] + begin + allowed_domains << domain_parts.join(".") + domain_parts.shift + end while domain_parts.length > ActionDispatch::Http::URL.tld_length + + allowed_domains + end + private # The default email address to send emails from def default_sender_address address = Mail::Address.new(Gitlab.config.gitlab.email_from) - address.display_name = "GitLab" + address.display_name = Gitlab.config.gitlab.email_display_name address end + def can_send_from_user_email?(sender) + sender_domain = sender.email.split("@").last + self.class.allowed_email_domains.include?(sender_domain) + end + # Return an email address that displays the name of the sender. # Only the displayed name changes; the actual email address is always the same. - def sender(sender_id) - if sender = User.find(sender_id) - address = default_sender_address - address.display_name = sender.name - address.format + def sender(sender_id, send_from_user_email = false) + return unless sender = User.find(sender_id) + + address = default_sender_address + address.display_name = sender.name + + if send_from_user_email && can_send_from_user_email?(sender) + address.address = sender.email end + + address.format end # Look up a User by their ID and return their email address @@ -50,9 +86,8 @@ class Notify < ActionMailer::Base # # Returns a String containing the User's email address. def recipient(recipient_id) - if recipient = User.find(recipient_id) - recipient.email - end + @current_user = User.find(recipient_id) + @current_user.notification_email end # Set the References header field @@ -102,6 +137,7 @@ class Notify < ActionMailer::Base # See: mail_answer_thread def mail_new_thread(model, headers = {}, &block) headers['Message-ID'] = message_id(model) + headers['X-GitLab-Project'] = "#{@project.name} | " if @project mail(headers, &block) end @@ -116,11 +152,16 @@ class Notify < ActionMailer::Base def mail_answer_thread(model, headers = {}, &block) headers['In-Reply-To'] = message_id(model) headers['References'] = message_id(model) + headers['X-GitLab-Project'] = "#{@project.name} | " if @project - if (headers[:subject]) + if headers[:subject] headers[:subject].prepend('Re: ') end mail(headers, &block) end + + def can? + Ability.abilities.allowed?(user, action, subject) + end end diff --git a/app/models/ability.rb b/app/models/ability.rb index f1d57de63b..85a15596f8 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -14,7 +14,7 @@ class Ability when "MergeRequest" then merge_request_abilities(user, subject) when "Group" then group_abilities(user, subject) when "Namespace" then namespace_abilities(user, subject) - when "UsersGroup" then users_group_abilities(user, subject) + when "GroupMember" then group_member_abilities(user, subject) else [] end.concat(global_abilities(user)) end @@ -37,7 +37,7 @@ class Ability :read_issue, :read_milestone, :read_project_snippet, - :read_team_member, + :read_project_member, :read_merge_request, :read_note, :download_code @@ -73,28 +73,28 @@ class Ability # Rules based on role in project if team.master?(user) - rules += project_master_rules + rules.push(*project_master_rules) elsif team.developer?(user) - rules += project_dev_rules + rules.push(*project_dev_rules) elsif team.reporter?(user) - rules += project_report_rules + rules.push(*project_report_rules) elsif team.guest?(user) - rules += project_guest_rules + rules.push(*project_guest_rules) end if project.public? || project.internal? - rules += public_project_rules + rules.push(*public_project_rules) end if project.owner == user || user.admin? - rules += project_admin_rules + rules.push(*project_admin_rules) end if project.group && project.group.has_owner?(user) - rules += project_admin_rules + rules.push(*project_admin_rules) end if project.archived? @@ -119,7 +119,7 @@ class Ability :read_issue, :read_milestone, :read_project_snippet, - :read_team_member, + :read_project_member, :read_merge_request, :read_note, :write_project, @@ -166,7 +166,7 @@ class Ability :admin_issue, :admin_milestone, :admin_project_snippet, - :admin_team_member, + :admin_project_member, :admin_merge_request, :admin_note, :admin_wiki, @@ -184,7 +184,7 @@ class Ability ] end - def group_abilities user, group + def group_abilities(user, group) rules = [] if user.admin? || group.users.include?(user) || ProjectsFinder.new.execute(user, group: group).any? @@ -193,31 +193,31 @@ class Ability # Only group masters and group owners can create new projects in group if group.has_master?(user) || group.has_owner?(user) || user.admin? - rules += [ + rules.push(*[ :create_projects, - ] + ]) end - # Only group owner and administrators can manage group + # Only group owner and administrators can admin group if group.has_owner?(user) || user.admin? - rules += [ - :manage_group, - :manage_namespace - ] + rules.push(*[ + :admin_group, + :admin_namespace + ]) end rules.flatten end - def namespace_abilities user, namespace + def namespace_abilities(user, namespace) rules = [] - # Only namespace owner and administrators can manage it + # Only namespace owner and administrators can admin it if namespace.owner == user || user.admin? - rules += [ + rules.push(*[ :create_projects, - :manage_namespace - ] + :admin_namespace + ]) end rules.flatten @@ -225,13 +225,15 @@ class Ability [:issue, :note, :project_snippet, :personal_snippet, :merge_request].each do |name| define_method "#{name}_abilities" do |user, subject| - if subject.author == user - [ + if subject.author == user || user.is_admin? + rules = [ :"read_#{name}", :"write_#{name}", :"modify_#{name}", :"admin_#{name}" ] + rules.push(:change_visibility_level) if subject.is_a?(Snippet) + rules elsif subject.respond_to?(:assignee) && subject.assignee == user [ :"read_#{name}", @@ -248,19 +250,27 @@ class Ability end end - def users_group_abilities(user, subject) + def group_member_abilities(user, subject) rules = [] target_user = subject.user group = subject.group - can_manage = group_abilities(user, group).include?(:manage_group) + can_manage = group_abilities(user, group).include?(:admin_group) if can_manage && (user != target_user) - rules << :modify - rules << :destroy + rules << :modify_group_member + rules << :destroy_group_member end if !group.last_owner?(user) && (can_manage || (user == target_user)) - rules << :destroy + rules << :destroy_group_member end rules end + + def abilities + @abilities ||= begin + abilities = Six.new + abilities << self + abilities + end + end end end diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb new file mode 100644 index 0000000000..0d8365c4ff --- /dev/null +++ b/app/models/application_setting.rb @@ -0,0 +1,61 @@ +# == Schema Information +# +# Table name: application_settings +# +# id :integer not null, primary key +# default_projects_limit :integer +# default_branch_protection :integer +# signup_enabled :boolean +# signin_enabled :boolean +# gravatar_enabled :boolean +# twitter_sharing_enabled :boolean +# sign_in_text :text +# created_at :datetime +# updated_at :datetime +# home_page_url :string(255) +# default_branch_protection :integer default(2) +# twitter_sharing_enabled :boolean default(TRUE) +# restricted_visibility_levels :text +# max_attachment_size :integer default(10) +# + +class ApplicationSetting < ActiveRecord::Base + serialize :restricted_visibility_levels + + validates :home_page_url, + allow_blank: true, + format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, + if: :home_page_url_column_exist + + validates_each :restricted_visibility_levels do |record, attr, value| + unless value.nil? + value.each do |level| + unless Gitlab::VisibilityLevel.options.has_value?(level) + record.errors.add(attr, "'#{level}' is not a valid visibility level") + end + end + end + end + + def self.current + ApplicationSetting.last + end + + def self.create_from_defaults + create( + default_projects_limit: Settings.gitlab['default_projects_limit'], + default_branch_protection: Settings.gitlab['default_branch_protection'], + signup_enabled: Settings.gitlab['signup_enabled'], + signin_enabled: Settings.gitlab['signin_enabled'], + twitter_sharing_enabled: Settings.gitlab['twitter_sharing_enabled'], + gravatar_enabled: Settings.gravatar['enabled'], + sign_in_text: Settings.extra['sign_in_text'], + restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'], + max_attachment_size: Settings.gitlab['max_attachment_size'] + ) + end + + def home_page_url_column_exist + ActiveRecord::Base.connection.column_exists?(:application_settings, :home_page_url) + end +end diff --git a/app/models/broadcast_message.rb b/app/models/broadcast_message.rb index 4d0c04bcc3..05f5e97969 100644 --- a/app/models/broadcast_message.rb +++ b/app/models/broadcast_message.rb @@ -14,6 +14,8 @@ # class BroadcastMessage < ActiveRecord::Base + include Sortable + validates :message, presence: true validates :starts_at, presence: true validates :ends_at, presence: true diff --git a/app/models/commit.rb b/app/models/commit.rb index ff5392957c..006fa62c8f 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -10,22 +10,33 @@ class Commit # Used to prevent 500 error on huge commits by suppressing diff # # User can force display of diff above this size - DIFF_SAFE_FILES = 100 - DIFF_SAFE_LINES = 5000 + DIFF_SAFE_FILES = 100 unless defined?(DIFF_SAFE_FILES) + DIFF_SAFE_LINES = 5000 unless defined?(DIFF_SAFE_LINES) # Commits above this size will not be rendered in HTML - DIFF_HARD_LIMIT_FILES = 1000 - DIFF_HARD_LIMIT_LINES = 50000 + DIFF_HARD_LIMIT_FILES = 1000 unless defined?(DIFF_HARD_LIMIT_FILES) + DIFF_HARD_LIMIT_LINES = 50000 unless defined?(DIFF_HARD_LIMIT_LINES) class << self def decorate(commits) - commits.map { |c| self.new(c) } + commits.map do |commit| + if commit.kind_of?(Commit) + commit + else + self.new(commit) + end + end end # Calculate number of lines to render for diffs def diff_line_count(diffs) diffs.reduce(0) { |sum, d| sum + d.diff.lines.count } end + + # Truncate sha to 8 characters + def truncate_sha(sha) + sha[0..7] + end end attr_accessor :raw @@ -64,11 +75,11 @@ class Commit return no_commit_message if title.blank? - title_end = title.index(/\n/) + title_end = title.index("\n") if (!title_end && title.length > 100) || (title_end && title_end > 100) - title[0..79] << "…".html_safe + title[0..79] << "…" else - title.split(/\n/, 2).first + title.split("\n", 2).first end end @@ -76,27 +87,51 @@ class Commit # # cut off, ellipses (`&hellp;`) are prepended to the commit message. def description - title_end = safe_message.index(/\n/) - @description ||= if (!title_end && safe_message.length > 100) || (title_end && title_end > 100) - "…".html_safe << safe_message[80..-1] - else - safe_message.split(/\n/, 2)[1].try(:chomp) - end + title_end = safe_message.index("\n") + @description ||= + if (!title_end && safe_message.length > 100) || (title_end && title_end > 100) + "…" << safe_message[80..-1] + else + safe_message.split("\n", 2)[1].try(:chomp) + end end def description? description.present? end + def hook_attrs(project) + path_with_namespace = project.path_with_namespace + + { + id: id, + message: safe_message, + timestamp: committed_date.xmlschema, + url: "#{Gitlab.config.gitlab.url}/#{path_with_namespace}/commit/#{id}", + author: { + name: author_name, + email: author_email + } + } + end + # Discover issues should be closed when this commit is pushed to a project's # default branch. - def closes_issues project - Gitlab::ClosingIssueExtractor.closed_by_message_in_project(safe_message, project) + def closes_issues(project, current_user = self.committer) + Gitlab::ClosingIssueExtractor.new(project, current_user).closed_by_message(safe_message) end # Mentionable override. def gfm_reference - "commit #{sha[0..5]}" + "commit #{id}" + end + + def author + User.find_for_commit(author_email, author_name) + end + + def committer + User.find_for_commit(committer_email, committer_name) end def method_missing(m, *args, &block) @@ -108,4 +143,13 @@ class Commit super end + + # Truncate sha to 8 characters + def short_id + @raw.short_id(7) + end + + def parents + @parents ||= Commit.decorate(super) + end end diff --git a/app/models/concerns/issuable.rb b/app/models/concerns/issuable.rb index 5c9b44812b..478134dff6 100644 --- a/app/models/concerns/issuable.rb +++ b/app/models/concerns/issuable.rb @@ -15,6 +15,7 @@ module Issuable has_many :notes, as: :noteable, dependent: :destroy has_many :label_links, as: :target, dependent: :destroy has_many :labels, through: :label_links + has_many :subscriptions, dependent: :destroy, as: :subscribable validates :author, presence: true validates :title, presence: true, length: { within: 0..255 } @@ -29,6 +30,8 @@ module Issuable scope :only_opened, -> { with_state(:opened) } scope :only_reopened, -> { with_state(:reopened) } scope :closed, -> { with_state(:closed) } + scope :order_milestone_due_desc, -> { joins(:milestone).reorder('milestones.due_date DESC, milestones.id DESC') } + scope :order_milestone_due_asc, -> { joins(:milestone).reorder('milestones.due_date ASC, milestones.id ASC') } delegate :name, :email, @@ -49,15 +52,16 @@ module Issuable where("LOWER(title) like :query", query: "%#{query.downcase}%") end + def full_search(query) + where("LOWER(title) like :query OR LOWER(description) like :query", query: "%#{query.downcase}%") + end + def sort(method) case method.to_s - when 'newest' then reorder("#{table_name}.created_at DESC") - when 'oldest' then reorder("#{table_name}.created_at ASC") - when 'recently_updated' then reorder("#{table_name}.updated_at DESC") - when 'last_updated' then reorder("#{table_name}.updated_at ASC") - when 'milestone_due_soon' then joins(:milestone).reorder("milestones.due_date ASC") - when 'milestone_due_later' then joins(:milestone).reorder("milestones.due_date DESC") - else reorder("#{table_name}.created_at DESC") + when 'milestone_due_asc' then order_milestone_due_asc + when 'milestone_due_desc' then order_milestone_due_desc + else + order_by(method) end end end @@ -84,7 +88,7 @@ module Issuable # Return the number of -1 comments (downvotes) def downvotes - notes.select(&:downvote?).size + filter_superceded_votes(notes.select(&:downvote?), notes).size end def downvotes_in_percent @@ -97,7 +101,7 @@ module Issuable # Return the number of +1 comments (upvotes) def upvotes - notes.select(&:upvote?).size + filter_superceded_votes(notes.select(&:upvote?), notes).size end def upvotes_in_percent @@ -114,23 +118,42 @@ module Issuable end # Return all users participating on the discussion - def participants + def participants(current_user = self.author) users = [] users << author users << assignee if is_assigned? mentions = [] - mentions << self.mentioned_users + mentions << self.mentioned_users(current_user) + notes.each do |note| users << note.author - mentions << note.mentioned_users + mentions << note.mentioned_users(current_user) end + users.concat(mentions.reduce([], :|)).uniq end - def to_hook_data + def subscribed?(user) + subscription = subscriptions.find_by_user_id(user.id) + + if subscription + return subscription.subscribed + end + + participants(user).include?(user) + end + + def toggle_subscription(user) + subscriptions. + find_or_initialize_by(user_id: user.id). + update(subscribed: !subscribed?(user)) + end + + def to_hook_data(user) { object_kind: self.class.name.underscore, - object_attributes: self.attributes + user: user.hook_attrs, + object_attributes: hook_attrs } end @@ -144,9 +167,23 @@ module Issuable def add_labels_by_names(label_names) label_names.each do |label_name| - label = project.labels.create_with( - color: Label::DEFAULT_COLOR).find_or_create_by(title: label_name.strip) + label = project.labels.create_with(color: Label::DEFAULT_COLOR). + find_or_create_by(title: label_name.strip) self.labels << label end end + + private + + def filter_superceded_votes(votes, notes) + filteredvotes = [] + votes + + votes.each do |vote| + if vote.superceded?(notes) + filteredvotes.delete(vote) + end + end + + filteredvotes + end end diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb index 81414959f3..b7882a2bb1 100644 --- a/app/models/concerns/mentionable.rb +++ b/app/models/concerns/mentionable.rb @@ -10,7 +10,7 @@ module Mentionable module ClassMethods # Indicate which attributes of the Mentionable to search for GFM references. - def attr_mentionable *attrs + def attr_mentionable(*attrs) mentionable_attrs.concat(attrs.map(&:to_s)) end @@ -38,41 +38,30 @@ module Mentionable # Determine whether or not a cross-reference Note has already been created between this Mentionable and # the specified target. - def has_mentioned? target + def has_mentioned?(target) Note.cross_reference_exists?(target, local_reference) end - def mentioned_users - users = [] - return users if mentionable_text.blank? - has_project = self.respond_to? :project - matches = mentionable_text.scan(/@[a-zA-Z][a-zA-Z0-9_\-\.]*/) - matches.each do |match| - identifier = match.delete "@" - if identifier == "all" - users += project.team.members.flatten - else - if has_project - id = project.team.members.find_by(username: identifier).try(:id) - else - id = User.find_by(username: identifier).try(:id) - end - users << User.find(id) unless id.blank? - end - end - users.uniq + def mentioned_users(current_user = nil) + return [] if mentionable_text.blank? + + ext = Gitlab::ReferenceExtractor.new(self.project, current_user) + ext.analyze(mentionable_text) + ext.users.uniq end # Extract GFM references to other Mentionables from this Mentionable. Always excludes its #local_reference. - def references p = project, text = mentionable_text + def references(p = project, current_user = self.author, text = mentionable_text) return [] if text.blank? - ext = Gitlab::ReferenceExtractor.new + + ext = Gitlab::ReferenceExtractor.new(p, current_user) ext.analyze(text) - (ext.issues_for(p) + ext.merge_requests_for(p) + ext.commits_for(p)).uniq - [local_reference] + + (ext.issues + ext.merge_requests + ext.commits).uniq - [local_reference] end # Create a cross-reference Note for each GFM reference to another Mentionable found in +mentionable_text+. - def create_cross_references! p = project, a = author, without = [] + def create_cross_references!(p = project, a = author, without = []) refs = references(p) - without refs.each do |ref| Note.create_cross_reference_note(ref, local_reference, a, p) @@ -81,7 +70,7 @@ module Mentionable # If the mentionable_text field is about to change, locate any *added* references and create cross references for # them. Invoke from an observer's #before_save implementation. - def notice_added_references p = project, a = author + def notice_added_references(p = project, a = author) ch = changed_attributes original, mentionable_changed = "", false self.class.mentionable_attrs.each do |attr| @@ -94,8 +83,7 @@ module Mentionable # Only proceed if the saved changes actually include a chance to an attr_mentionable field. return unless mentionable_changed - preexisting = references(p, original) + preexisting = references(p, self.author, original) create_cross_references!(p, a, preexisting) end - end diff --git a/app/models/concerns/notifiable.rb b/app/models/concerns/notifiable.rb index 722f375e71..d7dcd97911 100644 --- a/app/models/concerns/notifiable.rb +++ b/app/models/concerns/notifiable.rb @@ -1,6 +1,6 @@ # == Notifiable concern # -# Contains notification functionality shared between UsersProject and UsersGroup +# Contains notification functionality # module Notifiable extend ActiveSupport::Concern diff --git a/app/models/concerns/sortable.rb b/app/models/concerns/sortable.rb new file mode 100644 index 0000000000..0ad2654867 --- /dev/null +++ b/app/models/concerns/sortable.rb @@ -0,0 +1,35 @@ +# == Sortable concern +# +# Set default scope for ordering objects +# +module Sortable + extend ActiveSupport::Concern + + included do + # By default all models should be ordered + # by created_at field starting from newest + default_scope { order(created_at: :desc, id: :desc) } + + scope :order_created_desc, -> { reorder(created_at: :desc, id: :desc) } + scope :order_created_asc, -> { reorder(created_at: :asc, id: :asc) } + scope :order_updated_desc, -> { reorder(updated_at: :desc, id: :desc) } + scope :order_updated_asc, -> { reorder(updated_at: :asc, id: :asc) } + scope :order_name_asc, -> { reorder(name: :asc) } + scope :order_name_desc, -> { reorder(name: :desc) } + end + + module ClassMethods + def order_by(method) + case method.to_s + when 'name_asc' then order_name_asc + when 'name_desc' then order_name_desc + when 'updated_asc' then order_updated_asc + when 'updated_desc' then order_updated_desc + when 'created_asc' then order_created_asc + when 'created_desc' then order_created_desc + else + all + end + end + end +end diff --git a/app/models/concerns/taskable.rb b/app/models/concerns/taskable.rb new file mode 100644 index 0000000000..bbb3b301a9 --- /dev/null +++ b/app/models/concerns/taskable.rb @@ -0,0 +1,51 @@ +# Contains functionality for objects that can have task lists in their +# descriptions. Task list items can be added with Markdown like "* [x] Fix +# bugs". +# +# Used by MergeRequest and Issue +module Taskable + TASK_PATTERN_MD = /^(? *[*-] *)\[(?[ xX])\]/.freeze + TASK_PATTERN_HTML = /^
  • (?\s*

    )?\[(?[ xX])\]/.freeze + + # Change the state of a task list item for this Taskable. Edit the object's + # description by finding the nth task item and changing its checkbox + # placeholder to "[x]" if +checked+ is true, or "[ ]" if it's false. + # Note: task numbering starts with 1 + def update_nth_task(n, checked) + index = 0 + check_char = checked ? 'x' : ' ' + + # Do this instead of using #gsub! so that ActiveRecord detects that a field + # has changed. + self.description = self.description.gsub(TASK_PATTERN_MD) do |match| + index += 1 + case index + when n then "#{$LAST_MATCH_INFO[:bullet]}[#{check_char}]" + else match + end + end + + save + end + + # Return true if this object's description has any task list items. + def tasks? + description && description.match(TASK_PATTERN_MD) + end + + # Return a string that describes the current state of this Taskable's task + # list items, e.g. "20 tasks (12 done, 8 unfinished)" + def task_status + return nil unless description + + num_tasks = 0 + num_done = 0 + + description.scan(TASK_PATTERN_MD) do + num_tasks += 1 + num_done += 1 unless $LAST_MATCH_INFO[:checked] == ' ' + end + + "#{num_tasks} tasks (#{num_done} done, #{num_tasks - num_done} unfinished)" + end +end diff --git a/app/models/deploy_key.rb b/app/models/deploy_key.rb index 570f5e91c1..85d52d558c 100644 --- a/app/models/deploy_key.rb +++ b/app/models/deploy_key.rb @@ -7,6 +7,7 @@ # created_at :datetime # updated_at :datetime # key :text +# public :boolean default(FALSE) # title :string(255) # type :string(255) # fingerprint :string(255) @@ -17,4 +18,21 @@ class DeployKey < Key has_many :projects, through: :deploy_keys_projects scope :in_projects, ->(projects) { joins(:deploy_keys_projects).where('deploy_keys_projects.project_id in (?)', projects) } + scope :are_public, -> { where(public: true) } + + def private? + !public? + end + + def orphaned? + self.deploy_keys_projects.length == 0 + end + + def almost_orphaned? + self.deploy_keys_projects.length == 1 + end + + def destroyed_when_orphaned? + self.private? + end end diff --git a/app/models/deploy_keys_project.rb b/app/models/deploy_keys_project.rb index f23d8205dd..18db521741 100644 --- a/app/models/deploy_keys_project.rb +++ b/app/models/deploy_keys_project.rb @@ -16,4 +16,14 @@ class DeployKeysProject < ActiveRecord::Base validates :deploy_key_id, presence: true validates :deploy_key_id, uniqueness: { scope: [:project_id], message: "already exists in project" } validates :project_id, presence: true + + after_destroy :destroy_orphaned_deploy_key + + private + + def destroy_orphaned_deploy_key + return unless self.deploy_key.destroyed_when_orphaned? && self.deploy_key.orphaned? + + self.deploy_key.destroy + end end diff --git a/app/models/email.rb b/app/models/email.rb index 57f476bd51..556b0e9586 100644 --- a/app/models/email.rb +++ b/app/models/email.rb @@ -10,6 +10,8 @@ # class Email < ActiveRecord::Base + include Sortable + belongs_to :user validates :user_id, presence: true diff --git a/app/models/event.rb b/app/models/event.rb index 9e296c0028..c9a88ffa8e 100644 --- a/app/models/event.rb +++ b/app/models/event.rb @@ -15,6 +15,7 @@ # class Event < ActiveRecord::Base + include Sortable default_scope { where.not(author_id: nil) } CREATED = 1 @@ -46,36 +47,20 @@ class Event < ActiveRecord::Base scope :recent, -> { order("created_at DESC") } scope :code_push, -> { where(action: PUSHED) } scope :in_projects, ->(project_ids) { where(project_id: project_ids).recent } + scope :with_associations, -> { includes(project: :namespace) } class << self - def create_ref_event(project, user, ref, action = 'add', prefix = 'refs/heads') - commit = project.repository.commit(ref.target) - - if action.to_s == 'add' - before = '00000000' - after = commit.id - else - before = commit.id - after = '00000000' - end - - Event.create( - project: project, - action: Event::PUSHED, - data: { - ref: "#{prefix}/#{ref.name}", - before: before, - after: after - }, - author_id: user.id - ) - end - def reset_event_cache_for(target) Event.where(target_id: target.id, target_type: target.class.to_s). order('id DESC').limit(100). update_all(updated_at: Time.now) end + + def contributions + where("action = ? OR (target_type in (?) AND action in (?))", + Event::PUSHED, ["MergeRequest", "Issue"], + [Event::CREATED, Event::CLOSED, Event::MERGED]) + end end def proper? @@ -83,6 +68,8 @@ class Event < ActiveRecord::Base true elsif membership_changed? true + elsif created_project? + true else (issue? || merge_request? || note? || milestone?) && target end @@ -97,25 +84,51 @@ class Event < ActiveRecord::Base end def target_title - if target && target.respond_to?(:title) - target.title - end + target.title if target && target.respond_to?(:title) + end + + def created? + action == CREATED end def push? - action == self.class::PUSHED && valid_push? + action == PUSHED && valid_push? end def merged? - action == self.class::MERGED + action == MERGED end def closed? - action == self.class::CLOSED + action == CLOSED end def reopened? - action == self.class::REOPENED + action == REOPENED + end + + def joined? + action == JOINED + end + + def left? + action == LEFT + end + + def commented? + action == COMMENTED + end + + def membership_changed? + joined? || left? + end + + def created_project? + created? && !target + end + + def created_target? + created? && target end def milestone? @@ -134,32 +147,32 @@ class Event < ActiveRecord::Base target_type == "MergeRequest" end - def joined? - action == JOINED - end - - def left? - action == LEFT - end - - def membership_changed? - joined? || left? + def milestone + target if milestone? end def issue - target if target_type == "Issue" + target if issue? end def merge_request - target if target_type == "MergeRequest" + target if merge_request? end def note - target if target_type == "Note" + target if note? end def action_name - if closed? + if push? + if new_ref? + "pushed new" + elsif rm_ref? + "deleted" + else + "pushed to" + end + elsif closed? "closed" elsif merged? "accepted" @@ -167,6 +180,14 @@ class Event < ActiveRecord::Base 'joined' elsif left? 'left' + elsif commented? + "commented on" + elsif created_project? + if project.import? + "imported" + else + "created" + end else "opened" end @@ -174,28 +195,24 @@ class Event < ActiveRecord::Base def valid_push? data[:ref] && ref_name.present? - rescue => ex + rescue false end def tag? - data[:ref]["refs/tags"] + Gitlab::Git.tag_ref?(data[:ref]) end def branch? - data[:ref]["refs/heads"] - end - - def new_branch? - commit_from =~ /^00000/ + Gitlab::Git.branch_ref?(data[:ref]) end def new_ref? - commit_from =~ /^00000/ + Gitlab::Git.blank_ref?(commit_from) end def rm_ref? - commit_to =~ /^00000/ + Gitlab::Git.blank_ref?(commit_to) end def md_ref? @@ -219,11 +236,11 @@ class Event < ActiveRecord::Base end def branch_name - @branch_name ||= data[:ref].gsub("refs/heads/", "") + @branch_name ||= Gitlab::Git.ref_name(data[:ref]) end def tag_name - @tag_name ||= data[:ref].gsub("refs/tags/", "") + @tag_name ||= Gitlab::Git.ref_name(data[:ref]) end # Max 20 commits from push DESC @@ -239,18 +256,8 @@ class Event < ActiveRecord::Base tag? ? "tag" : "branch" end - def push_action_name - if new_ref? - "pushed new" - elsif rm_ref? - "deleted" - else - "pushed to" - end - end - def push_with_commits? - md_ref? && commits.any? && commit_from && commit_to + !commits.empty? && commit_from && commit_to end def last_push_to_non_root? @@ -266,7 +273,7 @@ class Event < ActiveRecord::Base end def note_short_commit_id - note_commit_id[0..8] + Commit.truncate_sha(note_commit_id) end def note_commit? diff --git a/app/models/external_issue.rb b/app/models/external_issue.rb new file mode 100644 index 0000000000..50efcb32f1 --- /dev/null +++ b/app/models/external_issue.rb @@ -0,0 +1,25 @@ +class ExternalIssue + def initialize(issue_identifier, project) + @issue_identifier, @project = issue_identifier, project + end + + def to_s + @issue_identifier.to_s + end + + def id + @issue_identifier.to_s + end + + def iid + @issue_identifier.to_s + end + + def ==(other) + other.is_a?(self.class) && (to_s == other.to_s) + end + + def project + @project + end +end diff --git a/app/models/group.rb b/app/models/group.rb index 66239f7fe6..1386a9eccc 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -17,35 +17,47 @@ require 'carrierwave/orm/activerecord' require 'file_size_validator' class Group < Namespace - has_many :users_groups, dependent: :destroy - has_many :users, through: :users_groups + has_many :group_members, dependent: :destroy, as: :source, class_name: 'GroupMember' + has_many :users, through: :group_members validate :avatar_type, if: ->(user) { user.avatar_changed? } - validates :avatar, file_size: { maximum: 100.kilobytes.to_i } + validates :avatar, file_size: { maximum: 200.kilobytes.to_i } - mount_uploader :avatar, AttachmentUploader + mount_uploader :avatar, AvatarUploader + + after_create :post_create_hook + after_destroy :post_destroy_hook + + class << self + def search(query) + where("LOWER(namespaces.name) LIKE :query or LOWER(namespaces.path) LIKE :query", query: "%#{query.downcase}%") + end + + def sort(method) + order_by(method) + end + end def human_name name end def owners - @owners ||= users_groups.owners.map(&:user) + @owners ||= group_members.owners.map(&:user) end - def add_users(user_ids, group_access) - user_ids.compact.each do |user_id| - user = self.users_groups.find_or_initialize_by(user_id: user_id) - user.update_attributes(group_access: group_access) + def add_users(user_ids, access_level, current_user = nil) + user_ids.each do |user_id| + Member.add_user(self.group_members, user_id, access_level, current_user) end end - def add_user(user, group_access) - self.users_groups.create(user_id: user.id, group_access: group_access) + def add_user(user, access_level, current_user = nil) + add_users([user], access_level, current_user) end - def add_owner(user) - self.add_user(user, UsersGroup::OWNER) + def add_owner(user, current_user = nil) + self.add_user(user, Gitlab::Access::OWNER, current_user) end def has_owner?(user) @@ -61,7 +73,7 @@ class Group < Namespace end def members - users_groups + group_members end def avatar_type @@ -74,19 +86,15 @@ class Group < Namespace projects.public_only.any? end - class << self - def search(query) - where("LOWER(namespaces.name) LIKE :query", query: "%#{query.downcase}%") - end + def post_create_hook + system_hook_service.execute_hooks_for(self, :create) + end - def sort(method) - case method.to_s - when "newest" then reorder("namespaces.created_at DESC") - when "oldest" then reorder("namespaces.created_at ASC") - when "recently_updated" then reorder("namespaces.updated_at DESC") - when "last_updated" then reorder("namespaces.updated_at ASC") - else reorder("namespaces.path, namespaces.name ASC") - end - end + def post_destroy_hook + system_hook_service.execute_hooks_for(self, :destroy) + end + + def system_hook_service + SystemHooksService.new end end diff --git a/app/models/group_milestone.rb b/app/models/group_milestone.rb index 3391531378..7e4f16ebf1 100644 --- a/app/models/group_milestone.rb +++ b/app/models/group_milestone.rb @@ -66,15 +66,15 @@ class GroupMilestone end def issues - @group_issues ||= milestones.map { |milestone| milestone.issues }.flatten.group_by(&:state) + @group_issues ||= milestones.map(&:issues).flatten.group_by(&:state) end def merge_requests - @group_merge_requests ||= milestones.map { |milestone| milestone.merge_requests }.flatten.group_by(&:state) + @group_merge_requests ||= milestones.map(&:merge_requests).flatten.group_by(&:state) end def participants - milestones.map { |milestone| milestone.participants.uniq }.reject(&:empty?).flatten + @group_participants ||= milestones.map(&:participants).flatten.compact.uniq end def opened_issues diff --git a/app/models/project_hook.rb b/app/models/hooks/project_hook.rb similarity index 100% rename from app/models/project_hook.rb rename to app/models/hooks/project_hook.rb diff --git a/app/models/service_hook.rb b/app/models/hooks/service_hook.rb similarity index 100% rename from app/models/service_hook.rb rename to app/models/hooks/service_hook.rb diff --git a/app/models/system_hook.rb b/app/models/hooks/system_hook.rb similarity index 100% rename from app/models/system_hook.rb rename to app/models/hooks/system_hook.rb diff --git a/app/models/web_hook.rb b/app/models/hooks/web_hook.rb similarity index 69% rename from app/models/web_hook.rb rename to app/models/hooks/web_hook.rb index 6cf0c1f683..315d96af1b 100644 --- a/app/models/web_hook.rb +++ b/app/models/hooks/web_hook.rb @@ -16,22 +16,27 @@ # class WebHook < ActiveRecord::Base + include Sortable include HTTParty default_value_for :push_events, true default_value_for :issues_events, false default_value_for :merge_requests_events, false + default_value_for :tag_push_events, false # HTTParty timeout - default_timeout 10 + default_timeout Gitlab.config.gitlab.webhook_timeout validates :url, presence: true, - format: { with: URI::regexp(%w(http https)), message: "should be a valid url" } + format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" } def execute(data) parsed_url = URI.parse(url) if parsed_url.userinfo.blank? - WebHook.post(url, body: data.to_json, headers: { "Content-Type" => "application/json" }, verify: false) + WebHook.post(url, + body: data.to_json, + headers: { "Content-Type" => "application/json" }, + verify: false) else post_url = url.gsub("#{parsed_url.userinfo}@", "") auth = { @@ -40,10 +45,13 @@ class WebHook < ActiveRecord::Base } WebHook.post(post_url, body: data.to_json, - headers: {"Content-Type" => "application/json"}, + headers: { "Content-Type" => "application/json" }, verify: false, basic_auth: auth) end + rescue SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED, Net::OpenTimeout => e + logger.error("WebHook Error => #{e}") + false end def async_execute(data) diff --git a/app/models/identity.rb b/app/models/identity.rb new file mode 100644 index 0000000000..756d19adec --- /dev/null +++ b/app/models/identity.rb @@ -0,0 +1,19 @@ +# == 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 +# + +class Identity < ActiveRecord::Base + include Sortable + belongs_to :user + + validates :extern_uid, allow_blank: true, uniqueness: { scope: :provider } + validates :user_id, uniqueness: { scope: :provider } +end diff --git a/app/models/issue.rb b/app/models/issue.rb index 92a8ff9b67..6e10205138 100644 --- a/app/models/issue.rb +++ b/app/models/issue.rb @@ -15,7 +15,6 @@ # milestone_id :integer # state :string(255) # iid :integer -# attachment :string(255) # require 'carrierwave/orm/activerecord' @@ -24,6 +23,8 @@ require 'file_size_validator' class Issue < ActiveRecord::Base include Issuable include InternalId + include Taskable + include Sortable ActsAsTaggableOn.strict_case_match = true @@ -31,7 +32,6 @@ class Issue < ActiveRecord::Base validates :project, presence: true scope :of_group, ->(group) { where(project_id: group.project_ids) } - scope :of_user_team, ->(team) { where(project_id: team.project_ids, assignee_id: team.member_ids) } scope :cared, ->(user) { where(assignee_id: user) } scope :open_for, ->(user) { opened.assigned_to(user) } @@ -49,6 +49,10 @@ class Issue < ActiveRecord::Base state :closed end + def hook_attrs + attributes + end + # Mentionable overrides. def gfm_reference @@ -66,4 +70,9 @@ class Issue < ActiveRecord::Base def reset_events_cache Event.reset_event_cache_for(self) end + + # To allow polymorphism with MergeRequest. + def source_project + project + end end diff --git a/app/models/key.rb b/app/models/key.rb index d59993b190..016eee8699 100644 --- a/app/models/key.rb +++ b/app/models/key.rb @@ -15,11 +15,11 @@ require 'digest/md5' class Key < ActiveRecord::Base - include Gitlab::Popen + include Sortable belongs_to :user - before_validation :strip_white_space, :generate_fingerpint + before_validation :strip_white_space, :generate_fingerprint validates :title, presence: true, length: { within: 0..255 } validates :key, presence: true, length: { within: 0..5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ }, uniqueness: true @@ -29,7 +29,9 @@ class Key < ActiveRecord::Base after_create :add_to_shell after_create :notify_user + after_create :post_create_hook after_destroy :remove_from_shell + after_destroy :post_destroy_hook def strip_white_space self.key = key.strip unless key.blank? @@ -56,6 +58,10 @@ class Key < ActiveRecord::Base NotificationService.new.new_key(self) end + def post_create_hook + SystemHooksService.new.execute_hooks_for(self, :create) + end + def remove_from_shell GitlabShellWorker.perform_async( :remove_key, @@ -64,24 +70,17 @@ class Key < ActiveRecord::Base ) end + def post_destroy_hook + SystemHooksService.new.execute_hooks_for(self, :destroy) + end + private - def generate_fingerpint + def generate_fingerprint self.fingerprint = nil - return unless key.present? - cmd_status = 0 - cmd_output = '' - Tempfile.open('gitlab_key_file') do |file| - file.puts key - file.rewind - cmd_output, cmd_status = popen(%W(ssh-keygen -lf #{file.path}), '/tmp') - end + return unless self.key.present? - if cmd_status.zero? - cmd_output.gsub /([\d\h]{2}:)+[\d\h]{2}/ do |match| - self.fingerprint = match - end - end + self.fingerprint = Gitlab::KeyFingerprint.new(self.key).fingerprint end end diff --git a/app/models/label.rb b/app/models/label.rb index 233f477444..1f22ed23d4 100644 --- a/app/models/label.rb +++ b/app/models/label.rb @@ -1,6 +1,20 @@ +# == 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 +# + class Label < ActiveRecord::Base DEFAULT_COLOR = '#428BCA' + default_value_for :color, DEFAULT_COLOR + belongs_to :project has_many :label_links, dependent: :destroy has_many :issues, through: :label_links, source: :target, source_type: 'Issue' @@ -16,7 +30,7 @@ class Label < ActiveRecord::Base format: { with: /\A[^&\?,&]+\z/ }, uniqueness: { scope: :project_id } - scope :order_by_name, -> { reorder("labels.title ASC") } + default_scope { order(title: :asc) } alias_attribute :name, :title diff --git a/app/models/label_link.rb b/app/models/label_link.rb index 47bd6eaf35..b94c9c777a 100644 --- a/app/models/label_link.rb +++ b/app/models/label_link.rb @@ -1,3 +1,15 @@ +# == 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 +# + class LabelLink < ActiveRecord::Base belongs_to :target, polymorphic: true belongs_to :label diff --git a/app/models/member.rb b/app/models/member.rb new file mode 100644 index 0000000000..d151c7b239 --- /dev/null +++ b/app/models/member.rb @@ -0,0 +1,172 @@ +# == 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 not null +# notification_level :integer not null +# type :string(255) +# 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 Notifiable + include Gitlab::Access + + attr_accessor :raw_invite_token + + belongs_to :created_by, class_name: "User" + belongs_to :user + belongs_to :source, polymorphic: true + + validates :user, presence: true, unless: :invite? + validates :source, presence: true + validates :user_id, uniqueness: { scope: [:source_type, :source_id], + message: "already exists in source", + allow_nil: true } + validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true + validates :invite_email, presence: { if: :invite? }, + email: { strict_mode: true, allow_nil: true }, + uniqueness: { scope: [:source_type, :source_id], allow_nil: true } + + scope :invite, -> { where(user_id: nil) } + scope :non_invite, -> { where("user_id IS NOT NULL") } + scope :guests, -> { where(access_level: GUEST) } + scope :reporters, -> { where(access_level: REPORTER) } + scope :developers, -> { where(access_level: DEVELOPER) } + scope :masters, -> { where(access_level: MASTER) } + scope :owners, -> { where(access_level: OWNER) } + + before_validation :generate_invite_token, on: :create, if: -> (member) { member.invite_email.present? } + after_create :send_invite, if: :invite? + after_create :post_create_hook, unless: :invite? + after_update :post_update_hook, unless: :invite? + after_destroy :post_destroy_hook, unless: :invite? + + delegate :name, :username, :email, to: :user, prefix: true + + class << self + def find_by_invite_token(invite_token) + invite_token = Devise.token_generator.digest(self, :invite_token, invite_token) + find_by(invite_token: invite_token) + end + + # This method is used to find users that have been entered into the "Add members" field. + # These can be the User objects directly, their IDs, their emails, or new emails to be invited. + def user_for_id(user_id) + return user_id if user_id.is_a?(User) + + user = User.find_by(id: user_id) + user ||= User.find_by(email: user_id) + user ||= user_id + user + end + + def add_user(members, user_id, access_level, current_user = nil) + user = user_for_id(user_id) + + # `user` can be either a User object or an email to be invited + if user.is_a?(User) + member = members.find_or_initialize_by(user_id: user.id) + else + member = members.build + member.invite_email = user + end + + member.created_by ||= current_user + member.access_level = access_level + + member.save + end + end + + def invite? + self.invite_token.present? + end + + def accept_invite!(new_user) + return false unless invite? + + self.invite_token = nil + self.invite_accepted_at = Time.now.utc + + self.user = new_user + + saved = self.save + + after_accept_invite if saved + + saved + end + + def decline_invite! + return false unless invite? + + destroyed = self.destroy + + after_decline_invite if destroyed + + destroyed + end + + def generate_invite_token + raw, enc = Devise.token_generator.generate(self.class, :invite_token) + @raw_invite_token = raw + self.invite_token = enc + end + + def generate_invite_token! + generate_invite_token && save(validate: false) + end + + def resend_invite + return unless invite? + + generate_invite_token! unless @raw_invite_token + + send_invite + end + + private + + def send_invite + # override in subclass + end + + def post_create_hook + system_hook_service.execute_hooks_for(self, :create) + end + + def post_update_hook + # override in subclass + end + + def post_destroy_hook + system_hook_service.execute_hooks_for(self, :destroy) + end + + def after_accept_invite + post_create_hook + end + + def after_decline_invite + # override in subclass + end + + def system_hook_service + SystemHooksService.new + end + + def notification_service + NotificationService.new + end +end diff --git a/app/models/members/group_member.rb b/app/models/members/group_member.rb new file mode 100644 index 0000000000..84c91372b3 --- /dev/null +++ b/app/models/members/group_member.rb @@ -0,0 +1,75 @@ +# == 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 not null +# notification_level :integer not null +# type :string(255) +# created_at :datetime +# updated_at :datetime +# + +class GroupMember < Member + SOURCE_TYPE = 'Namespace' + + belongs_to :group, class_name: 'Group', foreign_key: 'source_id' + + # Make sure group member points only to group as it source + default_value_for :source_type, SOURCE_TYPE + default_value_for :notification_level, Notification::N_GLOBAL + validates_format_of :source_type, with: /\ANamespace\z/ + default_scope { where(source_type: SOURCE_TYPE) } + + scope :with_group, ->(group) { where(source_id: group.id) } + scope :with_user, ->(user) { where(user_id: user.id) } + + def self.access_level_roles + Gitlab::Access.options_with_owner + end + + def group + source + end + + def access_field + access_level + end + + private + + def send_invite + notification_service.invite_group_member(self, @raw_invite_token) + + super + end + + def post_create_hook + notification_service.new_group_member(self) + + super + end + + def post_update_hook + if access_level_changed? + notification_service.update_group_member(self) + end + + super + end + + def after_accept_invite + notification_service.accept_group_invite(self) + + super + end + + def after_decline_invite + notification_service.decline_group_invite(self) + + super + end +end diff --git a/app/models/members/project_member.rb b/app/models/members/project_member.rb new file mode 100644 index 0000000000..0a3b4d2182 --- /dev/null +++ b/app/models/members/project_member.rb @@ -0,0 +1,165 @@ +# == 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 not null +# notification_level :integer not null +# type :string(255) +# created_at :datetime +# updated_at :datetime +# + +class ProjectMember < Member + SOURCE_TYPE = 'Project' + + include Gitlab::ShellAdapter + + 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 + default_value_for :notification_level, Notification::N_GLOBAL + validates_format_of :source_type, with: /\AProject\z/ + default_scope { where(source_type: SOURCE_TYPE) } + + scope :in_project, ->(project) { where(source_id: project.id) } + scope :in_projects, ->(projects) { where(source_id: projects.pluck(:id)) } + scope :with_user, ->(user) { where(user_id: user.id) } + + class << self + + # Add users to project teams with passed access option + # + # access can be an integer representing a access code + # or symbol like :master representing role + # + # Ex. + # add_users_into_projects( + # project_ids, + # user_ids, + # ProjectMember::MASTER + # ) + # + # add_users_into_projects( + # project_ids, + # user_ids, + # :master + # ) + # + def add_users_into_projects(project_ids, user_ids, access, current_user = nil) + access_level = if roles_hash.has_key?(access) + roles_hash[access] + elsif roles_hash.values.include?(access.to_i) + access + else + raise "Non valid access" + end + + users = user_ids.map { |user_id| Member.user_for_id(user_id) } + + ProjectMember.transaction do + project_ids.each do |project_id| + project = Project.find(project_id) + + users.each do |user| + Member.add_user(project.project_members, user, access_level, current_user) + end + end + end + + true + rescue + false + end + + def truncate_teams(project_ids) + ProjectMember.transaction do + members = ProjectMember.where(source_id: project_ids) + + members.each do |member| + member.destroy + end + end + + true + rescue + false + end + + def truncate_team(project) + truncate_teams [project.id] + end + + def roles_hash + Gitlab::Access.sym_options + end + + def access_roles + Gitlab::Access.options + end + end + + def access_field + access_level + end + + def project + source + end + + def owner? + project.owner == user + end + + private + + def send_invite + notification_service.invite_project_member(self, @raw_invite_token) + + super + end + + def post_create_hook + unless owner? + event_service.join_project(self.project, self.user) + notification_service.new_project_member(self) + end + + super + end + + def post_update_hook + if access_level_changed? + notification_service.update_project_member(self) + end + + super + end + + def post_destroy_hook + event_service.leave_project(self.project, self.user) + + super + end + + def after_accept_invite + notification_service.accept_project_invite(self) + + super + end + + def after_decline_invite + notification_service.decline_project_invite(self) + + super + end + + def event_service + EventCreateService.new + end +end diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb index b5705ef151..9c9e276250 100644 --- a/app/models/merge_request.rb +++ b/app/models/merge_request.rb @@ -17,6 +17,8 @@ # target_project_id :integer not null # iid :integer # description :text +# position :integer default(0) +# locked_at :datetime # require Rails.root.join("app/models/commit") @@ -24,7 +26,9 @@ require Rails.root.join("lib/static_model") class MergeRequest < ActiveRecord::Base include Issuable + include Taskable include InternalId + include Sortable belongs_to :target_project, foreign_key: :target_project_id, class_name: "Project" belongs_to :source_project, foreign_key: :source_project_id, class_name: "Project" @@ -68,6 +72,16 @@ class MergeRequest < ActiveRecord::Base transition locked: :reopened end + after_transition any => :locked do |merge_request, transition| + merge_request.locked_at = Time.now + merge_request.save + end + + after_transition locked: (any - :locked) do |merge_request, transition| + merge_request.locked_at = nil + merge_request.save + end + state :opened state :reopened state :closed @@ -81,16 +95,25 @@ class MergeRequest < ActiveRecord::Base end event :mark_as_mergeable do - transition unchecked: :can_be_merged + transition [:unchecked, :cannot_be_merged] => :can_be_merged end event :mark_as_unmergeable do - transition unchecked: :cannot_be_merged + transition [:unchecked, :can_be_merged] => :cannot_be_merged end state :unchecked state :can_be_merged state :cannot_be_merged + + around_transition do |merge_request, transition, block| + merge_request.record_timestamps = false + begin + block.call + ensure + merge_request.record_timestamps = true + end + end end validates :source_project, presence: true, unless: :allow_broken @@ -101,7 +124,6 @@ class MergeRequest < ActiveRecord::Base validate :validate_fork scope :of_group, ->(group) { where("source_project_id in (:group_project_ids) OR target_project_id in (:group_project_ids)", group_project_ids: group.project_ids) } - scope :of_user_team, ->(team) { where("(source_project_id in (:team_project_ids) OR target_project_id in (:team_project_ids) AND assignee_id in (:team_member_ids))", team_project_ids: team.project_ids, team_member_ids: team.member_ids) } scope :merged, -> { with_state(:merged) } scope :by_branch, ->(branch_name) { where("(source_branch LIKE :branch) OR (target_branch LIKE :branch)", branch: branch_name) } scope :cared, ->(user) { where('assignee_id = :user OR author_id = :user', user: user.id) } @@ -121,9 +143,11 @@ class MergeRequest < ActiveRecord::Base if opened? || reopened? similar_mrs = self.target_project.merge_requests.where(source_branch: source_branch, target_branch: target_branch, source_project_id: source_project.id).opened similar_mrs = similar_mrs.where('id not in (?)', self.id) if self.id - if similar_mrs.any? - errors.add :base, "Cannot Create: This merge request already exists: #{similar_mrs.pluck(:title)}" + errors.add :validate_branches, + "Cannot Create: This merge request already exists: #{ + similar_mrs.pluck(:title) + }" end end end @@ -139,7 +163,8 @@ class MergeRequest < ActiveRecord::Base if source_project.forked_from?(target_project) true else - errors.add :base, "Source project is not a fork of target project" + errors.add :validate_fork, + 'Source project is not a fork of target project' end end end @@ -174,7 +199,9 @@ class MergeRequest < ActiveRecord::Base end def automerge!(current_user, commit_message = nil) - MergeRequests::AutoMergeService.new.execute(self, current_user, commit_message) + MergeRequests::AutoMergeService. + new(target_project, current_user). + execute(self, commit_message) end def open? @@ -207,6 +234,20 @@ class MergeRequest < ActiveRecord::Base Gitlab::Satellite::MergeAction.new(current_user, self).format_patch end + def hook_attrs + attrs = { + source: source_project.hook_attrs, + target: target_project.hook_attrs, + last_commit: nil + } + + unless last_commit.nil? + attrs.merge!(last_commit: last_commit.hook_attrs(source_project)) + end + + attributes.merge!(attrs) + end + def for_fork? target_project != source_project end @@ -216,10 +257,11 @@ class MergeRequest < ActiveRecord::Base end # Return the set of issues that will be closed if this merge request is accepted. - def closes_issues + def closes_issues(current_user = self.author) if target_branch == project.default_branch - issues = commits.flat_map { |c| c.closes_issues(project) } - issues += Gitlab::ClosingIssueExtractor.closed_by_message_in_project(description, project) + issues = commits.flat_map { |c| c.closes_issues(project, current_user) } + issues.push(*Gitlab::ClosingIssueExtractor.new(project, current_user). + closed_by_message(description)) issues.uniq.sort_by(&:id) else [] @@ -299,7 +341,7 @@ class MergeRequest < ActiveRecord::Base end # Return array of possible target branches - # dependes on target project of MR + # depends on target project of MR def target_branches if target_project.nil? [] @@ -309,7 +351,7 @@ class MergeRequest < ActiveRecord::Base end # Return array of possible source branches - # dependes on source project of MR + # depends on source project of MR def source_branches if source_project.nil? [] @@ -317,4 +359,10 @@ class MergeRequest < ActiveRecord::Base source_project.repository.branch_names end end + + def locked_long_ago? + return false unless locked? + + locked_at.nil? || locked_at < (Time.now - 1.day) + end end diff --git a/app/models/merge_request_diff.rb b/app/models/merge_request_diff.rb index 248fa18353..acac1ca4cf 100644 --- a/app/models/merge_request_diff.rb +++ b/app/models/merge_request_diff.rb @@ -3,7 +3,7 @@ # Table name: merge_request_diffs # # id :integer not null, primary key -# state :string(255) default("collected"), not null +# state :string(255) # st_commits :text # st_diffs :text # merge_request_id :integer not null @@ -14,6 +14,8 @@ require Rails.root.join("app/models/commit") class MergeRequestDiff < ActiveRecord::Base + include Sortable + # Prevent store of diff # if commits amount more then 200 COMMITS_SAFE_SIZE = 200 @@ -55,7 +57,7 @@ class MergeRequestDiff < ActiveRecord::Base end def last_commit_short_sha - @last_commit_short_sha ||= last_commit.sha[0..10] + @last_commit_short_sha ||= last_commit.short_id end private diff --git a/app/models/milestone.rb b/app/models/milestone.rb index 8fd3e56d2e..9bbb2bafb9 100644 --- a/app/models/milestone.rb +++ b/app/models/milestone.rb @@ -15,6 +15,7 @@ class Milestone < ActiveRecord::Base include InternalId + include Sortable belongs_to :project has_many :issues diff --git a/app/models/namespace.rb b/app/models/namespace.rb index b19b72906e..e1de114375 100644 --- a/app/models/namespace.rb +++ b/app/models/namespace.rb @@ -14,21 +14,27 @@ # class Namespace < ActiveRecord::Base + include Sortable include Gitlab::ShellAdapter has_many :projects, dependent: :destroy belongs_to :owner, class_name: "User" validates :owner, presence: true, unless: ->(n) { n.type == "Group" } - validates :name, presence: true, uniqueness: true, - length: { within: 0..255 }, - format: { with: Gitlab::Regex.name_regex, - message: Gitlab::Regex.name_regex_message } + validates :name, + presence: true, uniqueness: true, + length: { within: 0..255 }, + format: { with: Gitlab::Regex.namespace_name_regex, + message: Gitlab::Regex.namespace_name_regex_message } + validates :description, length: { within: 0..255 } - validates :path, uniqueness: { case_sensitive: false }, presence: true, length: { within: 1..255 }, - exclusion: { in: Gitlab::Blacklist.path }, - format: { with: Gitlab::Regex.path_regex, - message: Gitlab::Regex.path_regex_message } + validates :path, + uniqueness: { case_sensitive: false }, + presence: true, + length: { within: 1..255 }, + exclusion: { in: Gitlab::Blacklist.path }, + format: { with: Gitlab::Regex.namespace_regex, + message: Gitlab::Regex.namespace_regex_message } delegate :name, to: :owner, allow_nil: true, prefix: true @@ -38,12 +44,37 @@ class Namespace < ActiveRecord::Base scope :root, -> { where('type IS NULL') } - def self.search query - where("name LIKE :query OR path LIKE :query", query: "%#{query}%") - end + class << self + def by_path(path) + where('lower(path) = :value', value: path.downcase).first + end - def self.global_id - 'GLN' + # Case insensetive search for namespace by path or name + def find_by_path_or_name(path) + find_by("lower(path) = :path OR lower(name) = :path", path: path.downcase) + end + + def search(query) + where("name LIKE :query OR path LIKE :query", query: "%#{query}%") + end + + def clean_path(path) + path = path.dup + path.gsub!(/@.*\z/, "") + path.gsub!(/\.git\z/, "") + path.gsub!(/\A-+/, "") + path.gsub!(/\.+\z/, "") + path.gsub!(/[^a-zA-Z0-9_\-\.]/, "") + + counter = 0 + base = path + while Namespace.by_path(path).present? + counter += 1 + path = "#{base}#{counter}" + end + + path + end end def to_param @@ -90,4 +121,8 @@ class Namespace < ActiveRecord::Base def kind type == 'Group' ? 'group' : 'user' end + + def find_fork_of(project) + projects.joins(:forked_project_link).where('forked_project_links.forked_from_project_id = ?', project.id).first + end end diff --git a/app/models/network/graph.rb b/app/models/network/graph.rb index 424819f350..f4e9012537 100644 --- a/app/models/network/graph.rb +++ b/app/models/network/graph.rb @@ -6,7 +6,7 @@ module Network @max_count ||= 650 end - def initialize project, ref, commit, filter_ref + def initialize(project, ref, commit, filter_ref) @project = project @ref = ref @commit = commit @@ -84,7 +84,7 @@ module Network skip += self.class.max_count end else - # Cant't find the target commit in the repo. + # Can't find the target commit in the repo. offset = 0 end end @@ -178,12 +178,6 @@ module Network space = find_free_space(time_range, 2, space_base) leaves.each do |l| l.spaces << space - # Also add space to parent - l.parents(@map).each do |parent| - if 0 < parent.space && parent.space < space - parent.spaces << space - end - end end # and mark it as reserved @@ -232,7 +226,7 @@ module Network reserved = [] for day in time_range - reserved += @reserved[day] + reserved.push(*@reserved[day]) end reserved.uniq! diff --git a/app/models/note.rb b/app/models/note.rb index 7ff6444cc9..2cf3fac2de 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -22,6 +22,7 @@ require 'file_size_validator' class Note < ActiveRecord::Base include Mentionable + include Gitlab::CurrentSettings default_value_for :system, false @@ -36,7 +37,8 @@ class Note < ActiveRecord::Base validates :note, :project, presence: true validates :line_code, format: { with: /\A[a-z0-9]+_\d+_\d+\Z/ }, allow_blank: true - validates :attachment, file_size: { maximum: 10.megabytes.to_i } + # 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' } @@ -47,9 +49,10 @@ class Note < ActiveRecord::Base 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]) } - scope :fresh, ->{ order("created_at ASC, id ASC") } + scope :fresh, ->{ order(created_at: :asc, id: :asc) } scope :inc_author_project, ->{ includes(:project, :author) } scope :inc_author, ->{ includes(:author) } @@ -59,7 +62,7 @@ class Note < ActiveRecord::Base class << self def create_status_change_note(noteable, project, author, status, source) - body = "_Status changed to #{status}#{' by ' + source.gfm_reference if source}_" + body = "Status changed to #{status}#{' by ' + source.gfm_reference if source}" create( noteable: noteable, @@ -70,13 +73,17 @@ class Note < ActiveRecord::Base ) end - # +noteable+ was referenced from +mentioner+, by including GFM in either +mentioner+'s description or an associated Note. - # Create a system Note associated with +noteable+ with a GFM back-reference to +mentioner+. + # +noteable+ was referenced from +mentioner+, by including GFM in either + # +mentioner+'s description or an associated Note. + # Create a system Note associated with +noteable+ with a GFM back-reference + # to +mentioner+. def create_cross_reference_note(noteable, mentioner, author, project) + gfm_reference = mentioner_gfm_ref(noteable, mentioner, project) + note_options = { project: project, author: author, - note: "_mentioned in #{mentioner.gfm_reference}_", + note: cross_reference_note_content(gfm_reference), system: true } @@ -86,14 +93,14 @@ class Note < ActiveRecord::Base note_options.merge!(noteable: noteable) end - create(note_options) + create(note_options) unless cross_reference_disallowed?(noteable, mentioner) end def create_milestone_change_note(noteable, project, author, milestone) body = if milestone.nil? - '_Milestone removed_' + 'Milestone removed' else - "_Milestone changed to #{milestone.title}_" + "Milestone changed to #{milestone.title}" end create( @@ -106,7 +113,7 @@ class Note < ActiveRecord::Base end def create_assignee_change_note(noteable, project, author, assignee) - body = assignee.nil? ? '_Assignee removed_' : "_Reassigned to @#{assignee.username}_" + body = assignee.nil? ? 'Assignee removed' : "Reassigned to @#{assignee.username}" create({ noteable: noteable, @@ -117,6 +124,82 @@ class Note < ActiveRecord::Base }) end + def create_labels_change_note(noteable, project, author, added_labels, removed_labels) + labels_count = added_labels.count + removed_labels.count + added_labels = added_labels.map{ |label| "~#{label.id}" }.join(' ') + removed_labels = removed_labels.map{ |label| "~#{label.id}" }.join(' ') + message = '' + + if added_labels.present? + message << "added #{added_labels}" + end + + if added_labels.present? && removed_labels.present? + message << ' and ' + end + + if removed_labels.present? + message << "removed #{removed_labels}" + end + + message << ' ' << 'label'.pluralize(labels_count) + body = "#{message.capitalize}" + + create( + noteable: noteable, + project: project, + author: author, + note: body, + system: true + ) + end + + def create_new_commits_note(merge_request, project, author, new_commits, existing_commits = [], oldrev = nil) + total_count = new_commits.length + existing_commits.length + commits_text = ActionController::Base.helpers.pluralize(total_count, 'commit') + body = "Added #{commits_text}:\n\n" + + if existing_commits.length > 0 + commit_ids = + if existing_commits.length == 1 + existing_commits.first.short_id + else + if oldrev + "#{Commit.truncate_sha(oldrev)}...#{existing_commits.last.short_id}" + else + "#{existing_commits.first.short_id}..#{existing_commits.last.short_id}" + end + end + + commits_text = ActionController::Base.helpers.pluralize(existing_commits.length, 'commit') + + branch = + if merge_request.for_fork? + "#{merge_request.target_project_namespace}:#{merge_request.target_branch}" + else + merge_request.target_branch + end + + message = "* #{commit_ids} - #{commits_text} from branch `#{branch}`" + body << message + body << "\n" + end + + new_commits.each do |commit| + message = "* #{commit.short_id} - #{commit.title}" + body << message + body << "\n" + end + + create( + noteable: merge_request, + project: project, + author: author, + note: body, + system: true + ) + end + def discussions_from_notes(notes) discussion_ids = [] discussions = [] @@ -142,20 +225,120 @@ class Note < ActiveRecord::Base [:discussion, type.try(:underscore), id, line_code].join("-").to_sym end + # Determine if cross reference note should be created. + # eg. mentioning a commit in MR comments which exists inside a MR + # should not create "mentioned in" note. + def cross_reference_disallowed?(noteable, mentioner) + if mentioner.kind_of?(MergeRequest) + mentioner.commits.map(&:id).include? noteable.id + end + end + # Determine whether or not a cross-reference note already exists. def cross_reference_exists?(noteable, mentioner) - where(noteable_id: noteable.id, system: true, note: "_mentioned in #{mentioner.gfm_reference}_").any? + gfm_reference = mentioner_gfm_ref(noteable, mentioner) + notes = if noteable.is_a?(Commit) + where(commit_id: noteable.id) + else + where(noteable_id: noteable.id) + end + + notes.where('note like ?', cross_reference_note_pattern(gfm_reference)). + system.any? end + + def search(query) + where("note like :query", query: "%#{query}%") + end + + def cross_reference_note_prefix + 'mentioned in ' + end + + private + + def cross_reference_note_content(gfm_reference) + cross_reference_note_prefix + "#{gfm_reference}" + end + + def cross_reference_note_pattern(gfm_reference) + # Older cross reference notes contained underscores for emphasis + "%" + cross_reference_note_content(gfm_reference) + "%" + end + + # Prepend the mentioner's namespaced project path to the GFM reference for + # cross-project references. For same-project references, return the + # unmodified GFM reference. + def mentioner_gfm_ref(noteable, mentioner, project = nil) + if mentioner.is_a?(Commit) + if project.nil? + return mentioner.gfm_reference.sub('commit ', 'commit %') + else + mentioning_project = project + end + else + mentioning_project = mentioner.project + end + + noteable_project_id = noteable_project_id(noteable, mentioning_project) + + full_gfm_reference(mentioning_project, noteable_project_id, mentioner) + end + + # Return the ID of the project that +noteable+ belongs to, or nil if + # +noteable+ is a commit and is not part of the project that owns + # +mentioner+. + def noteable_project_id(noteable, mentioning_project) + if noteable.is_a?(Commit) + if mentioning_project.repository.commit(noteable.id) + # The noteable commit belongs to the mentioner's project + mentioning_project.id + else + nil + end + else + noteable.project.id + end + end + + # Return the +mentioner+ GFM reference. If the mentioner and noteable + # projects are not the same, add the mentioning project's path to the + # returned value. + def full_gfm_reference(mentioning_project, noteable_project_id, mentioner) + if mentioning_project.id == noteable_project_id + mentioner.gfm_reference + else + if mentioner.is_a?(Commit) + mentioner.gfm_reference.sub( + /(commit )/, + "\\1#{mentioning_project.path_with_namespace}@" + ) + else + mentioner.gfm_reference.sub( + /(issue |merge request )/, + "\\1#{mentioning_project.path_with_namespace}" + ) + end + end + end + end + + def max_attachment_size + current_application_settings.max_attachment_size.megabytes.to_i end def commit_author @commit_author ||= - project.users.find_by(email: noteable.author_email) || - project.users.find_by(name: noteable.author_name) + project.team.users.find_by(email: noteable.author_email) || + project.team.users.find_by(name: noteable.author_name) rescue nil end + def cross_reference? + note.start_with?(self.class.cross_reference_note_prefix) + end + def find_diff return nil unless noteable && noteable.diffs.present? @@ -164,10 +347,14 @@ class Note < ActiveRecord::Base end end + def hook_attrs + attributes + end + def set_diff # First lets find notes with same diff # before iterating over all mr diffs - diff = Note.where(noteable_id: self.noteable_id, noteable_type: self.noteable_type, line_code: self.line_code).last.try(:diff) + diff = diff_for_line_code unless for_merge_request? diff ||= find_diff self.st_diff = diff.to_hash if diff @@ -177,18 +364,24 @@ class Note < ActiveRecord::Base @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 such line of code exists in merge request diff # If exists - its active discussion # If not - its outdated diff def active? return true unless self.diff + return false unless noteable noteable.diffs.each do |mr_diff| next unless mr_diff.new_path == self.diff.new_path - Gitlab::DiffParser.new(mr_diff.diff.lines.to_a, mr_diff.new_path). - each do |full_line, type, line_code, line_new, line_old| - if full_line == diff_line + lines = Gitlab::Diff::Parser.new.parse(mr_diff.diff.lines.to_a) + + lines.each do |line| + if line.text == diff_line return true end end @@ -202,34 +395,86 @@ class Note < ActiveRecord::Base end def diff_file_index - line_code.split('_')[0] + 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 + line_code.split('_')[1].to_i if line_code end def diff_new_line - line_code.split('_')[2].to_i + line_code.split('_')[2].to_i if line_code + 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 - Gitlab::DiffParser.new(diff.diff.lines.to_a, diff.new_path) - .each do |full_line, type, line_code, line_new, line_old| - @diff_line = full_line if line_code == self.line_code + 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 = [] + + 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 + + def diff_lines + @diff_lines ||= Gitlab::Diff::Parser.new.parse(diff.diff.lines.to_a) + end + def discussion_id @discussion_id ||= Note.build_discussion_id(noteable_type, noteable_id || commit_id, line_code) end @@ -268,6 +513,10 @@ class Note < ActiveRecord::Base for_merge_request? && for_diff_line? end + def for_project_snippet? + noteable_type == "Snippet" + end + # override to return commits, which are not active record def noteable if for_commit? @@ -291,6 +540,26 @@ class Note < ActiveRecord::Base ) end + def superceded?(notes) + return false unless vote? + + notes.each do |note| + next if note == self + + if note.vote? && + self[:author_id] == note[:author_id] && + self[:created_at] <= note[:created_at] + return true + end + end + + false + end + + def vote? + upvote? || downvote? + end + def votable? for_issue? || (for_merge_request? && !for_diff_line?) end @@ -312,7 +581,7 @@ class Note < ActiveRecord::Base end # FIXME: Hack for polymorphic associations with STI - # For more information wisit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations + # For more information visit http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html#label-Polymorphic+Associations def noteable_type=(sType) super(sType.to_s.classify.constantize.base_class.to_s) end @@ -333,4 +602,8 @@ class Note < ActiveRecord::Base def set_references notice_added_references(project, author) end + + def editable? + !read_attribute(:system) + end end diff --git a/app/models/notification.rb b/app/models/notification.rb index b0f8ed6a4e..1395274173 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -6,12 +6,13 @@ class Notification N_PARTICIPATING = 1 N_WATCH = 2 N_GLOBAL = 3 + N_MENTION = 4 attr_accessor :target class << self def notification_levels - [N_DISABLED, N_PARTICIPATING, N_WATCH] + [N_DISABLED, N_PARTICIPATING, N_WATCH, N_MENTION] end def options_with_labels @@ -19,12 +20,13 @@ class Notification disabled: N_DISABLED, participating: N_PARTICIPATING, watch: N_WATCH, + mention: N_MENTION, global: N_GLOBAL } end def project_notification_levels - [N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL] + [N_DISABLED, N_PARTICIPATING, N_WATCH, N_GLOBAL, N_MENTION] end end @@ -48,6 +50,10 @@ class Notification target.notification_level == N_GLOBAL end + def mention? + target.notification_level == N_MENTION + end + def level target.notification_level end diff --git a/app/models/personal_snippet.rb b/app/models/personal_snippet.rb index a3c0d201ee..9cee3b70cb 100644 --- a/app/models/personal_snippet.rb +++ b/app/models/personal_snippet.rb @@ -2,17 +2,17 @@ # # 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) -# expires_at :datetime -# private :boolean default(TRUE), not null -# type :string(255) +# 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) +# expires_at :datetime +# type :string(255) +# visibility_level :integer default(0), not null # class PersonalSnippet < Snippet diff --git a/app/models/project.rb b/app/models/project.rb index 7f6aa6d424..64ee2c2212 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -22,13 +22,23 @@ # visibility_level :integer default(0), not null # archived :boolean default(FALSE), not null # import_status :string(255) -# star_count :integer +# repository_size :float default(0.0) +# star_count :integer default(0), not null +# import_type :string(255) +# import_source :string(255) +# avatar :string(255) # +require 'carrierwave/orm/activerecord' +require 'file_size_validator' + class Project < ActiveRecord::Base + include Sortable include Gitlab::ShellAdapter include Gitlab::VisibilityLevel include Gitlab::ConfigHelper + include Rails.application.routes.url_helpers + extend Gitlab::ConfigHelper extend Enumerize @@ -40,14 +50,20 @@ class Project < ActiveRecord::Base default_value_for :wall_enabled, false default_value_for :snippets_enabled, gitlab_config_features.snippets + # set last_activity_at to the same as created_at + after_create :set_last_activity_at + def set_last_activity_at + update_column(:last_activity_at, self.created_at) + end + ActsAsTaggableOn.strict_case_match = true acts_as_taggable_on :tags attr_accessor :new_default_branch # Relations - belongs_to :creator, foreign_key: "creator_id", class_name: "User" - belongs_to :group, -> { where(type: Group) }, foreign_key: "namespace_id" + belongs_to :creator, foreign_key: 'creator_id', class_name: 'User' + belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id' belongs_to :namespace has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id' @@ -57,100 +73,121 @@ class Project < ActiveRecord::Base has_one :gitlab_ci_service, dependent: :destroy has_one :campfire_service, dependent: :destroy has_one :emails_on_push_service, dependent: :destroy + has_one :irker_service, dependent: :destroy has_one :pivotaltracker_service, dependent: :destroy has_one :hipchat_service, dependent: :destroy has_one :flowdock_service, dependent: :destroy has_one :assembla_service, dependent: :destroy + has_one :asana_service, dependent: :destroy has_one :gemnasium_service, dependent: :destroy has_one :slack_service, dependent: :destroy + has_one :buildkite_service, dependent: :destroy + has_one :bamboo_service, dependent: :destroy + has_one :teamcity_service, dependent: :destroy + has_one :pushover_service, dependent: :destroy + has_one :jira_service, dependent: :destroy + has_one :redmine_service, dependent: :destroy + has_one :custom_issue_tracker_service, dependent: :destroy + has_one :gitlab_issue_tracker_service, dependent: :destroy + has_one :external_wiki_service, dependent: :destroy + has_one :forked_project_link, dependent: :destroy, foreign_key: "forked_to_project_id" + has_one :forked_from_project, through: :forked_project_link # Merge Requests for target project should be removed with it - has_many :merge_requests, dependent: :destroy, foreign_key: "target_project_id" + has_many :merge_requests, dependent: :destroy, foreign_key: 'target_project_id' # Merge requests from source project should be kept when source project was removed - has_many :fork_merge_requests, foreign_key: "source_project_id", class_name: MergeRequest - has_many :issues, -> { order "state DESC, created_at DESC" }, dependent: :destroy + has_many :fork_merge_requests, foreign_key: 'source_project_id', class_name: MergeRequest + has_many :issues, dependent: :destroy has_many :labels, dependent: :destroy has_many :services, dependent: :destroy has_many :events, dependent: :destroy has_many :milestones, dependent: :destroy has_many :notes, dependent: :destroy - has_many :snippets, dependent: :destroy, class_name: "ProjectSnippet" - has_many :hooks, dependent: :destroy, class_name: "ProjectHook" + has_many :snippets, dependent: :destroy, class_name: 'ProjectSnippet' + has_many :hooks, dependent: :destroy, class_name: 'ProjectHook' has_many :protected_branches, dependent: :destroy - has_many :users_projects, dependent: :destroy - has_many :users, through: :users_projects + has_many :project_members, dependent: :destroy, as: :source, class_name: 'ProjectMember' + has_many :users, through: :project_members has_many :deploy_keys_projects, dependent: :destroy has_many :deploy_keys, through: :deploy_keys_projects has_many :users_star_projects, dependent: :destroy has_many :starrers, through: :users_star_projects, source: :user + has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" + delegate :name, to: :owner, allow_nil: true, prefix: true delegate :members, to: :team, prefix: true # Validations validates :creator, presence: true, on: :create validates :description, length: { maximum: 2000 }, allow_blank: true - validates :name, presence: true, length: { within: 0..255 }, - format: { with: Gitlab::Regex.project_name_regex, - message: Gitlab::Regex.project_regex_message } - validates :path, presence: true, length: { within: 0..255 }, - exclusion: { in: Gitlab::Blacklist.path }, - format: { with: Gitlab::Regex.path_regex, - message: Gitlab::Regex.path_regex_message } + validates :name, + presence: true, + length: { within: 0..255 }, + format: { with: Gitlab::Regex.project_name_regex, + message: Gitlab::Regex.project_name_regex_message } + validates :path, + presence: true, + length: { within: 0..255 }, + format: { with: Gitlab::Regex.project_path_regex, + message: Gitlab::Regex.project_path_regex_message } validates :issues_enabled, :merge_requests_enabled, :wiki_enabled, inclusion: { in: [true, false] } - validates :visibility_level, - exclusion: { in: gitlab_config.restricted_visibility_levels }, - if: -> { gitlab_config.restricted_visibility_levels.any? } validates :issues_tracker_id, length: { maximum: 255 }, allow_blank: true validates :namespace, presence: true validates_uniqueness_of :name, scope: :namespace_id validates_uniqueness_of :path, scope: :namespace_id validates :import_url, - format: { with: URI::regexp(%w(git http https)), message: "should be a valid url" }, + format: { with: /\A#{URI.regexp(%w(ssh git http https))}\z/, message: 'should be a valid url' }, if: :import? validates :star_count, numericality: { greater_than_or_equal_to: 0 } validate :check_limit, on: :create + validate :avatar_type, + if: ->(project) { project.avatar && project.avatar_changed? } + validates :avatar, file_size: { maximum: 200.kilobytes.to_i } + + mount_uploader :avatar, AvatarUploader # Scopes - 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_team, ->(team) { where("projects.id IN (:ids)", ids: team.projects.map(&:id)) } + 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) { where(namespace_id: namespace.id) } scope :in_group_namespace, -> { joins(:group) } - scope :sorted_by_activity, -> { reorder("projects.last_activity_at DESC") } scope :personal, ->(user) { where(namespace_id: user.namespace_id) } - scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) } + scope :joined, ->(user) { where('namespace_id != ?', user.namespace_id) } scope :public_only, -> { where(visibility_level: Project::PUBLIC) } scope :public_and_internal_only, -> { where(visibility_level: Project.public_and_internal_levels) } scope :non_archived, -> { where(archived: false) } - enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab - state_machine :import_status, initial: :none do event :import_start do - transition :none => :started + transition [:none, :finished] => :started end event :import_finish do - transition :started => :finished + transition started: :finished end event :import_fail do - transition :started => :failed + transition started: :failed end event :import_retry do - transition :failed => :started + transition failed: :started end state :started state :finished state :failed - after_transition any => :started, :do => :add_import_job + after_transition any => :started, do: :add_import_job + after_transition any => :finished, do: :clear_import_data end class << self @@ -164,30 +201,35 @@ class Project < ActiveRecord::Base def publicish(user) visibility_levels = [Project::PUBLIC] - visibility_levels += [Project::INTERNAL] if user + visibility_levels << Project::INTERNAL if user where(visibility_level: visibility_levels) end def with_push - includes(:events).where('events.action = ?', Event::PUSHED) + 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") + joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC') end - def search query - joins(:namespace).where("projects.archived = ?", false).where("projects.name LIKE :query OR projects.path LIKE :query OR namespaces.name LIKE :query OR projects.description LIKE :query", query: "%#{query}%") + def search(query) + joins(:namespace).where('projects.archived = ?', false). + where('LOWER(projects.name) LIKE :query OR + LOWER(projects.path) LIKE :query OR + LOWER(namespaces.name) LIKE :query OR + LOWER(projects.description) LIKE :query', + query: "%#{query.try(:downcase)}%") end - def search_by_title query - where("projects.archived = ?", false).where("LOWER(projects.name) LIKE :query", query: "%#{query.downcase}%") + def search_by_title(query) + where('projects.archived = ?', false).where('LOWER(projects.name) LIKE :query', query: "%#{query.downcase}%") end def find_with_namespace(id) - return nil unless id.include?("/") + return nil unless id.include?('/') - id = id.split("/") + id = id.split('/') namespace = Namespace.find_by(path: id.first) return nil unless namespace @@ -199,13 +241,10 @@ class Project < ActiveRecord::Base end def sort(method) - case method.to_s - when 'newest' then reorder('projects.created_at DESC') - when 'oldest' then reorder('projects.created_at ASC') - when 'recently_updated' then reorder('projects.updated_at DESC') - when 'last_updated' then reorder('projects.updated_at ASC') - when 'largest_repository' then reorder('projects.repository_size DESC') - else reorder("namespaces.path, projects.name ASC") + if method == 'repository_size_desc' + reorder(repository_size: :desc, id: :desc) + else + order_by(method) end end end @@ -226,6 +265,10 @@ class Project < ActiveRecord::Base RepositoryImportWorker.perform_in(2.seconds, id) end + def clear_import_data + self.import_data.destroy if self.import_data + end + def import? import_url.present? end @@ -255,19 +298,19 @@ class Project < ActiveRecord::Base end def to_param - namespace.path + "/" + path + path end def web_url - [gitlab_config.url, path_with_namespace].join("/") + [gitlab_config.url, path_with_namespace].join('/') end def web_url_without_protocol - web_url.split("://")[1] + web_url.split('://')[1] end def build_commit_note(commit) - notes.new(commit_id: commit.id, noteable_type: "Commit") + notes.new(commit_id: commit.id, noteable_type: 'Commit') end def last_activity @@ -283,33 +326,68 @@ class Project < ActiveRecord::Base end def issue_exists?(issue_id) - if used_default_issues_tracker? + if default_issues_tracker? self.issues.where(iid: issue_id).first.present? else true end end - def used_default_issues_tracker? - self.issues_tracker == Project.issues_tracker.default_value + def default_issue_tracker + gitlab_issue_tracker_service || create_gitlab_issue_tracker_service end - def can_have_issues_tracker_id? - self.issues_enabled && !self.used_default_issues_tracker? - end - - def build_missing_services - available_services_names.each do |service_name| - service = services.find { |service| service.to_param == service_name } - - # If service is available but missing in db - # we should create an instance. Ex `create_gitlab_ci_service` - service = self.send :"create_#{service_name}_service" if service.nil? + def issues_tracker + if external_issue_tracker + external_issue_tracker + else + default_issue_tracker end end - def available_services_names - %w(gitlab_ci campfire hipchat pivotaltracker flowdock assembla emails_on_push gemnasium slack) + def default_issues_tracker? + if external_issue_tracker + false + else + true + end + end + + def external_issues_trackers + services.select(&:issue_tracker?).reject(&:default?) + end + + def external_issue_tracker + @external_issues_tracker ||= external_issues_trackers.select(&:activated?).first + end + + def can_have_issues_tracker_id? + self.issues_enabled && !self.default_issues_tracker? + end + + def build_missing_services + services_templates = Service.where(template: true) + + Service.available_services_names.each do |service_name| + service = find_service(services, service_name) + + # If service is available but missing in db + if service.nil? + # We should check if template for the service exists + template = find_service(services_templates, service_name) + + if template.nil? + # If no template, we should create an instance. Ex `create_gitlab_ci_service` + service = self.send :"create_#{service_name}_service" + else + Service.create_from_template(self.id, template) + end + end + end + end + + def find_service(list, name) + list.find { |service| service.to_param == name } end def gitlab_ci? @@ -324,12 +402,33 @@ class Project < ActiveRecord::Base @ci_service ||= ci_services.select(&:activated?).first end + def avatar_type + unless self.avatar.image? + self.errors.add :avatar, 'only images allowed' + end + end + + def avatar_in_git + @avatar_file ||= 'logo.png' if repository.blob_at_branch('master', 'logo.png') + @avatar_file ||= 'logo.jpg' if repository.blob_at_branch('master', 'logo.jpg') + @avatar_file ||= 'logo.gif' if repository.blob_at_branch('master', 'logo.gif') + @avatar_file + end + + def avatar_url + if avatar.present? + [gitlab_config.url, avatar.url].join + elsif avatar_in_git + [gitlab_config.url, namespace_project_avatar_path(namespace, self)].join + end + end + # For compatibility with old code def code path end - def items_for entity + def items_for(entity) case entity when 'issue' then issues @@ -350,20 +449,20 @@ class Project < ActiveRecord::Base end end - def team_member_by_name_or_email(name = nil, email = nil) - user = users.where("name like ? or email like ?", name, email).first - users_projects.where(user: user) if user + def project_member_by_name_or_email(name = nil, email = nil) + user = users.where('name like ? or email like ?', name, email).first + project_members.where(user: user) if user end # Get Team Member record by user id - def team_member_by_id(user_id) - users_projects.find_by(user_id: user_id) + def project_member_by_id(user_id) + project_members.find_by(user_id: user_id) end def name_with_namespace @name_with_namespace ||= begin if namespace - namespace.human_name + " / " + name + namespace.human_name + ' / ' + name else name end @@ -384,41 +483,22 @@ class Project < ActiveRecord::Base end end - def execute_services(data) - services.each do |service| - - # Call service hook only if it is active - begin - service.execute(data) if service.active - rescue => e - logger.error(e) - end + def execute_services(data, hooks_scope = :push_hooks) + # Call only service hooks that are active for this scope + services.send(hooks_scope).each do |service| + service.async_execute(data) end end def update_merge_requests(oldrev, newrev, ref, user) - return true unless ref =~ /heads/ - branch_name = ref.gsub("refs/heads/", "") - c_ids = self.repository.commits_between(oldrev, newrev).map(&:id) - - # Close merge requests - mrs = self.merge_requests.opened.where(target_branch: branch_name).to_a - mrs = mrs.select(&:last_commit).select { |mr| c_ids.include?(mr.last_commit.id) } - mrs.each { |merge_request| MergeRequests::MergeService.new.execute(merge_request, user, nil) } - - # Update code for merge requests into project between project branches - mrs = self.merge_requests.opened.by_branch(branch_name).to_a - # Update code for merge requests between project and project fork - mrs += self.fork_merge_requests.opened.by_branch(branch_name).to_a - mrs.each { |merge_request| merge_request.reload_code; merge_request.mark_as_unchecked } - - true + MergeRequests::RefreshService.new(self, user). + execute(oldrev, newrev, ref) end def valid_repo? repository.exists? rescue - errors.add(:path, "Invalid repository path") + errors.add(:path, 'Invalid repository path') false end @@ -477,14 +557,18 @@ class Project < ActiveRecord::Base end def http_url_to_repo - [gitlab_config.url, "/", path_with_namespace, ".git"].join('') + [gitlab_config.url, '/', path_with_namespace, '.git'].join('') end # Check if current branch name is marked as protected in the system - def protected_branch? branch_name + def protected_branch?(branch_name) protected_branches_names.include?(branch_name) end + def developers_can_push_to_protected_branch?(branch_name) + protected_branches.any? { |pb| pb.name == branch_name && pb.developers_can_push } + end + def forked? !(forked_project_link.nil? || forked_project_link.forked_from_project.nil?) end @@ -521,11 +605,22 @@ class Project < ActiveRecord::Base end end + def hook_attrs + { + name: name, + ssh_url: ssh_url_to_repo, + http_url: http_url_to_repo, + namespace: namespace.name, + visibility_level: visibility_level + } + end + # Reset events cache related to this project # # Since we do cache @event we need to reset cache in special cases: # * when project was moved # * when project was renamed + # * when the project avatar changes # Events cache stored like events/23-20130109142513. # The cache key includes updated_at timestamp. # Thus it will automatically generate a new fragment @@ -537,7 +632,7 @@ class Project < ActiveRecord::Base end def project_member(user) - users_projects.where(user_id: user).first + project_members.where(user_id: user).first end def default_branch @@ -581,4 +676,29 @@ class Project < ActiveRecord::Base def find_label(name) labels.find_by(name: name) end + + def origin_merge_requests + merge_requests.where(source_project_id: self.id) + end + + def create_repository + if gitlab_shell.add_repository(path_with_namespace) + true + else + errors.add(:base, 'Failed to create repository') + false + end + end + + def repository_exists? + !!repository.exists? + end + + def create_wiki + ProjectWiki.new(self, self.owner).wiki + true + rescue ProjectWiki::CouldNotCreateWikiError => ex + errors.add(:base, 'Failed create wiki') + false + end end diff --git a/app/models/project_import_data.rb b/app/models/project_import_data.rb new file mode 100644 index 0000000000..6a8a8a56eb --- /dev/null +++ b/app/models/project_import_data.rb @@ -0,0 +1,19 @@ +# == Schema Information +# +# Table name: project_import_datas +# +# id :integer not null, primary key +# project_id :integer +# data :text +# + +require 'carrierwave/orm/activerecord' +require 'file_size_validator' + +class ProjectImportData < ActiveRecord::Base + belongs_to :project + + serialize :data, JSON + + validates :project, presence: true +end diff --git a/app/models/project_services/asana_service.rb b/app/models/project_services/asana_service.rb new file mode 100644 index 0000000000..e6e16058d4 --- /dev/null +++ b/app/models/project_services/asana_service.rb @@ -0,0 +1,127 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null +# +require 'asana' + +class AsanaService < Service + prop_accessor :api_key, :restrict_to_branch + validates :api_key, presence: true, if: :activated? + + def title + 'Asana' + end + + def description + 'Asana - Teamwork without email' + end + + def help + 'This service adds commit messages as comments to Asana tasks. +Once enabled, commit messages are checked for Asana task URLs +(for example, `https://app.asana.com/0/123456/987654`) or task IDs +starting with # (for example, `#987654`). Every task ID found will +get the commit comment added to it. + +You can also close a task with a message containing: `fix #123456`. + +You can find your Api Keys here: +http://developer.asana.com/documentation/#api_keys' + end + + def to_param + 'asana' + end + + def fields + [ + { + type: 'text', + name: 'api_key', + placeholder: 'User API token. User must have access to task, +all comments will be attributed to this user.' + }, + { + type: 'text', + name: 'restrict_to_branch', + placeholder: 'Comma-separated list of branches which will be +automatically inspected. Leave blank to include all branches.' + } + ] + end + + def supported_events + %w(push) + end + + def execute(data) + return unless supported_events.include?(data[:object_kind]) + + Asana.configure do |client| + client.api_key = api_key + end + + user = data[:user_name] + branch = Gitlab::Git.ref_name(data[:ref]) + + branch_restriction = restrict_to_branch.to_s + + # check the branch restriction is poplulated and branch is not included + if branch_restriction.length > 0 && branch_restriction.index(branch).nil? + return + end + + project_name = project.name_with_namespace + push_msg = user + ' pushed to branch ' + branch + ' of ' + project_name + + data[:commits].each do |commit| + check_commit(' ( ' + commit[:url] + ' ): ' + commit[:message], push_msg) + end + end + + def check_commit(message, push_msg) + task_list = [] + close_list = [] + + message.split("\n").each do |line| + # look for a task ID or a full Asana url + task_list.concat(line.scan(/#(\d+)/)) + task_list.concat(line.scan(/https:\/\/app\.asana\.com\/\d+\/\d+\/(\d+)/)) + # look for a word starting with 'fix' followed by a task ID + close_list.concat(line.scan(/(fix\w*)\W*#(\d+)/i)) + end + + # post commit to every taskid found + task_list.each do |taskid| + task = Asana::Task.find(taskid[0]) + + if task + task.create_story(text: push_msg + ' ' + message) + end + end + + # close all tasks that had 'fix(ed/es/ing) #:id' in them + close_list.each do |taskid| + task = Asana::Task.find(taskid.last) + + if task + task.modify(completed: true) + end + end + end +end diff --git a/app/models/project_services/assembla_service.rb b/app/models/project_services/assembla_service.rb index 9a8cbb32ac..fb7e0c0fb0 100644 --- a/app/models/project_services/assembla_service.rb +++ b/app/models/project_services/assembla_service.rb @@ -2,24 +2,26 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# token :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# project_url :string(255) -# subdomain :string(255) -# room :string(255) -# recipients :text -# api_key :string(255) +# 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 # class AssemblaService < Service include HTTParty + prop_accessor :token, :subdomain validates :token, presence: true, if: :activated? def title @@ -41,8 +43,14 @@ class AssemblaService < Service ] end - def execute(push) + def supported_events + %w(push) + end + + def execute(data) + return unless supported_events.include?(data[:object_kind]) + url = "https://atlas.assembla.com/spaces/#{subdomain}/github_tool?secret_key=#{token}" - AssemblaService.post(url, body: { payload: push }.to_json, headers: { 'Content-Type' => 'application/json' }) + AssemblaService.post(url, body: { payload: data }.to_json, headers: { 'Content-Type' => 'application/json' }) end end diff --git a/app/models/project_services/bamboo_service.rb b/app/models/project_services/bamboo_service.rb new file mode 100644 index 0000000000..d8aedbd2ab --- /dev/null +++ b/app/models/project_services/bamboo_service.rb @@ -0,0 +1,137 @@ +# == 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 +# + +class BambooService < CiService + include HTTParty + + prop_accessor :bamboo_url, :build_key, :username, :password + + validates :bamboo_url, + presence: true, + format: { with: /\A#{URI.regexp}\z/ }, + if: :activated? + validates :build_key, presence: true, if: :activated? + validates :username, + presence: true, + if: ->(service) { service.password? }, + if: :activated? + validates :password, + presence: true, + if: ->(service) { service.username? }, + if: :activated? + + attr_accessor :response + + after_save :compose_service_hook, if: :activated? + + def compose_service_hook + hook = service_hook || build_service_hook + hook.save + end + + def title + 'Atlassian Bamboo CI' + end + + def description + 'A continuous integration and build server' + end + + def help + 'You must set up automatic revision labeling and a repository trigger in Bamboo.' + end + + def to_param + 'bamboo' + end + + def fields + [ + { type: 'text', name: 'bamboo_url', + placeholder: 'Bamboo root URL like https://bamboo.example.com' }, + { type: 'text', name: 'build_key', + placeholder: 'Bamboo build plan key like KEY' }, + { type: 'text', name: 'username', + placeholder: 'A user with API access, if applicable' }, + { type: 'password', name: 'password' }, + ] + end + + def supported_events + %w(push) + end + + def build_info(sha) + url = URI.parse("#{bamboo_url}/rest/api/latest/result?label=#{sha}") + + if username.blank? && password.blank? + @response = HTTParty.get(parsed_url.to_s, verify: false) + else + get_url = "#{url}&os_authType=basic" + auth = { + username: username, + password: password, + } + @response = HTTParty.get(get_url, verify: false, basic_auth: auth) + end + end + + def build_page(sha, ref) + build_info(sha) if @response.nil? || !@response.code + + if @response.code != 200 || @response['results']['results']['size'] == '0' + # If actual build link can't be determined, send user to build summary page. + "#{bamboo_url}/browse/#{build_key}" + else + # If actual build link is available, go to build result page. + result_key = @response['results']['results']['result']['planResultKey']['key'] + "#{bamboo_url}/browse/#{result_key}" + end + end + + def commit_status(sha, ref) + build_info(sha) if @response.nil? || !@response.code + return :error unless @response.code == 200 || @response.code == 404 + + status = if @response.code == 404 || @response['results']['results']['size'] == '0' + 'Pending' + else + @response['results']['results']['result']['buildState'] + end + + if status.include?('Success') + 'success' + elsif status.include?('Failed') + 'failed' + elsif status.include?('Pending') + 'pending' + else + :error + end + end + + def execute(data) + return unless supported_events.include?(data[:object_kind]) + + # Bamboo requires a GET and does not take any data. + self.class.get("#{bamboo_url}/updateAndBuild.action?buildKey=#{build_key}", + verify: false) + end +end diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb new file mode 100644 index 0000000000..a714bc8224 --- /dev/null +++ b/app/models/project_services/buildkite_service.rb @@ -0,0 +1,135 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# template :boolean default(FALSE) +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) +# note_events :boolean default(TRUE), not null +# + +require "addressable/uri" + +class BuildkiteService < CiService + ENDPOINT = "https://buildkite.com" + + prop_accessor :project_url, :token + + validates :project_url, presence: true, if: :activated? + validates :token, presence: true, if: :activated? + + after_save :compose_service_hook, if: :activated? + + def webhook_url + "#{buildkite_endpoint('webhook')}/deliver/#{webhook_token}" + end + + def compose_service_hook + hook = service_hook || build_service_hook + hook.url = webhook_url + hook.save + end + + def supported_events + %w(push) + end + + def execute(data) + return unless supported_events.include?(data[:object_kind]) + + service_hook.execute(data) + end + + def commit_status(sha, ref) + response = HTTParty.get(commit_status_path(sha), verify: false) + + if response.code == 200 && response['status'] + response['status'] + else + :error + end + end + + def commit_status_path(sha) + "#{buildkite_endpoint('gitlab')}/status/#{status_token}.json?commit=#{sha}" + end + + def build_page(sha, ref) + "#{project_url}/builds?commit=#{sha}" + end + + def builds_path + "#{project_url}/builds?branch=#{project.default_branch}" + end + + def status_img_path + "#{buildkite_endpoint('badge')}/#{status_token}.svg" + end + + def title + 'Buildkite' + end + + def description + 'Continuous integration and deployments' + end + + def to_param + 'buildkite' + end + + def fields + [ + { type: 'text', + name: 'token', + placeholder: 'Buildkite project GitLab token' }, + + { type: 'text', + name: 'project_url', + placeholder: "#{ENDPOINT}/example/project" } + ] + end + + private + + def webhook_token + token_parts.first + end + + def status_token + token_parts.second + end + + def token_parts + if token.present? + token.split(':') + else + [] + end + end + + def buildkite_endpoint(subdomain = nil) + if subdomain.present? + uri = Addressable::URI.parse(ENDPOINT) + new_endpoint = "#{uri.scheme || 'http'}://#{subdomain}.#{uri.host}" + + if uri.port.present? + "#{new_endpoint}:#{uri.port}" + else + new_endpoint + end + else + ENDPOINT + end + end +end diff --git a/app/models/project_services/campfire_service.rb b/app/models/project_services/campfire_service.rb index 83e1bac1ef..e591afdda6 100644 --- a/app/models/project_services/campfire_service.rb +++ b/app/models/project_services/campfire_service.rb @@ -2,22 +2,24 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# token :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# project_url :string(255) -# subdomain :string(255) -# room :string(255) -# recipients :text -# api_key :string(255) +# 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 # class CampfireService < Service + prop_accessor :token, :subdomain, :room validates :token, presence: true, if: :activated? def title @@ -40,11 +42,17 @@ class CampfireService < Service ] end - def execute(push_data) + def supported_events + %w(push) + end + + def execute(data) + return unless supported_events.include?(data[:object_kind]) + room = gate.find_room_by_name(self.room) return true unless room - message = build_message(push_data) + message = build_message(data) room.speak(message) end @@ -56,7 +64,7 @@ class CampfireService < Service end def build_message(push) - ref = push[:ref].gsub("refs/heads/", "") + ref = Gitlab::Git.ref_name(push[:ref]) before = push[:before] after = push[:after] @@ -64,9 +72,9 @@ class CampfireService < Service message << "[#{project.name_with_namespace}] " message << "#{push[:user_name]} " - if before =~ /000000/ + if Gitlab::Git.blank_ref?(before) message << "pushed new branch #{ref} \n" - elsif after =~ /000000/ + elsif Gitlab::Git.blank_ref?(after) message << "removed branch #{ref} \n" else message << "pushed #{push[:total_commits_count]} commits to #{ref}. " diff --git a/app/models/project_services/ci_service.rb b/app/models/project_services/ci_service.rb index fd34a2a35e..1a36e43924 100644 --- a/app/models/project_services/ci_service.rb +++ b/app/models/project_services/ci_service.rb @@ -1,3 +1,22 @@ +# == 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) +# + # Base class for CI services # List methods you need to implement to get your CI service # working with GitLab Merge Requests @@ -6,12 +25,16 @@ class CiService < Service :ci end + def supported_events + %w(push) + end + # Return complete url to build page # # Ex. # http://jenkins.example.com:8888/job/test1/scm/bySHA1/12d65c # - def build_page(sha) + def build_page(sha, ref) # implement inside child end @@ -28,7 +51,7 @@ class CiService < Service # # => 'running' # # - def commit_status(sha) + def commit_status(sha, ref) # implement inside child end end diff --git a/app/models/project_services/custom_issue_tracker_service.rb b/app/models/project_services/custom_issue_tracker_service.rb new file mode 100644 index 0000000000..8d25f62787 --- /dev/null +++ b/app/models/project_services/custom_issue_tracker_service.rb @@ -0,0 +1,57 @@ +# == 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) +# + +class CustomIssueTrackerService < IssueTrackerService + + prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url + + def title + if self.properties && self.properties['title'].present? + self.properties['title'] + else + 'Custom Issue Tracker' + end + end + + def description + if self.properties && self.properties['description'].present? + self.properties['description'] + else + 'Custom issue tracker' + end + end + + def to_param + 'custom_issue_tracker' + end + + def fields + [ + { type: 'text', name: 'title', placeholder: title }, + { type: 'text', name: 'description', placeholder: description }, + { type: 'text', name: 'project_url', placeholder: 'Project url' }, + { type: 'text', name: 'issues_url', placeholder: 'Issue url' }, + { type: 'text', name: 'new_issue_url', placeholder: 'New Issue url' } + ] + end + + def initialize_properties + self.properties = {} if properties.nil? + end +end diff --git a/app/models/project_services/emails_on_push_service.rb b/app/models/project_services/emails_on_push_service.rb index be5bab4ec3..6f6e5950aa 100644 --- a/app/models/project_services/emails_on_push_service.rb +++ b/app/models/project_services/emails_on_push_service.rb @@ -2,22 +2,25 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# token :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# project_url :string(255) -# subdomain :string(255) -# room :string(255) -# recipients :text -# api_key :string(255) +# 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) # class EmailsOnPushService < Service + prop_accessor :send_from_committer_email + prop_accessor :disable_diffs + prop_accessor :recipients validates :recipients, presence: true, if: :activated? def title @@ -32,12 +35,37 @@ class EmailsOnPushService < Service 'emails_on_push' end + def supported_events + %w(push tag_push) + end + def execute(push_data) - EmailsOnPushWorker.perform_async(project_id, recipients, push_data) + return unless supported_events.include?(push_data[:object_kind]) + + EmailsOnPushWorker.perform_async( + project_id, + recipients, + push_data, + send_from_committer_email: send_from_committer_email?, + disable_diffs: disable_diffs? + ) + end + + def send_from_committer_email? + self.send_from_committer_email == "1" + end + + def disable_diffs? + self.disable_diffs == "1" end def fields + domains = Notify.allowed_email_domains.map { |domain| "user@#{domain}" }.join(", ") [ + { type: 'checkbox', name: 'send_from_committer_email', title: "Send from committer", + help: "Send notifications from the committer's email address if the domain is part of the domain GitLab is running on (e.g. #{domains})." }, + { type: 'checkbox', name: 'disable_diffs', title: "Disable code diffs", + help: "Don't include possibly sensitive code diffs in notification body." }, { type: 'textarea', name: 'recipients', placeholder: 'Emails separated by whitespace' }, ] end diff --git a/app/models/project_services/external_wiki_service.rb b/app/models/project_services/external_wiki_service.rb new file mode 100644 index 0000000000..a199d0e86f --- /dev/null +++ b/app/models/project_services/external_wiki_service.rb @@ -0,0 +1,48 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# + +class ExternalWikiService < Service + include HTTParty + + prop_accessor :external_wiki_url + validates :external_wiki_url, + presence: true, + format: { with: /\A#{URI.regexp}\z/ }, + if: :activated? + + def title + 'External Wiki' + end + + def description + 'Replaces the link to the internal wiki with a link to an external wiki.' + end + + def to_param + 'external_wiki' + end + + def fields + [ + { type: 'text', name: 'external_wiki_url', placeholder: 'The URL of the external Wiki' }, + ] + end + + def execute(_data) + @response = HTTParty.get(properties['external_wiki_url'], verify: true) rescue nil + if @response !=200 + nil + end + end +end diff --git a/app/models/project_services/flowdock_service.rb b/app/models/project_services/flowdock_service.rb index 6cdd04a864..99e361dd6e 100644 --- a/app/models/project_services/flowdock_service.rb +++ b/app/models/project_services/flowdock_service.rb @@ -2,24 +2,25 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# token :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# project_url :string(255) -# subdomain :string(255) -# room :string(255) -# recipients :text -# api_key :string(255) +# 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) # require "flowdock-git-hook" class FlowdockService < Service + prop_accessor :token validates :token, presence: true, if: :activated? def title @@ -40,14 +41,19 @@ class FlowdockService < Service ] end - def execute(push_data) - repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git") + def supported_events + %w(push) + end + + def execute(data) + return unless supported_events.include?(data[:object_kind]) + Flowdock::Git.post( - push_data[:ref], - push_data[:before], - push_data[:after], + data[:ref], + data[:before], + data[:after], token: token, - repo: repo_path, + repo: project.repository.path_to_repo, repo_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}", commit_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/%s", diff_url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/compare/%s...%s", diff --git a/app/models/project_services/gemnasium_service.rb b/app/models/project_services/gemnasium_service.rb index b363d7f57d..4e75bdfc95 100644 --- a/app/models/project_services/gemnasium_service.rb +++ b/app/models/project_services/gemnasium_service.rb @@ -2,24 +2,25 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# token :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# project_url :string(255) -# subdomain :string(255) -# room :string(255) -# recipients :text -# api_key :string(255) +# 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) # require "gemnasium/gitlab_service" class GemnasiumService < Service + prop_accessor :token, :api_key validates :token, :api_key, presence: true, if: :activated? def title @@ -41,15 +42,20 @@ class GemnasiumService < Service ] end - def execute(push_data) - repo_path = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git") + def supported_events + %w(push) + end + + def execute(data) + return unless supported_events.include?(data[:object_kind]) + Gemnasium::GitlabService.execute( - ref: push_data[:ref], - before: push_data[:before], - after: push_data[:after], + ref: data[:ref], + before: data[:before], + after: data[:after], token: token, api_key: api_key, - repo: repo_path + repo: project.repository.path_to_repo ) end end diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb index 58ddce4528..0f9838a575 100644 --- a/app/models/project_services/gitlab_ci_service.rb +++ b/app/models/project_services/gitlab_ci_service.rb @@ -2,27 +2,28 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# token :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# project_url :string(255) -# subdomain :string(255) -# room :string(255) -# recipients :text -# api_key :string(255) +# 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) # class GitlabCiService < CiService + API_PREFIX = "api/v1" + + prop_accessor :project_url, :token validates :project_url, presence: true, if: :activated? validates :token, presence: true, if: :activated? - delegate :execute, to: :service_hook, prefix: nil - after_save :compose_service_hook, if: :activated? def compose_service_hook @@ -31,12 +32,27 @@ class GitlabCiService < CiService hook.save end - def commit_status_path sha - project_url + "/builds/#{sha}/status.json?token=#{token}" + def supported_events + %w(push tag_push) end - def commit_status sha - response = HTTParty.get(commit_status_path(sha), verify: false) + def execute(data) + return unless supported_events.include?(data[:object_kind]) + + service_hook.execute(data) + end + + def commit_status_path(sha, ref) + project_url + "/refs/#{ref}/commits/#{sha}/status.json?token=#{token}" + end + + def get_ci_build(sha, ref) + @ci_builds ||= {} + @ci_builds[sha] ||= HTTParty.get(commit_status_path(sha, ref), verify: false) + end + + def commit_status(sha, ref) + response = get_ci_build(sha, ref) if response.code == 200 and response["status"] response["status"] @@ -45,8 +61,36 @@ class GitlabCiService < CiService end end - def build_page sha - project_url + "/builds/#{sha}" + def fork_registration(new_project, private_token) + params = { + id: new_project.id, + name_with_namespace: new_project.name_with_namespace, + web_url: new_project.web_url, + default_branch: new_project.default_branch, + ssh_url_to_repo: new_project.ssh_url_to_repo + } + + HTTParty.post( + fork_registration_path, + body: { + project_id: project.id, + project_token: token, + private_token: private_token, + data: params }, + verify: false + ) + end + + def commit_coverage(sha, ref) + response = get_ci_build(sha, ref) + + if response.code == 200 and response["coverage"] + response["coverage"] + end + end + + def build_page(sha, ref) + project_url + "/refs/#{ref}/commits/#{sha}" end def builds_path @@ -72,7 +116,13 @@ class GitlabCiService < CiService def fields [ { type: 'text', name: 'token', placeholder: 'GitLab CI project specific token' }, - { type: 'text', name: 'project_url', placeholder: 'http://ci.gitlabhq.com/projects/3'} + { type: 'text', name: 'project_url', placeholder: 'http://ci.gitlabhq.com/projects/3' } ] end + + private + + def fork_registration_path + project_url.sub(/projects\/\d*/, "#{API_PREFIX}/forks") + end end diff --git a/app/models/project_services/gitlab_issue_tracker_service.rb b/app/models/project_services/gitlab_issue_tracker_service.rb new file mode 100644 index 0000000000..5f0553f3b0 --- /dev/null +++ b/app/models/project_services/gitlab_issue_tracker_service.rb @@ -0,0 +1,62 @@ +# == 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 +# + +class GitlabIssueTrackerService < IssueTrackerService + include Rails.application.routes.url_helpers + + default_url_options[:host] = Gitlab.config.gitlab.host + default_url_options[:protocol] = Gitlab.config.gitlab.protocol + default_url_options[:port] = Gitlab.config.gitlab.port unless Gitlab.config.gitlab_on_standard_port? + default_url_options[:script_name] = Gitlab.config.gitlab.relative_url_root + + prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url + + def default? + true + end + + def to_param + 'gitlab' + end + + def project_url + namespace_project_issues_url(project.namespace, project) + end + + def new_issue_url + new_namespace_project_issue_url(namespace_id: project.namespace, project_id: project) + end + + def issue_url(iid) + namespace_project_issue_url(namespace_id: project.namespace, project_id: project, id: iid) + end + + def project_path + namespace_project_issues_path(project.namespace, project) + end + + def new_issue_path + new_namespace_project_issue_path(namespace_id: project.namespace, project_id: project) + end + + def issue_path(iid) + namespace_project_issue_path(namespace_id: project.namespace, project_id: project, id: iid) + end +end diff --git a/app/models/project_services/hipchat_service.rb b/app/models/project_services/hipchat_service.rb index 256debffc5..d264a56ebd 100644 --- a/app/models/project_services/hipchat_service.rb +++ b/app/models/project_services/hipchat_service.rb @@ -2,28 +2,29 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# token :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# project_url :string(255) -# subdomain :string(255) -# room :string(255) -# recipients :text -# api_key :string(255) +# 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) # class HipchatService < Service MAX_COMMITS = 3 + prop_accessor :token, :room, :server validates :token, presence: true, if: :activated? def title - 'Hipchat' + 'HipChat' end def description @@ -36,37 +37,64 @@ class HipchatService < Service def fields [ - { type: 'text', name: 'token', placeholder: '' }, - { type: 'text', name: 'room', placeholder: '' } + { type: 'text', name: 'token', placeholder: 'Room token' }, + { type: 'text', name: 'room', placeholder: 'Room name or ID' }, + { type: 'text', name: 'server', + placeholder: 'Leave blank for default. https://hipchat.example.com' } ] end - def execute(push_data) - gate[room].send('GitLab', create_message(push_data)) + def supported_events + %w(push issue merge_request note tag_push) + end + + def execute(data) + return unless supported_events.include?(data[:object_kind]) + + gate[room].send('GitLab', create_message(data)) end private def gate - @gate ||= HipChat::Client.new(token) + options = { api_version: 'v2' } + options[:server_url] = server unless server.blank? + @gate ||= HipChat::Client.new(token, options) end - def create_message(push) - ref = push[:ref].gsub("refs/heads/", "") + def create_message(data) + object_kind = data[:object_kind] + + message = \ + case object_kind + when "push", "tag_push" + create_push_message(data) + when "issue" + create_issue_message(data) unless is_update?(data) + when "merge_request" + create_merge_request_message(data) unless is_update?(data) + when "note" + create_note_message(data) + end + end + + def create_push_message(push) + ref_type = Gitlab::Git.tag_ref?(push[:ref]) ? 'tag' : 'branch' + ref = Gitlab::Git.ref_name(push[:ref]) + before = push[:before] after = push[:after] message = "" message << "#{push[:user_name]} " - if before =~ /000000/ - message << "pushed new branch #{ref}"\ - " to "\ - "#{project.name_with_namespace.gsub!(/\s/, "")}\n" - elsif after =~ /000000/ - message << "removed branch #{ref} from #{project.name_with_namespace.gsub!(/\s/,'')} \n" + if Gitlab::Git.blank_ref?(before) + message << "pushed new #{ref_type} #{ref}"\ + " to #{project_link}\n" + elsif Gitlab::Git.blank_ref?(after) + message << "removed #{ref_type} #{ref} from #{project_name} \n" else - message << "pushed to branch #{ref} " message << "of #{project.name_with_namespace.gsub!(/\s/,'')} " message << "(Compare changes)" @@ -82,4 +110,129 @@ class HipchatService < Service message end + + def format_body(body) + if body + body = body.truncate(200, separator: ' ', omission: '...') + end + + "

    #{body}
    " + end + + def create_issue_message(data) + user_name = data[:user][:name] + + obj_attr = data[:object_attributes] + obj_attr = HashWithIndifferentAccess.new(obj_attr) + title = obj_attr[:title] + state = obj_attr[:state] + issue_iid = obj_attr[:iid] + issue_url = obj_attr[:url] + description = obj_attr[:description] + + issue_link = "issue ##{issue_iid}" + message = "#{user_name} #{state} #{issue_link} in #{project_link}: #{title}" + + if description + description = format_body(description) + message << description + end + + message + end + + def create_merge_request_message(data) + user_name = data[:user][:name] + + obj_attr = data[:object_attributes] + obj_attr = HashWithIndifferentAccess.new(obj_attr) + merge_request_id = obj_attr[:iid] + source_branch = obj_attr[:source_branch] + target_branch = obj_attr[:target_branch] + state = obj_attr[:state] + description = obj_attr[:description] + title = obj_attr[:title] + + merge_request_url = "#{project_url}/merge_requests/#{merge_request_id}" + merge_request_link = "merge request ##{merge_request_id}" + message = "#{user_name} #{state} #{merge_request_link} in " \ + "#{project_link}: #{title}" + + if description + description = format_body(description) + message << description + end + + message + end + + def format_title(title) + "" + title.lines.first.chomp + "" + end + + def create_note_message(data) + data = HashWithIndifferentAccess.new(data) + user_name = data[:user][:name] + + repo_attr = HashWithIndifferentAccess.new(data[:repository]) + + obj_attr = HashWithIndifferentAccess.new(data[:object_attributes]) + note = obj_attr[:note] + note_url = obj_attr[:url] + noteable_type = obj_attr[:noteable_type] + + case noteable_type + when "Commit" + commit_attr = HashWithIndifferentAccess.new(data[:commit]) + subject_desc = commit_attr[:id] + subject_desc = Commit.truncate_sha(subject_desc) + subject_type = "commit" + title = format_title(commit_attr[:message]) + when "Issue" + subj_attr = HashWithIndifferentAccess.new(data[:issue]) + subject_id = subj_attr[:iid] + subject_desc = "##{subject_id}" + subject_type = "issue" + title = format_title(subj_attr[:title]) + when "MergeRequest" + subj_attr = HashWithIndifferentAccess.new(data[:merge_request]) + subject_id = subj_attr[:iid] + subject_desc = "##{subject_id}" + subject_type = "merge request" + title = format_title(subj_attr[:title]) + when "Snippet" + subj_attr = HashWithIndifferentAccess.new(data[:snippet]) + subject_id = subj_attr[:id] + subject_desc = "##{subject_id}" + subject_type = "snippet" + title = format_title(subj_attr[:title]) + end + + subject_html = "#{subject_type} #{subject_desc}" + message = "#{user_name} commented on #{subject_html} in #{project_link}: " + message << title + + if note + note = format_body(note) + message << note + end + + message + end + + def project_name + project.name_with_namespace.gsub(/\s/, '') + end + + def project_url + project.web_url + end + + def project_link + "#{project_name}" + end + + def is_update?(data) + data[:object_attributes][:action] == 'update' + end end diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb new file mode 100644 index 0000000000..e9e1e276e7 --- /dev/null +++ b/app/models/project_services/irker_service.rb @@ -0,0 +1,163 @@ +# == 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) +# + +require 'uri' + +class IrkerService < Service + prop_accessor :colorize_messages, :recipients, :channels + validates :recipients, presence: true, if: :activated? + validate :check_recipients_count, if: :activated? + + before_validation :get_channels + after_initialize :initialize_settings + + # Writer for RSpec tests + attr_writer :settings + + def initialize_settings + # See the documentation (doc/project_services/irker.md) for possible values + # here + @settings ||= { + server_ip: 'localhost', + server_port: 6659, + max_channels: 3, + default_irc_uri: nil + } + end + + def title + 'Irker (IRC gateway)' + end + + def description + 'Send IRC messages, on update, to a list of recipients through an Irker '\ + 'gateway.' + end + + def help + msg = 'Recipients have to be specified with a full URI: '\ + 'irc[s]://irc.network.net[:port]/#channel. Special cases: if you want '\ + 'the channel to be a nickname instead, append ",isnick" to the channel '\ + 'name; if the channel is protected by a secret password, append '\ + '"?key=secretpassword" to the URI.' + + unless @settings[:default_irc].nil? + msg += ' Note that a default IRC URI is provided by this service\'s '\ + "administrator: #{default_irc}. You can thus just give a channel name." + end + msg + end + + def to_param + 'irker' + end + + def supported_events + %w(push) + end + + def execute(data) + return unless supported_events.include?(data[:object_kind]) + + IrkerWorker.perform_async(project_id, channels, + colorize_messages, data, @settings) + end + + def fields + [ + { type: 'textarea', name: 'recipients', + placeholder: 'Recipients/channels separated by whitespaces' }, + { type: 'checkbox', name: 'colorize_messages' }, + ] + end + + private + + def check_recipients_count + return true if recipients.nil? || recipients.empty? + + if recipients.split(/\s+/).count > max_chans + errors.add(:recipients, "are limited to #{max_chans}") + end + end + + def max_chans + @settings[:max_channels] + end + + def get_channels + return true unless :activated? + return true if recipients.nil? || recipients.empty? + + map_recipients + + errors.add(:recipients, 'are all invalid') if channels.empty? + true + end + + def map_recipients + self.channels = recipients.split(/\s+/).map do |recipient| + format_channel default_irc_uri, recipient + end + channels.reject! &:nil? + end + + def default_irc_uri + default_irc = @settings[:default_irc_uri] + if !(default_irc.nil? || default_irc[-1] == '/') + default_irc += '/' + end + default_irc + end + + def format_channel(default_irc, recipient) + cnt = 0 + url = nil + + # Try to parse the chan as a full URI + begin + uri = URI.parse(recipient) + raise URI::InvalidURIError if uri.scheme.nil? && cnt == 0 + rescue URI::InvalidURIError + unless default_irc.nil? + cnt += 1 + recipient = "#{default_irc}#{recipient}" + retry if cnt == 1 + end + else + url = consider_uri uri + end + url + end + + def consider_uri(uri) + # Authorize both irc://domain.com/#chan and irc://domain.com/chan + if uri.is_a?(URI) && uri.scheme[/^ircs?\z/] && !uri.path.nil? + # Do not authorize irc://domain.com/ + if uri.fragment.nil? && uri.path.length > 1 + uri.to_s + else + # Authorize irc://domain.com/smthg#chan + # The irker daemon will deal with it by concatenating smthg and + # chan, thus sending messages on #smthgchan + uri.to_s + end + end + end +end diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb new file mode 100644 index 0000000000..c8ab9d63b7 --- /dev/null +++ b/app/models/project_services/issue_tracker_service.rb @@ -0,0 +1,125 @@ +# == 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 +# + +class IssueTrackerService < Service + + validates :project_url, :issues_url, :new_issue_url, presence: true, if: :activated? + + def category + :issue_tracker + end + + def default? + false + end + + def issue_url(iid) + self.issues_url.gsub(':id', iid.to_s) + end + + def project_path + project_url + end + + def new_issue_path + new_issue_url + end + + def issue_path(iid) + issue_url(iid) + end + + def fields + [ + { type: 'text', name: 'description', placeholder: description }, + { type: 'text', name: 'project_url', placeholder: 'Project url' }, + { type: 'text', name: 'issues_url', placeholder: 'Issue url' }, + { type: 'text', name: 'new_issue_url', placeholder: 'New Issue url' } + ] + end + + def initialize_properties + if properties.nil? + if enabled_in_gitlab_config + self.properties = { + title: issues_tracker['title'], + project_url: add_issues_tracker_id(issues_tracker['project_url']), + issues_url: add_issues_tracker_id(issues_tracker['issues_url']), + new_issue_url: add_issues_tracker_id(issues_tracker['new_issue_url']) + } + else + self.properties = {} + end + end + end + + def supported_events + %w(push) + end + + def execute(data) + return unless supported_events.include?(data[:object_kind]) + + message = "#{self.type} was unable to reach #{self.project_url}. Check the url and try again." + result = false + + begin + url = URI.parse(self.project_url) + + if url.host && url.port + http = Net::HTTP.start(url.host, url.port, { open_timeout: 5, read_timeout: 5 }) + response = http.head("/") + + if response + message = "#{self.type} received response #{response.code} when attempting to connect to #{self.project_url}" + result = true + end + end + rescue Timeout::Error, SocketError, Errno::ECONNRESET, Errno::ECONNREFUSED => error + message = "#{self.type} had an error when trying to connect to #{self.project_url}: #{error.message}" + end + Rails.logger.info(message) + result + end + + private + + def enabled_in_gitlab_config + Gitlab.config.issues_tracker && + Gitlab.config.issues_tracker.values.any? && + issues_tracker + end + + def issues_tracker + Gitlab.config.issues_tracker[to_param] + end + + def add_issues_tracker_id(url) + if self.project + id = self.project.issues_tracker_id + + if id + url = url.gsub(":issues_tracker_id", id) + end + end + + url + end +end diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb new file mode 100644 index 0000000000..fcd9dc2f33 --- /dev/null +++ b/app/models/project_services/jira_service.rb @@ -0,0 +1,58 @@ +# == 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 +# + +class JiraService < IssueTrackerService + include Rails.application.routes.url_helpers + + prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url + + def help + issue_tracker_link = help_page_path("integration", "external-issue-tracker") + + line1 = "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](#{issue_tracker_link}) for details." + + line2 = '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)' + + [line1, line2].join("\n\n") + end + + def title + if self.properties && self.properties['title'].present? + self.properties['title'] + else + 'JIRA' + end + end + + def description + if self.properties && self.properties['description'].present? + self.properties['description'] + else + 'Jira issue tracker' + end + end + + def to_param + 'jira' + end +end diff --git a/app/models/project_services/pivotaltracker_service.rb b/app/models/project_services/pivotaltracker_service.rb index aa2bcc5def..ade9ee9787 100644 --- a/app/models/project_services/pivotaltracker_service.rb +++ b/app/models/project_services/pivotaltracker_service.rb @@ -2,24 +2,26 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# token :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# project_url :string(255) -# subdomain :string(255) -# room :string(255) -# recipients :text -# api_key :string(255) +# 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 # class PivotaltrackerService < Service include HTTParty + prop_accessor :token validates :token, presence: true, if: :activated? def title @@ -40,9 +42,15 @@ class PivotaltrackerService < Service ] end - def execute(push) + def supported_events + %w(push) + end + + def execute(data) + return unless supported_events.include?(data[:object_kind]) + url = 'https://www.pivotaltracker.com/services/v5/source_commits' - push[:commits].each do |commit| + data[:commits].each do |commit| message = { 'source_commit' => { 'commit_id' => commit[:id], diff --git a/app/models/project_services/pushover_service.rb b/app/models/project_services/pushover_service.rb new file mode 100644 index 0000000000..53edf522e9 --- /dev/null +++ b/app/models/project_services/pushover_service.rb @@ -0,0 +1,125 @@ +# == 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 +# + +class PushoverService < Service + include HTTParty + base_uri 'https://api.pushover.net/1' + + prop_accessor :api_key, :user_key, :device, :priority, :sound + validates :api_key, :user_key, :priority, presence: true, if: :activated? + + def title + 'Pushover' + end + + def description + 'Pushover makes it easy to get real-time notifications on your Android device, iPhone, iPad, and Desktop.' + end + + def to_param + 'pushover' + end + + def fields + [ + { type: 'text', name: 'api_key', placeholder: 'Your application key' }, + { type: 'text', name: 'user_key', placeholder: 'Your user key' }, + { type: 'text', name: 'device', placeholder: 'Leave blank for all active devices' }, + { type: 'select', name: 'priority', choices: + [ + ['Lowest Priority', -2], + ['Low Priority', -1], + ['Normal Priority', 0], + ['High Priority', 1] + ], + default_choice: 0 + }, + { type: 'select', name: 'sound', choices: + [ + ['Device default sound', nil], + ['Pushover (default)', 'pushover'], + ['Bike', 'bike'], + ['Bugle', 'bugle'], + ['Cash Register', 'cashregister'], + ['Classical', 'classical'], + ['Cosmic', 'cosmic'], + ['Falling', 'falling'], + ['Gamelan', 'gamelan'], + ['Incoming', 'incoming'], + ['Intermission', 'intermission'], + ['Magic', 'magic'], + ['Mechanical', 'mechanical'], + ['Piano Bar', 'pianobar'], + ['Siren', 'siren'], + ['Space Alarm', 'spacealarm'], + ['Tug Boat', 'tugboat'], + ['Alien Alarm (long)', 'alien'], + ['Climb (long)', 'climb'], + ['Persistent (long)', 'persistent'], + ['Pushover Echo (long)', 'echo'], + ['Up Down (long)', 'updown'], + ['None (silent)', 'none'] + ] + }, + ] + end + + def supported_events + %w(push) + end + + def execute(data) + return unless supported_events.include?(data[:object_kind]) + + ref = Gitlab::Git.ref_name(data[:ref]) + before = data[:before] + after = data[:after] + + if Gitlab::Git.blank_ref?(before) + message = "#{data[:user_name]} pushed new branch \"#{ref}\"." + elsif Gitlab::Git.blank_ref?(after) + message = "#{data[:user_name]} deleted branch \"#{ref}\"." + else + message = "#{data[:user_name]} push to branch \"#{ref}\"." + end + + if data[:total_commits_count] > 0 + message << "\nTotal commits count: #{data[:total_commits_count]}" + end + + pushover_data = { + token: api_key, + user: user_key, + device: device, + priority: priority, + title: "#{project.name_with_namespace}", + message: message, + url: data[:repository][:homepage], + url_title: "See project #{project.name_with_namespace}" + } + + # Sound parameter MUST NOT be sent to API if not selected + if sound + pushover_data.merge!(sound: sound) + end + + PushoverService.post('/messages.json', body: pushover_data) + end +end diff --git a/app/models/project_services/redmine_service.rb b/app/models/project_services/redmine_service.rb new file mode 100644 index 0000000000..dd9ba97ee1 --- /dev/null +++ b/app/models/project_services/redmine_service.rb @@ -0,0 +1,44 @@ +# == 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 +# + +class RedmineService < IssueTrackerService + + prop_accessor :title, :description, :project_url, :issues_url, :new_issue_url + + def title + if self.properties && self.properties['title'].present? + self.properties['title'] + else + 'Redmine' + end + end + + def description + if self.properties && self.properties['description'].present? + self.properties['description'] + else + 'Redmine issue tracker' + end + end + + def to_param + 'redmine' + end +end diff --git a/app/models/project_services/slack_message.rb b/app/models/project_services/slack_message.rb deleted file mode 100644 index 28204e5ea6..0000000000 --- a/app/models/project_services/slack_message.rb +++ /dev/null @@ -1,110 +0,0 @@ -require 'slack-notifier' - -class SlackMessage - def initialize(params) - @after = params.fetch(:after) - @before = params.fetch(:before) - @commits = params.fetch(:commits, []) - @project_name = params.fetch(:project_name) - @project_url = params.fetch(:project_url) - @ref = params.fetch(:ref).gsub('refs/heads/', '') - @username = params.fetch(:user_name) - end - - def pretext - format(message) - end - - def attachments - return [] if new_branch? || removed_branch? - - commit_message_attachments - end - - private - - attr_reader :after - attr_reader :before - attr_reader :commits - attr_reader :project_name - attr_reader :project_url - attr_reader :ref - attr_reader :username - - def message - if new_branch? - new_branch_message - elsif removed_branch? - removed_branch_message - else - push_message - end - end - - def format(string) - Slack::Notifier::LinkFormatter.format(string) - end - - def new_branch_message - "#{username} pushed new branch #{branch_link} to #{project_link}" - end - - def removed_branch_message - "#{username} removed branch #{ref} from #{project_link}" - end - - def push_message - "#{username} pushed to branch #{branch_link} of #{project_link} (#{compare_link})" - end - - def commit_messages - commits.each_with_object('') do |commit, str| - str << compose_commit_message(commit) - end.chomp - end - - def commit_message_attachments - [{ text: format(commit_messages), color: attachment_color }] - end - - def compose_commit_message(commit) - author = commit.fetch(:author).fetch(:name) - id = commit.fetch(:id)[0..8] - message = commit.fetch(:message) - url = commit.fetch(:url) - - "[#{id}](#{url}): #{message} - #{author}\n" - end - - def new_branch? - before =~ /000000/ - end - - def removed_branch? - after =~ /000000/ - end - - def branch_url - "#{project_url}/commits/#{ref}" - end - - def compare_url - "#{project_url}/compare/#{before}...#{after}" - end - - def branch_link - "[#{ref}](#{branch_url})" - end - - def project_link - "[#{project_name}](#{project_url})" - end - - def compare_link - "[Compare changes](#{compare_url})" - end - - def attachment_color - '#345' - end -end diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb index 7e54188abf..36d9874edd 100644 --- a/app/models/project_services/slack_service.rb +++ b/app/models/project_services/slack_service.rb @@ -2,25 +2,25 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# token :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# project_url :string(255) -# subdomain :string(255) -# room :string(255) -# recipients :text -# api_key :string(255) +# 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 # class SlackService < Service - validates :room, presence: true, if: :activated? - validates :subdomain, presence: true, if: :activated? - validates :token, presence: true, if: :activated? + prop_accessor :webhook, :username, :channel + validates :webhook, presence: true, if: :activated? def title 'Slack' @@ -36,22 +36,52 @@ class SlackService < Service def fields [ - { type: 'text', name: 'subdomain', placeholder: '' }, - { type: 'text', name: 'token', placeholder: '' }, - { type: 'text', name: 'room', placeholder: 'Ex. #general' }, + { type: 'text', name: 'webhook', + placeholder: 'https://hooks.slack.com/services/...' }, + { type: 'text', name: 'username', placeholder: 'username' }, + { type: 'text', name: 'channel', placeholder: '#channel' } ] end - def execute(push_data) - message = SlackMessage.new(push_data.merge( + def supported_events + %w(push issue merge_request note tag_push) + end + + def execute(data) + return unless supported_events.include?(data[:object_kind]) + return unless webhook.present? + + object_kind = data[:object_kind] + + data = data.merge( project_url: project_url, project_name: project_name - )) + ) - notifier = Slack::Notifier.new(subdomain, token) - notifier.channel = room - notifier.username = 'GitLab' - notifier.ping(message.pretext, attachments: message.attachments) + # WebHook events often have an 'update' event that follows a 'open' or + # 'close' action. Ignore update events for now to prevent duplicate + # messages from arriving. + + message = \ + case object_kind + when "push", "tag_push" + PushMessage.new(data) + when "issue" + IssueMessage.new(data) unless is_update?(data) + when "merge_request" + MergeMessage.new(data) unless is_update?(data) + when "note" + NoteMessage.new(data) + end + + opt = {} + opt[:channel] = channel if channel + opt[:username] = username if username + + if message + notifier = Slack::Notifier.new(webhook, opt) + notifier.ping(message.pretext, attachments: message.attachments) + end end private @@ -63,4 +93,13 @@ class SlackService < Service def project_url project.web_url end + + def is_update?(data) + data[:object_attributes][:action] == 'update' + end end + +require "slack_service/issue_message" +require "slack_service/push_message" +require "slack_service/merge_message" +require "slack_service/note_message" diff --git a/app/models/project_services/slack_service/base_message.rb b/app/models/project_services/slack_service/base_message.rb new file mode 100644 index 0000000000..aa00d6061a --- /dev/null +++ b/app/models/project_services/slack_service/base_message.rb @@ -0,0 +1,31 @@ +require 'slack-notifier' + +class SlackService + class BaseMessage + def initialize(params) + raise NotImplementedError + end + + def pretext + format(message) + end + + def attachments + raise NotImplementedError + end + + private + + def message + raise NotImplementedError + end + + def format(string) + Slack::Notifier::LinkFormatter.format(string) + end + + def attachment_color + '#345' + end + end +end diff --git a/app/models/project_services/slack_service/issue_message.rb b/app/models/project_services/slack_service/issue_message.rb new file mode 100644 index 0000000000..5af24a8060 --- /dev/null +++ b/app/models/project_services/slack_service/issue_message.rb @@ -0,0 +1,56 @@ +class SlackService + class IssueMessage < BaseMessage + attr_reader :user_name + attr_reader :title + attr_reader :project_name + attr_reader :project_url + attr_reader :issue_iid + attr_reader :issue_url + attr_reader :action + attr_reader :state + attr_reader :description + + def initialize(params) + @user_name = params[:user][:name] + @project_name = params[:project_name] + @project_url = params[:project_url] + + obj_attr = params[:object_attributes] + obj_attr = HashWithIndifferentAccess.new(obj_attr) + @title = obj_attr[:title] + @issue_iid = obj_attr[:iid] + @issue_url = obj_attr[:url] + @action = obj_attr[:action] + @state = obj_attr[:state] + @description = obj_attr[:description] + end + + def attachments + return [] unless opened_issue? + + description_message + end + + private + + def message + "#{user_name} #{state} #{issue_link} in #{project_link}: *#{title}*" + end + + def opened_issue? + action == "open" + end + + def description_message + [{ text: format(description), color: attachment_color }] + end + + def project_link + "[#{project_name}](#{project_url})" + end + + def issue_link + "[issue ##{issue_iid}](#{issue_url})" + end + end +end diff --git a/app/models/project_services/slack_service/merge_message.rb b/app/models/project_services/slack_service/merge_message.rb new file mode 100644 index 0000000000..e792c258f7 --- /dev/null +++ b/app/models/project_services/slack_service/merge_message.rb @@ -0,0 +1,60 @@ +class SlackService + class MergeMessage < BaseMessage + attr_reader :user_name + attr_reader :project_name + attr_reader :project_url + attr_reader :merge_request_id + attr_reader :source_branch + attr_reader :target_branch + attr_reader :state + attr_reader :title + + def initialize(params) + @user_name = params[:user][:name] + @project_name = params[:project_name] + @project_url = params[:project_url] + + obj_attr = params[:object_attributes] + obj_attr = HashWithIndifferentAccess.new(obj_attr) + @merge_request_id = obj_attr[:iid] + @source_branch = obj_attr[:source_branch] + @target_branch = obj_attr[:target_branch] + @state = obj_attr[:state] + @title = format_title(obj_attr[:title]) + end + + def pretext + format(message) + end + + def attachments + [] + end + + private + + def format_title(title) + '*' + title.lines.first.chomp + '*' + end + + def message + merge_request_message + end + + def project_link + "[#{project_name}](#{project_url})" + end + + def merge_request_message + "#{user_name} #{state} #{merge_request_link} in #{project_link}: #{title}" + end + + def merge_request_link + "[merge request ##{merge_request_id}](#{merge_request_url})" + end + + def merge_request_url + "#{project_url}/merge_requests/#{merge_request_id}" + end + end +end diff --git a/app/models/project_services/slack_service/note_message.rb b/app/models/project_services/slack_service/note_message.rb new file mode 100644 index 0000000000..074478b292 --- /dev/null +++ b/app/models/project_services/slack_service/note_message.rb @@ -0,0 +1,82 @@ +class SlackService + class NoteMessage < BaseMessage + attr_reader :message + attr_reader :user_name + attr_reader :project_name + attr_reader :project_link + attr_reader :note + attr_reader :note_url + attr_reader :title + + def initialize(params) + params = HashWithIndifferentAccess.new(params) + @user_name = params[:user][:name] + @project_name = params[:project_name] + @project_url = params[:project_url] + + obj_attr = params[:object_attributes] + obj_attr = HashWithIndifferentAccess.new(obj_attr) + @note = obj_attr[:note] + @note_url = obj_attr[:url] + noteable_type = obj_attr[:noteable_type] + + case noteable_type + when "Commit" + create_commit_note(HashWithIndifferentAccess.new(params[:commit])) + when "Issue" + create_issue_note(HashWithIndifferentAccess.new(params[:issue])) + when "MergeRequest" + create_merge_note(HashWithIndifferentAccess.new(params[:merge_request])) + when "Snippet" + create_snippet_note(HashWithIndifferentAccess.new(params[:snippet])) + end + end + + def attachments + description_message + end + + private + + def format_title(title) + title.lines.first.chomp + end + + def create_commit_note(commit) + commit_sha = commit[:id] + commit_sha = Commit.truncate_sha(commit_sha) + commit_link = "[commit #{commit_sha}](#{@note_url})" + title = format_title(commit[:message]) + @message = "#{@user_name} commented on #{commit_link} in #{project_link}: *#{title}*" + end + + def create_issue_note(issue) + issue_iid = issue[:iid] + note_link = "[issue ##{issue_iid}](#{@note_url})" + title = format_title(issue[:title]) + @message = "#{@user_name} commented on #{note_link} in #{project_link}: *#{title}*" + end + + def create_merge_note(merge_request) + merge_request_id = merge_request[:iid] + merge_request_link = "[merge request ##{merge_request_id}](#{@note_url})" + title = format_title(merge_request[:title]) + @message = "#{@user_name} commented on #{merge_request_link} in #{project_link}: *#{title}*" + end + + def create_snippet_note(snippet) + snippet_id = snippet[:id] + snippet_link = "[snippet ##{snippet_id}](#{@note_url})" + title = format_title(snippet[:title]) + @message = "#{@user_name} commented on #{snippet_link} in #{project_link}: *#{title}*" + end + + def description_message + [{ text: format(@note), color: attachment_color }] + end + + def project_link + "[#{@project_name}](#{@project_url})" + end + end +end diff --git a/app/models/project_services/slack_service/push_message.rb b/app/models/project_services/slack_service/push_message.rb new file mode 100644 index 0000000000..b26f3e9ddc --- /dev/null +++ b/app/models/project_services/slack_service/push_message.rb @@ -0,0 +1,110 @@ +class SlackService + class PushMessage < BaseMessage + attr_reader :after + attr_reader :before + attr_reader :commits + attr_reader :project_name + attr_reader :project_url + attr_reader :ref + attr_reader :ref_type + attr_reader :user_name + + def initialize(params) + @after = params[:after] + @before = params[:before] + @commits = params.fetch(:commits, []) + @project_name = params[:project_name] + @project_url = params[:project_url] + @ref_type = Gitlab::Git.tag_ref?(params[:ref]) ? 'tag' : 'branch' + @ref = Gitlab::Git.ref_name(params[:ref]) + @user_name = params[:user_name] + end + + def pretext + format(message) + end + + def attachments + return [] if new_branch? || removed_branch? + + commit_message_attachments + end + + private + + def message + if new_branch? + new_branch_message + elsif removed_branch? + removed_branch_message + else + push_message + end + end + + def format(string) + Slack::Notifier::LinkFormatter.format(string) + end + + def new_branch_message + "#{user_name} pushed new #{ref_type} #{branch_link} to #{project_link}" + end + + def removed_branch_message + "#{user_name} removed #{ref_type} #{ref} from #{project_link}" + end + + def push_message + "#{user_name} pushed to #{ref_type} #{branch_link} of #{project_link} (#{compare_link})" + end + + def commit_messages + commits.map { |commit| compose_commit_message(commit) }.join("\n") + end + + def commit_message_attachments + [{ text: format(commit_messages), color: attachment_color }] + end + + def compose_commit_message(commit) + author = commit[:author][:name] + id = Commit.truncate_sha(commit[:id]) + message = commit[:message] + url = commit[:url] + + "[#{id}](#{url}): #{message} - #{author}" + end + + def new_branch? + Gitlab::Git.blank_ref?(before) + end + + def removed_branch? + Gitlab::Git.blank_ref?(after) + end + + def branch_url + "#{project_url}/commits/#{ref}" + end + + def compare_url + "#{project_url}/compare/#{before}...#{after}" + end + + def branch_link + "[#{ref}](#{branch_url})" + end + + def project_link + "[#{project_name}](#{project_url})" + end + + def compare_link + "[Compare changes](#{compare_url})" + end + + def attachment_color + '#345' + end + end +end diff --git a/app/models/project_services/teamcity_service.rb b/app/models/project_services/teamcity_service.rb new file mode 100644 index 0000000000..3c002a1634 --- /dev/null +++ b/app/models/project_services/teamcity_service.rb @@ -0,0 +1,145 @@ +# == 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 +# + +class TeamcityService < CiService + include HTTParty + + prop_accessor :teamcity_url, :build_type, :username, :password + + validates :teamcity_url, + presence: true, + format: { with: /\A#{URI.regexp}\z/ }, if: :activated? + validates :build_type, presence: true, if: :activated? + validates :username, + presence: true, + if: ->(service) { service.password? }, if: :activated? + validates :password, + presence: true, + if: ->(service) { service.username? }, if: :activated? + + attr_accessor :response + + after_save :compose_service_hook, if: :activated? + + def compose_service_hook + hook = service_hook || build_service_hook + hook.save + end + + def title + 'JetBrains TeamCity CI' + end + + def description + 'A continuous integration and build server' + end + + def help + 'The build configuration in Teamcity must use the build format '\ + 'number %build.vcs.number% '\ + 'you will also want to configure monitoring of all branches so merge '\ + 'requests build, that setting is in the vsc root advanced settings.' + end + + def to_param + 'teamcity' + end + + def supported_events + %w(push) + end + + def fields + [ + { type: 'text', name: 'teamcity_url', + placeholder: 'TeamCity root URL like https://teamcity.example.com' }, + { type: 'text', name: 'build_type', + placeholder: 'Build configuration ID' }, + { type: 'text', name: 'username', + placeholder: 'A user with permissions to trigger a manual build' }, + { type: 'password', name: 'password' }, + ] + end + + def build_info(sha) + url = URI.parse("#{teamcity_url}/httpAuth/app/rest/builds/"\ + "branch:unspecified:any,number:#{sha}") + auth = { + username: username, + password: password, + } + @response = HTTParty.get("#{url}", verify: false, basic_auth: auth) + end + + def build_page(sha, ref) + build_info(sha) if @response.nil? || !@response.code + + if @response.code != 200 + # If actual build link can't be determined, + # send user to build summary page. + "#{teamcity_url}/viewLog.html?buildTypeId=#{build_type}" + else + # If actual build link is available, go to build result page. + built_id = @response['build']['id'] + "#{teamcity_url}/viewLog.html?buildId=#{built_id}"\ + "&buildTypeId=#{build_type}" + end + end + + def commit_status(sha, ref) + build_info(sha) if @response.nil? || !@response.code + return :error unless @response.code == 200 || @response.code == 404 + + status = if @response.code == 404 + 'Pending' + else + @response['build']['status'] + end + + if status.include?('SUCCESS') + 'success' + elsif status.include?('FAILURE') + 'failed' + elsif status.include?('Pending') + 'pending' + else + :error + end + end + + def execute(data) + return unless supported_events.include?(data[:object_kind]) + + auth = { + username: username, + password: password, + } + + branch = Gitlab::Git.ref_name(data[:ref]) + + self.class.post("#{teamcity_url}/httpAuth/app/rest/buildQueue", + body: ""\ + ""\ + '', + headers: { 'Content-type' => 'application/xml' }, + basic_auth: auth + ) + end +end diff --git a/app/models/project_snippet.rb b/app/models/project_snippet.rb index 14c8804642..9e2c1b0e18 100644 --- a/app/models/project_snippet.rb +++ b/app/models/project_snippet.rb @@ -2,17 +2,17 @@ # # 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) -# expires_at :datetime -# private :boolean default(TRUE), not null -# type :string(255) +# 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) +# expires_at :datetime +# type :string(255) +# visibility_level :integer default(0), not null # class ProjectSnippet < Snippet diff --git a/app/models/project_team.rb b/app/models/project_team.rb index 0bbbd3d00e..56e49af232 100644 --- a/app/models/project_team.rb +++ b/app/models/project_team.rb @@ -11,13 +11,13 @@ class ProjectTeam # @team << [@user, :master] # @team << [@users, :master] # - def << args - users = args.first + def <<(args) + users, access, current_user = *args if users.respond_to?(:each) - add_users(users, args.second) + add_users(users, access, current_user) else - add_user(users, args.second) + add_user(users, access, current_user) end end @@ -31,37 +31,34 @@ class ProjectTeam user end - def find_tm(user_id) - tm = project.users_projects.find_by(user_id: user_id) + def find_member(user_id) + member = project.project_members.find_by(user_id: user_id) # If user is not in project members # we should check for group membership - if group && !tm - tm = group.users_groups.find_by(user_id: user_id) + if group && !member + member = group.group_members.find_by(user_id: user_id) end - tm + member end - def add_user(user, access) - add_users_ids([user.id], access) - end - - def add_users(users, access) - add_users_ids(users.map(&:id), access) - end - - def add_users_ids(user_ids, access) - UsersProject.add_users_into_projects( + def add_users(users, access, current_user = nil) + ProjectMember.add_users_into_projects( [project.id], - user_ids, - access + users, + access, + current_user ) end + def add_user(user, access, current_user = nil) + add_users([user], access, current_user) + end + # Remove all users from project team def truncate - UsersProject.truncate_team(project) + ProjectMember.truncate_team(project) end def users @@ -88,27 +85,28 @@ class ProjectTeam @masters ||= fetch_members(:masters) end - def import(source_project) + def import(source_project, current_user = nil) target_project = project - source_team = source_project.users_projects.to_a - target_user_ids = target_project.users_projects.pluck(:user_id) + source_members = source_project.project_members.to_a + target_user_ids = target_project.project_members.pluck(:user_id) - source_team.reject! do |tm| + source_members.reject! do |member| # Skip if user already present in team - target_user_ids.include?(tm.user_id) + !member.invite? && target_user_ids.include?(member.user_id) end - source_team.map! do |tm| - new_tm = tm.dup - new_tm.id = nil - new_tm.project_id = target_project.id - new_tm + source_members.map! do |member| + new_member = member.dup + new_member.id = nil + new_member.source = target_project + new_member.created_by = current_user + new_member end - UsersProject.transaction do - source_team.each do |tm| - tm.save + ProjectMember.transaction do + source_members.each do |member| + member.save end end @@ -118,27 +116,31 @@ class ProjectTeam end def guest?(user) - max_tm_access(user.id) == Gitlab::Access::GUEST + max_member_access(user.id) == Gitlab::Access::GUEST end def reporter?(user) - max_tm_access(user.id) == Gitlab::Access::REPORTER + max_member_access(user.id) == Gitlab::Access::REPORTER end def developer?(user) - max_tm_access(user.id) == Gitlab::Access::DEVELOPER + max_member_access(user.id) == Gitlab::Access::DEVELOPER end def master?(user) - max_tm_access(user.id) == Gitlab::Access::MASTER + max_member_access(user.id) == Gitlab::Access::MASTER end - def max_tm_access(user_id) + def member?(user_id) + !!find_member(user_id) + end + + def max_member_access(user_id) access = [] - access << project.users_projects.find_by(user_id: user_id).try(:access_field) + access << project.project_members.find_by(user_id: user_id).try(:access_field) if group - access << group.users_groups.find_by(user_id: user_id).try(:access_field) + access << group.group_members.find_by(user_id: user_id).try(:access_field) end access.compact.max @@ -147,8 +149,8 @@ class ProjectTeam private def fetch_members(level = nil) - project_members = project.users_projects - group_members = group ? group.users_groups : [] + project_members = project.project_members + group_members = group ? group.group_members : [] if level project_members = project_members.send(level) @@ -156,7 +158,7 @@ class ProjectTeam end user_ids = project_members.pluck(:user_id) - user_ids += group_members.pluck(:user_id) if group + user_ids.push(*group_members.pluck(:user_id)) if group User.where(id: user_ids) end diff --git a/app/models/project_wiki.rb b/app/models/project_wiki.rb index a8ba5efcc7..772c868d9c 100644 --- a/app/models/project_wiki.rb +++ b/app/models/project_wiki.rb @@ -2,9 +2,10 @@ class ProjectWiki include Gitlab::ShellAdapter MARKUPS = { - "Markdown" => :markdown, - "RDoc" => :rdoc - } + 'Markdown' => :markdown, + 'RDoc' => :rdoc, + 'AsciiDoc' => :asciidoc + } unless defined?(MARKUPS) class CouldNotCreateWikiError < StandardError; end @@ -103,7 +104,19 @@ class ProjectWiki def page_title_and_dir(title) title_array = title.split("/") title = title_array.pop - [title.gsub(/\.[^.]*$/, ""), title_array.join("/")] + [title, title_array.join("/")] + end + + def search_files(query) + repository.search_files(query, default_branch) + end + + def repository + Repository.new(path_with_namespace, default_branch) + end + + def default_branch + wiki.class.default_ref end private @@ -123,7 +136,7 @@ class ProjectWiki def commit_details(action, message = nil, title = nil) commit_message = message || default_message(action, title) - {email: @user.email, name: @user.name, message: commit_message} + { email: @user.email, name: @user.name, message: commit_message } end def default_message(action, title) diff --git a/app/models/protected_branch.rb b/app/models/protected_branch.rb index 1b06dd7752..97207ba127 100644 --- a/app/models/protected_branch.rb +++ b/app/models/protected_branch.rb @@ -2,11 +2,12 @@ # # 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 +# 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 # class ProtectedBranch < ActiveRecord::Base diff --git a/app/models/repository.rb b/app/models/repository.rb index e970c449a7..263a436d52 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -25,14 +25,16 @@ class Repository raw_repository.empty? end - def commit(id = nil) + def commit(id = 'HEAD') return nil unless raw_repository commit = Gitlab::Git::Commit.find(raw_repository, id) commit = Commit.new(commit) if commit commit + rescue Rugged::OdbError + nil end - def commits(ref, path = nil, limit = nil, offset = nil) + def commits(ref, path = nil, limit = nil, offset = nil, skip_merges = false) commits = Gitlab::Git::Commit.where( repo: raw_repository, ref: ref, @@ -59,25 +61,29 @@ class Repository end def add_branch(branch_name, ref) - Rails.cache.delete(cache_key(:branch_names)) + cache.expire(:branch_names) + @branches = nil gitlab_shell.add_branch(path_with_namespace, branch_name, ref) end - def add_tag(tag_name, ref) - Rails.cache.delete(cache_key(:tag_names)) + def add_tag(tag_name, ref, message = nil) + cache.expire(:tag_names) + @tags = nil - gitlab_shell.add_tag(path_with_namespace, tag_name, ref) + gitlab_shell.add_tag(path_with_namespace, tag_name, ref, message) end def rm_branch(branch_name) - Rails.cache.delete(cache_key(:branch_names)) + cache.expire(:branch_names) + @branches = nil gitlab_shell.rm_branch(path_with_namespace, branch_name) end def rm_tag(tag_name) - Rails.cache.delete(cache_key(:tag_names)) + cache.expire(:tag_names) + @tags = nil gitlab_shell.rm_tag(path_with_namespace, tag_name) end @@ -95,19 +101,15 @@ class Repository end def branch_names - Rails.cache.fetch(cache_key(:branch_names)) do - raw_repository.branch_names - end + cache.fetch(:branch_names) { raw_repository.branch_names } end def tag_names - Rails.cache.fetch(cache_key(:tag_names)) do - raw_repository.tag_names - end + cache.fetch(:tag_names) { raw_repository.tag_names } end def commit_count - Rails.cache.fetch(cache_key(:commit_count)) do + cache.fetch(:commit_count) do begin raw_repository.commit_count(self.root_ref) rescue @@ -119,35 +121,45 @@ class Repository # Return repo size in megabytes # Cached in redis def size - Rails.cache.fetch(cache_key(:size)) do - raw_repository.size - end + cache.fetch(:size) { raw_repository.size } end def expire_cache - Rails.cache.delete(cache_key(:size)) - Rails.cache.delete(cache_key(:branch_names)) - Rails.cache.delete(cache_key(:tag_names)) - Rails.cache.delete(cache_key(:commit_count)) - Rails.cache.delete(cache_key(:graph_log)) - Rails.cache.delete(cache_key(:readme)) - Rails.cache.delete(cache_key(:version)) - Rails.cache.delete(cache_key(:contribution_guide)) - end - - def graph_log - Rails.cache.fetch(cache_key(:graph_log)) do - stats = Gitlab::Git::GitStats.new(raw, root_ref, Gitlab.config.git.timeout) - stats.parsed_log + %i(size branch_names tag_names commit_count graph_log + readme version contribution_guide changelog license).each do |key| + cache.expire(key) end end - def cache_key(type) - "#{type}:#{path_with_namespace}" + def graph_log + cache.fetch(:graph_log) do + commits = raw_repository.log(limit: 6000, skip_merges: true, + ref: root_ref) + + commits.map do |rugged_commit| + commit = Gitlab::Git::Commit.new(rugged_commit) + + { + author_name: commit.author_name, + author_email: commit.author_email, + additions: commit.stats.additions, + deletions: commit.stats.deletions, + } + end + end + end + + def lookup_cache + @lookup_cache ||= {} end def method_missing(m, *args, &block) - raw_repository.send(m, *args, &block) + if m == :lookup && !block_given? + lookup_cache[m] ||= {} + lookup_cache[m][args.join(":")] ||= raw_repository.send(m, *args, &block) + else + raw_repository.send(m, *args, &block) + end end def respond_to?(method) @@ -165,13 +177,11 @@ class Repository end def readme - Rails.cache.fetch(cache_key(:readme)) do - tree(:head).readme - end + cache.fetch(:readme) { tree(:head).readme } end def version - Rails.cache.fetch(cache_key(:version)) do + cache.fetch(:version) do tree(:head).blobs.find do |file| file.name.downcase == 'version' end @@ -179,18 +189,44 @@ class Repository end def contribution_guide - Rails.cache.fetch(cache_key(:contribution_guide)) do - tree(:head).contribution_guide + cache.fetch(:contribution_guide) do + tree(:head).blobs.find do |file| + file.contributing? + end + end + end + + def changelog + cache.fetch(:changelog) do + tree(:head).blobs.find do |file| + file.name =~ /\A(changelog|history)/i + end + end + end + + def license + cache.fetch(:license) do + tree(:head).blobs.find do |file| + file.name =~ /\Alicense/i + end end end def head_commit - commit(self.root_ref) + @head_commit ||= commit(self.root_ref) + end + + def head_tree + @head_tree ||= Tree.new(self, head_commit.sha, nil) end def tree(sha = :head, path = nil) if sha == :head - sha = head_commit.sha + if path.nil? + return head_tree + else + sha = head_commit.sha + end end Tree.new(self, sha, path) @@ -223,12 +259,18 @@ class Repository end def last_commit_for_path(sha, path) - commits(sha, path, 1).last + args = %W(git rev-list --max-count=1 #{sha} -- #{path}) + sha = Gitlab::Popen.popen(args, path_to_repo).first.strip + commit(sha) end # Remove archives older than 2 hours def clean_old_archives - Gitlab::Popen.popen(%W(find #{Gitlab.config.gitlab.repository_downloads_path} -mmin +120 -delete)) + repository_downloads_path = Gitlab.config.gitlab.repository_downloads_path + + return unless File.directory?(repository_downloads_path) + + Gitlab::Popen.popen(%W(find #{repository_downloads_path} -not -path #{repository_downloads_path} -mmin +120 -delete)) end def branches_sorted_by(value) @@ -247,20 +289,18 @@ class Repository end def contributors - log = graph_log.group_by { |i| i[:author_email] } + commits = self.commits(nil, nil, 2000, 0, true) - log.map do |email, contributions| + commits.group_by(&:author_email).map do |email, commits| contributor = Gitlab::Contributor.new contributor.email = email - contributions.each do |contribution| + commits.each do |commit| if contributor.name.blank? - contributor.name = contribution[:author_name] + contributor.name = commit.author_name end contributor.commits += 1 - contributor.additions += contribution[:additions] || 0 - contributor.deletions += contribution[:deletions] || 0 end contributor @@ -282,4 +322,56 @@ class Repository blob_at(commit.parent_id, diff.old_path) end end + + def branch_names_contains(sha) + args = %W(git branch --contains #{sha}) + names = Gitlab::Popen.popen(args, path_to_repo).first + + if names.respond_to?(:split) + names = names.split("\n").map(&:strip) + + names.each do |name| + name.slice! '* ' + end + + names + else + [] + end + end + + def tag_names_contains(sha) + args = %W(git tag --contains #{sha}) + names = Gitlab::Popen.popen(args, path_to_repo).first + + if names.respond_to?(:split) + names = names.split("\n").map(&:strip) + + names.each do |name| + name.slice! '* ' + end + + names + else + [] + end + end + + def branches + @branches ||= raw_repository.branches + end + + def tags + @tags ||= raw_repository.tags + end + + def root_ref + @root_ref ||= raw_repository.root_ref + end + + private + + def cache + @cache ||= RepositoryCache.new(path_with_namespace) + end end diff --git a/app/models/service.rb b/app/models/service.rb index 0dc6d514b4..393cf55a69 100644 --- a/app/models/service.rb +++ b/app/models/service.rb @@ -2,39 +2,65 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# token :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# project_url :string(255) -# subdomain :string(255) -# room :string(255) -# recipients :text -# api_key :string(255) +# 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) # # To add new service you should build a class inherited from Service # and implement a set of methods class Service < ActiveRecord::Base + include Sortable + serialize :properties, JSON + default_value_for :active, false + default_value_for :push_events, true + default_value_for :issues_events, true + default_value_for :merge_requests_events, true + default_value_for :tag_push_events, true + default_value_for :note_events, true + + after_initialize :initialize_properties belongs_to :project has_one :service_hook - validates :project_id, presence: true + validates :project_id, presence: true, unless: Proc.new { |service| service.template? } + + scope :visible, -> { where.not(type: 'GitlabIssueTrackerService') } + + scope :push_hooks, -> { where(push_events: true, active: true) } + scope :tag_push_hooks, -> { where(tag_push_events: true, active: true) } + scope :issue_hooks, -> { where(issues_events: true, active: true) } + scope :merge_request_hooks, -> { where(merge_requests_events: true, active: true) } + scope :note_hooks, -> { where(note_events: true, active: true) } def activated? active end + def template? + template + end + def category :common end + def initialize_properties + self.properties = {} if properties.nil? + end + def title # implement inside child end @@ -56,6 +82,10 @@ class Service < ActiveRecord::Base [] end + def supported_events + %w(push tag_push issue merge_request) + end + def execute # implement inside child end @@ -63,4 +93,61 @@ class Service < ActiveRecord::Base def can_test? !project.empty_repo? end + + # Provide convenient accessor methods + # for each serialized property. + def self.prop_accessor(*args) + args.each do |arg| + class_eval %{ + def #{arg} + properties['#{arg}'] + end + + def #{arg}=(value) + self.properties['#{arg}'] = value + end + } + end + end + + def async_execute(data) + return unless supported_events.include?(data[:object_kind]) + + Sidekiq::Client.enqueue(ProjectServiceWorker, id, data) + end + + def issue_tracker? + self.category == :issue_tracker + end + + def self.available_services_names + %w( + asana + assembla + bamboo + buildkite + campfire + custom_issue_tracker + emails_on_push + external_wiki + flowdock + gemnasium + gitlab_ci + hipchat + irker + jira + pivotaltracker + pushover + redmine + slack + teamcity + ) + end + + def self.create_from_template(project_id, template) + service = template.dup + service.template = false + service.project_id = project_id + service if service.save + end end diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 2c38e7939b..b35e72c4bd 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -2,23 +2,25 @@ # # 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) -# expires_at :datetime -# private :boolean default(TRUE), not null -# type :string(255) +# 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) +# expires_at :datetime +# type :string(255) +# visibility_level :integer default(0), not null # class Snippet < ActiveRecord::Base + include Sortable include Linguist::BlobHelper + include Gitlab::VisibilityLevel - default_value_for :private, true + default_value_for :visibility_level, Snippet::PRIVATE belongs_to :author, class_name: "User" @@ -28,12 +30,19 @@ class Snippet < ActiveRecord::Base validates :author, presence: true validates :title, presence: true, length: { within: 0..255 } - validates :file_name, presence: true, length: { within: 0..255 } + validates :file_name, + presence: true, + length: { within: 0..255 }, + format: { with: Gitlab::Regex.file_name_regex, + message: Gitlab::Regex.file_name_regex_message } validates :content, presence: true + validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values } # Scopes - scope :are_public, -> { where(private: false) } - scope :are_private, -> { where(private: true) } + scope :are_internal, -> { where(visibility_level: Snippet::INTERNAL) } + scope :are_private, -> { where(visibility_level: Snippet::PRIVATE) } + scope :are_public, -> { where(visibility_level: Snippet::PUBLIC) } + scope :public_and_internal, -> { where(visibility_level: [Snippet::PUBLIC, Snippet::INTERNAL]) } scope :fresh, -> { order("created_at DESC") } scope :expired, -> { where(["expires_at IS NOT NULL AND expires_at < ?", Time.current]) } scope :non_expired, -> { where(["expires_at IS NULL OR expires_at > ?", Time.current]) } @@ -50,6 +59,10 @@ class Snippet < ActiveRecord::Base content end + def hook_attrs + attributes + end + def size 0 end @@ -58,6 +71,10 @@ class Snippet < ActiveRecord::Base file_name end + def sanitized_file_name + file_name.gsub(/[^a-zA-Z0-9_\-\.]+/, '') + end + def mode nil end @@ -65,4 +82,22 @@ class Snippet < ActiveRecord::Base def expired? expires_at && expires_at < Time.current end + + def visibility_level_field + visibility_level + end + + class << self + def search(query) + where('(title LIKE :query OR file_name LIKE :query)', query: "%#{query}%") + end + + def search_code(query) + where('(content LIKE :query)', query: "%#{query}%") + end + + def accessible_to(user) + where('visibility_level IN (?) OR author_id = ?', [Snippet::INTERNAL, Snippet::PUBLIC], user) + end + end end diff --git a/app/models/subscription.rb b/app/models/subscription.rb new file mode 100644 index 0000000000..dd75d3ab8b --- /dev/null +++ b/app/models/subscription.rb @@ -0,0 +1,21 @@ +# == Schema Information +# +# Table name: subscriptions +# +# id :integer not null, primary key +# user_id :integer +# subscribable_id :integer +# subscribable_type :string(255) +# subscribed :boolean +# created_at :datetime +# updated_at :datetime +# + +class Subscription < ActiveRecord::Base + belongs_to :user + belongs_to :subscribable, polymorphic: true + + validates :user_id, + uniqueness: { scope: [:subscribable_id, :subscribable_type] }, + presence: true +end diff --git a/app/models/tree.rb b/app/models/tree.rb index 07c9a825e2..f279e896cd 100644 --- a/app/models/tree.rb +++ b/app/models/tree.rb @@ -1,38 +1,38 @@ class Tree include Gitlab::MarkdownHelper - attr_accessor :entries, :readme, :contribution_guide + attr_accessor :repository, :sha, :path, :entries def initialize(repository, sha, path = '/') path = '/' if path.blank? + + @repository = repository + @sha = sha + @path = path + + git_repo = @repository.raw_repository + @entries = Gitlab::Git::Tree.where(git_repo, @sha, @path) + end + + def readme + return @readme if defined?(@readme) + + available_readmes = blobs.select(&:readme?) + + if available_readmes.count == 0 + return @readme = nil + end + + # Take the first previewable readme, or the first available readme, if we + # can't preview any of them + readme_tree = available_readmes.find do |readme| + previewable?(readme.name) + end || available_readmes.first + + readme_path = path == '/' ? readme_tree.name : File.join(path, readme_tree.name) + git_repo = repository.raw_repository - @entries = Gitlab::Git::Tree.where(git_repo, sha, path) - - available_readmes = @entries.select(&:readme?) - - if available_readmes.count > 0 - # If there is more than 1 readme in tree, find readme which is supported - # by markup renderer. - if available_readmes.length > 1 - supported_readmes = available_readmes.select do |readme| - gitlab_markdown?(readme.name) || markup?(readme.name) - end - - # Take the first supported readme, or the first available readme, if we - # don't support any of them - readme_tree = supported_readmes.first || available_readmes.first - else - readme_tree = available_readmes.first - end - - readme_path = path == '/' ? readme_tree.name : File.join(path, readme_tree.name) - @readme = Gitlab::Git::Blob.find(git_repo, sha, readme_path) - end - - if contribution_tree = @entries.find(&:contributing?) - contribution_path = path == '/' ? contribution_tree.name : File.join(path, contribution_tree.name) - @contribution_guide = Gitlab::Git::Blob.find(git_repo, sha, contribution_path) - end + @readme = Gitlab::Git::Blob.find(git_repo, sha, readme_path) end def trees diff --git a/app/models/user.rb b/app/models/user.rb index f1ff76edd1..d6b93afe73 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -2,63 +2,71 @@ # # 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 -# extern_uid :string(255) -# provider :string(255) -# 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 +# 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 +# github_access_token :string(255) +# gitlab_access_token :string(255) +# notification_email :string(255) +# hide_no_password :boolean default(FALSE) +# password_automatically_set :boolean default(FALSE) +# bitbucket_access_token :string(255) +# bitbucket_access_token_secret :string(255) +# public_email :string(255) default(""), not null # require 'carrierwave/orm/activerecord' require 'file_size_validator' class User < ActiveRecord::Base + include Sortable include Gitlab::ConfigHelper - extend Gitlab::ConfigHelper include TokenAuthenticatable + extend Gitlab::ConfigHelper + include Gitlab::CurrentSettings default_value_for :admin, false default_value_for :can_create_group, gitlab_config.default_can_create_group default_value_for :can_create_team, false default_value_for :hide_no_ssh_key, false - default_value_for :projects_limit, gitlab_config.default_projects_limit + default_value_for :hide_no_password, false default_value_for :theme_id, gitlab_config.default_theme devise :database_authenticatable, :lockable, :async, @@ -79,56 +87,68 @@ class User < ActiveRecord::Base # Profile has_many :keys, dependent: :destroy has_many :emails, dependent: :destroy + has_many :identities, dependent: :destroy # Groups - has_many :users_groups, dependent: :destroy - has_many :groups, through: :users_groups - has_many :owned_groups, -> { where users_groups: { group_access: UsersGroup::OWNER } }, through: :users_groups, source: :group - has_many :masters_groups, -> { where users_groups: { group_access: UsersGroup::MASTER } }, through: :users_groups, source: :group + has_many :members, dependent: :destroy + has_many :project_members, source: 'ProjectMember' + has_many :group_members, source: 'GroupMember' + has_many :groups, through: :group_members + has_many :owned_groups, -> { where members: { access_level: Gitlab::Access::OWNER } }, through: :group_members, source: :group + has_many :masters_groups, -> { where members: { access_level: Gitlab::Access::MASTER } }, through: :group_members, source: :group # Projects has_many :groups_projects, through: :groups, source: :projects has_many :personal_projects, through: :namespace, source: :projects - has_many :projects, through: :users_projects + has_many :projects, through: :project_members has_many :created_projects, foreign_key: :creator_id, class_name: 'Project' has_many :users_star_projects, dependent: :destroy has_many :starred_projects, through: :users_star_projects, source: :project has_many :snippets, dependent: :destroy, foreign_key: :author_id, class_name: "Snippet" - has_many :users_projects, dependent: :destroy + has_many :project_members, dependent: :destroy, class_name: 'ProjectMember' has_many :issues, dependent: :destroy, foreign_key: :author_id has_many :notes, dependent: :destroy, foreign_key: :author_id has_many :merge_requests, dependent: :destroy, foreign_key: :author_id has_many :events, dependent: :destroy, foreign_key: :author_id, class_name: "Event" + has_many :subscriptions, dependent: :destroy has_many :recent_events, -> { order "id DESC" }, foreign_key: :author_id, class_name: "Event" has_many :assigned_issues, dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue" has_many :assigned_merge_requests, dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest" + has_many :oauth_applications, class_name: 'Doorkeeper::Application', as: :owner, dependent: :destroy # # Validations # validates :name, presence: true - validates :email, presence: true, email: {strict_mode: true}, uniqueness: true + validates :email, presence: true, email: { strict_mode: true }, uniqueness: true + validates :notification_email, presence: true, email: { strict_mode: true } + validates :public_email, presence: true, email: { strict_mode: true }, allow_blank: true, uniqueness: true validates :bio, length: { maximum: 255 }, allow_blank: true - validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider} - validates :projects_limit, presence: true, numericality: {greater_than_or_equal_to: 0} - validates :username, presence: true, uniqueness: { case_sensitive: false }, - exclusion: { in: Gitlab::Blacklist.path }, - format: { with: Gitlab::Regex.username_regex, - message: Gitlab::Regex.username_regex_message } + validates :projects_limit, presence: true, numericality: { greater_than_or_equal_to: 0 } + validates :username, + presence: true, + uniqueness: { case_sensitive: false }, + exclusion: { in: Gitlab::Blacklist.path }, + format: { with: Gitlab::Regex.namespace_regex, + message: Gitlab::Regex.namespace_regex_message } validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true validate :namespace_uniq, if: ->(user) { user.username_changed? } validate :avatar_type, if: ->(user) { user.avatar_changed? } validate :unique_email, if: ->(user) { user.email_changed? } - validates :avatar, file_size: { maximum: 100.kilobytes.to_i } + validate :owns_notification_email, if: ->(user) { user.notification_email_changed? } + validates :avatar, file_size: { maximum: 200.kilobytes.to_i } before_validation :generate_password, on: :create before_validation :sanitize_attrs + before_validation :set_notification_email, if: ->(user) { user.email_changed? } + before_validation :set_public_email, if: ->(user) { user.public_email_changed? } before_save :ensure_authentication_token after_save :ensure_namespace_correct + after_initialize :set_projects_limit after_create :post_create_hook after_destroy :post_destroy_hook @@ -138,24 +158,6 @@ class User < ActiveRecord::Base delegate :path, to: :namespace, allow_nil: true, prefix: true state_machine :state, initial: :active do - after_transition any => :blocked do |user, transition| - # Remove user from all projects and - user.users_projects.find_each do |membership| - # skip owned resources - next if membership.project.owner == user - - return false unless membership.destroy - end - - # Remove user from all groups - user.users_groups.find_each do |membership| - # skip owned resources - next if membership.group.last_owner?(user) - - return false unless membership.destroy - end - end - event :block do transition active: :blocked end @@ -165,20 +167,14 @@ class User < ActiveRecord::Base end end - mount_uploader :avatar, AttachmentUploader + mount_uploader :avatar, AvatarUploader # Scopes scope :admins, -> { where(admin: true) } scope :blocked, -> { with_state(:blocked) } scope :active, -> { with_state(:active) } - scope :alphabetically, -> { order('name ASC') } - scope :in_team, ->(team){ where(id: team.member_ids) } - scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) } scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : all } - scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)') } - scope :ldap, -> { where(provider: 'ldap') } - - scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active } + scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM members)') } # # Class methods @@ -194,6 +190,15 @@ class User < ActiveRecord::Base end end + def sort(method) + case method.to_s + when 'recent_sign_in' then reorder(last_sign_in_at: :desc) + when 'oldest_sign_in' then reorder(last_sign_in_at: :asc) + else + order_by(method) + end + end + def find_for_commit(email, name) # Prefer email match over name match User.where(email: email).first || @@ -201,7 +206,7 @@ class User < ActiveRecord::Base User.where(name: name).first end - def filter filter_name + def filter(filter_name) case filter_name when "admins"; self.admins when "blocked"; self.blocked @@ -211,10 +216,15 @@ class User < ActiveRecord::Base end end - def search query + def search(query) where("lower(name) LIKE :query OR lower(email) LIKE :query OR lower(username) LIKE :query", query: "%#{query.downcase}%") end + def by_login(login) + where('lower(username) = :value OR lower(email) = :value', + value: login.to_s.downcase).first + end + def by_username_or_id(name_or_id) where('users.username = ? OR users.id = ?', name_or_id.to_s, name_or_id.to_i).first end @@ -253,7 +263,8 @@ class User < ActiveRecord::Base def namespace_uniq namespace_name = self.username - if Namespace.find_by(path: namespace_name) + existing_namespace = Namespace.by_path(namespace_name) + if existing_namespace && existing_namespace != self.namespace self.errors.add :username, "already exists" end end @@ -268,11 +279,15 @@ class User < ActiveRecord::Base self.errors.add(:email, 'has already been taken') if Email.exists?(email: self.email) end + def owns_notification_email + self.errors.add(:notification_email, "is not an email you own") unless self.all_emails.include?(self.notification_email) + end + # Groups user has access to def authorized_groups @authorized_groups ||= begin group_ids = (groups.pluck(:id) + authorized_projects.pluck(:namespace_id)) - Group.where(id: group_ids).order('namespaces.name ASC') + Group.where(id: group_ids) end end @@ -281,9 +296,9 @@ class User < ActiveRecord::Base def authorized_projects @authorized_projects ||= begin project_ids = personal_projects.pluck(:id) - project_ids += groups_projects.pluck(:id) - project_ids += projects.pluck(:id).uniq - Project.where(id: project_ids).joins(:namespace).order('namespaces.name ASC') + project_ids.push(*groups_projects.pluck(:id)) + project_ids.push(*projects.pluck(:id).uniq) + Project.where(id: project_ids) end end @@ -295,7 +310,7 @@ class User < ActiveRecord::Base # Team membership in authorized projects def tm_in_authorized_projects - UsersProject.where(project_id: authorized_projects.map(&:id), user_id: self.id) + ProjectMember.where(source_id: authorized_projects.map(&:id), user_id: self.id) end def is_admin? @@ -306,6 +321,10 @@ class User < ActiveRecord::Base keys.count == 0 end + def require_password? + password_automatically_set? && !ldap_user? + end + def can_change_username? gitlab_config.username_changing_enabled end @@ -319,18 +338,14 @@ class User < ActiveRecord::Base end def abilities - @abilities ||= begin - abilities = Six.new - abilities << Ability - abilities - end + Ability.abilities end def can_select_namespace? several_namespaces? || admin end - def can? action, subject + def can?(action, subject) abilities.allowed?(self, action, subject) end @@ -351,7 +366,7 @@ class User < ActiveRecord::Base (personal_projects.count.to_f / projects_limit) * 100 end - def recent_push project_id = nil + def recent_push(project_id = nil) # Get push events not earlier than 2 hours ago events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours) events = events.where(project_id: project_id) if project_id @@ -377,14 +392,14 @@ class User < ActiveRecord::Base end def tm_of(project) - project.team_member_by_id(self.id) + project.project_member_by_id(self.id) end - def already_forked? project + def already_forked?(project) !!fork_of(project) end - def fork_of project + def fork_of(project) links = ForkedProjectLink.where(forked_from_project_id: project, forked_to_project_id: personal_projects) if links.any? @@ -395,11 +410,23 @@ class User < ActiveRecord::Base end def ldap_user? - extern_uid && provider == 'ldap' + identities.exists?(["provider LIKE ? AND extern_uid IS NOT NULL", "ldap%"]) + end + + def ldap_identity + @ldap_identity ||= identities.find_by(["provider LIKE ?", "ldap%"]) + end + + def project_deploy_keys + DeployKey.in_projects(self.authorized_projects.pluck(:id)) end def accessible_deploy_keys - DeployKey.in_projects(self.authorized_projects.pluck(:id)).uniq + @accessible_deploy_keys ||= begin + key_ids = project_deploy_keys.pluck(:id) + key_ids.push(*DeployKey.are_public.pluck(:id)) + DeployKey.where(id: key_ids) + end end def created_by @@ -413,6 +440,25 @@ class User < ActiveRecord::Base end end + def set_notification_email + if self.notification_email.blank? || !self.all_emails.include?(self.notification_email) + self.notification_email = self.email + end + end + + def set_public_email + if self.public_email.blank? || !self.all_emails.include?(self.public_email) + self.public_email = '' + end + end + + def set_projects_limit + connection_default_value_defined = new_record? && !projects_limit_changed? + return unless self.projects_limit.nil? || connection_default_value_defined + + self.projects_limit = current_application_settings.default_projects_limit + end + def requires_ldap_check? if !Gitlab.config.ldap.enabled false @@ -457,13 +503,13 @@ class User < ActiveRecord::Base end def full_website_url - return "http://#{website_url}" if website_url !~ /^https?:\/\// + return "http://#{website_url}" if website_url !~ /\Ahttps?:\/\// website_url end def short_website_url - website_url.gsub(/https?:\/\//, '') + website_url.sub(/\Ahttps?:\/\//, '') end def all_ssh_keys @@ -471,11 +517,7 @@ class User < ActiveRecord::Base end def temp_oauth_email? - email =~ /\Atemp-email-for-oauth/ - end - - def generate_tmp_oauth_email - self.email = "temp-email-for-oauth-#{username}@gitlab.localhost" + email.start_with?('temp-email-for-oauth') end def public_profile? @@ -484,12 +526,24 @@ class User < ActiveRecord::Base def avatar_url(size = nil) if avatar.present? - [gitlab_config.url, avatar.url].join("/") + [gitlab_config.url, avatar.url].join else GravatarService.new.execute(email, size) end end + def all_emails + [self.email, *self.emails.map(&:email)] + end + + def hook_attrs + { + name: name, + username: username, + avatar_url: avatar_url + } + end + def ensure_namespace_correct # Ensure user has namespace self.create_namespace!(path: self.username, name: self.username) unless self.namespace @@ -501,7 +555,7 @@ class User < ActiveRecord::Base def post_create_hook log_info("User \"#{self.name}\" (#{self.email}) was created") - notification_service.new_user(self, @reset_token) + notification_service.new_user(self, @reset_token) if self.created_by_id system_hook_service.execute_hooks_for(self, :create) end @@ -514,7 +568,7 @@ class User < ActiveRecord::Base NotificationService.new end - def log_info message + def log_info(message) Gitlab::AppLogger.info message end @@ -535,4 +589,26 @@ class User < ActiveRecord::Base UsersStarProject.create!(project: project, user: self) end end + + def manageable_namespaces + @manageable_namespaces ||= + begin + namespaces = [] + namespaces << namespace + namespaces += owned_groups + namespaces += masters_groups + end + end + + def oauth_authorized_tokens + Doorkeeper::AccessToken.where(resource_owner_id: self.id, revoked_at: nil) + end + + def contributed_projects_ids + Event.contributions.where(author_id: self). + where("created_at > ?", Time.now - 1.year). + reorder(project_id: :desc). + select(:project_id). + uniq.map(&:project_id) + end end diff --git a/app/models/users_group.rb b/app/models/users_group.rb deleted file mode 100644 index 270f968ef6..0000000000 --- a/app/models/users_group.rb +++ /dev/null @@ -1,61 +0,0 @@ -# == Schema Information -# -# Table name: users_groups -# -# id :integer not null, primary key -# group_access :integer not null -# group_id :integer not null -# user_id :integer not null -# created_at :datetime -# updated_at :datetime -# notification_level :integer default(3), not null -# - -class UsersGroup < ActiveRecord::Base - include Notifiable - include Gitlab::Access - - def self.group_access_roles - Gitlab::Access.options_with_owner - end - - belongs_to :user - belongs_to :group - - scope :guests, -> { where(group_access: GUEST) } - scope :reporters, -> { where(group_access: REPORTER) } - scope :developers, -> { where(group_access: DEVELOPER) } - scope :masters, -> { where(group_access: MASTER) } - scope :owners, -> { where(group_access: OWNER) } - - scope :with_group, ->(group) { where(group_id: group.id) } - scope :with_user, ->(user) { where(user_id: user.id) } - - after_create :notify_create - after_update :notify_update - - validates :group_access, inclusion: { in: UsersGroup.group_access_roles.values }, presence: true - validates :user_id, presence: true - validates :group_id, presence: true - validates :user_id, uniqueness: { scope: [:group_id], message: "already exists in group" } - - delegate :name, :username, :email, to: :user, prefix: true - - def access_field - group_access - end - - def notify_create - notification_service.new_group_member(self) - end - - def notify_update - if group_access_changed? - notification_service.update_group_member(self) - end - end - - def notification_service - NotificationService.new - end -end diff --git a/app/models/users_project.rb b/app/models/users_project.rb deleted file mode 100644 index 60bdf7a3cf..0000000000 --- a/app/models/users_project.rb +++ /dev/null @@ -1,152 +0,0 @@ -# == Schema Information -# -# Table name: users_projects -# -# id :integer not null, primary key -# user_id :integer not null -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# project_access :integer default(0), not null -# notification_level :integer default(3), not null -# - -class UsersProject < ActiveRecord::Base - include Gitlab::ShellAdapter - include Notifiable - include Gitlab::Access - - belongs_to :user - belongs_to :project - - validates :user, presence: true - validates :user_id, uniqueness: { scope: [:project_id], message: "already exists in project" } - validates :project_access, inclusion: { in: Gitlab::Access.values }, presence: true - validates :project, presence: true - - delegate :name, :username, :email, to: :user, prefix: true - - scope :guests, -> { where(project_access: GUEST) } - scope :reporters, -> { where(project_access: REPORTER) } - scope :developers, -> { where(project_access: DEVELOPER) } - scope :masters, -> { where(project_access: MASTER) } - - scope :in_project, ->(project) { where(project_id: project.id) } - scope :in_projects, ->(projects) { where(project_id: projects.map { |p| p.id }) } - scope :with_user, ->(user) { where(user_id: user.id) } - - after_create :post_create_hook - after_update :post_update_hook - after_destroy :post_destroy_hook - - class << self - - # Add users to project teams with passed access option - # - # access can be an integer representing a access code - # or symbol like :master representing role - # - # Ex. - # add_users_into_projects( - # project_ids, - # user_ids, - # UsersProject::MASTER - # ) - # - # add_users_into_projects( - # project_ids, - # user_ids, - # :master - # ) - # - def add_users_into_projects(project_ids, user_ids, access) - project_access = if roles_hash.has_key?(access) - roles_hash[access] - elsif roles_hash.values.include?(access.to_i) - access - else - raise "Non valid access" - end - - UsersProject.transaction do - project_ids.each do |project_id| - user_ids.each do |user_id| - users_project = UsersProject.new(project_access: project_access, user_id: user_id) - users_project.project_id = project_id - users_project.save - end - end - end - - true - rescue - false - end - - def truncate_teams(project_ids) - UsersProject.transaction do - users_projects = UsersProject.where(project_id: project_ids) - users_projects.each do |users_project| - users_project.destroy - end - end - - true - rescue - false - end - - def truncate_team project - truncate_teams [project.id] - end - - def roles_hash - Gitlab::Access.sym_options - end - - def access_roles - Gitlab::Access.options - end - end - - def access_field - project_access - end - - def owner? - project.owner == user - end - - def post_create_hook - Event.create( - project_id: self.project.id, - action: Event::JOINED, - author_id: self.user.id - ) - - notification_service.new_team_member(self) unless owner? - system_hook_service.execute_hooks_for(self, :create) - end - - def post_update_hook - notification_service.update_team_member(self) if self.project_access_changed? - end - - def post_destroy_hook - Event.create( - project_id: self.project.id, - action: Event::LEFT, - author_id: self.user.id - ) - - system_hook_service.execute_hooks_for(self, :destroy) - end - - def notification_service - NotificationService.new - end - - def system_hook_service - SystemHooksService.new - end -end diff --git a/app/models/users_star_project.rb b/app/models/users_star_project.rb index 80e756bd00..3d49cb0594 100644 --- a/app/models/users_star_project.rb +++ b/app/models/users_star_project.rb @@ -2,11 +2,11 @@ # # Table name: users_star_projects # -# id :integer not null, primary key -# starrer_id :integer not null -# project_id :integer not null -# created_at :datetime -# updated_at :datetime +# 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 diff --git a/app/models/wiki_page.rb b/app/models/wiki_page.rb index b8a0a9eb58..e9413c34ba 100644 --- a/app/models/wiki_page.rb +++ b/app/models/wiki_page.rb @@ -43,7 +43,7 @@ class WikiPage @attributes[:slug] end - alias :to_param :slug + alias_method :to_param, :slug # The formatted title of this page. def title @@ -87,14 +87,14 @@ class WikiPage def version return nil unless persisted? - @version ||= Commit.new(Gitlab::Git::Commit.new(@page.version)) + @version ||= @page.version end # Returns an array of Gitlab Commit instances. def versions return [] unless persisted? - @page.versions.map { |v| Commit.new(Gitlab::Git::Commit.new(v)) } + @page.versions end def commit @@ -179,7 +179,8 @@ class WikiPage if valid? && project_wiki.send(method, *args) page_details = if method == :update_page - @page.path + # Use url_path instead of path to omit format extension + @page.url_path else title end diff --git a/app/services/archive_repository_service.rb b/app/services/archive_repository_service.rb index 8823f6fdc6..e1b41527d8 100644 --- a/app/services/archive_repository_service.rb +++ b/app/services/archive_repository_service.rb @@ -1,14 +1,62 @@ class ArchiveRepositoryService - def execute(project, ref, format) - storage_path = Gitlab.config.gitlab.repository_downloads_path + attr_reader :project, :ref, :format - unless File.directory?(storage_path) - FileUtils.mkdir_p(storage_path) + def initialize(project, ref, format) + format ||= 'tar.gz' + @project, @ref, @format = project, ref, format.downcase + end + + def execute(options = {}) + project.repository.clean_old_archives + + raise "No archive file path" unless file_path + + return file_path if archived? + + unless archiving? + RepositoryArchiveWorker.perform_async(project.id, ref, format) end - format ||= 'tar.gz' - repository = project.repository - repository.clean_old_archives - repository.archive_repo(ref, storage_path, format.downcase) + archived = wait_until_archived(options[:timeout] || 5.0) + + file_path if archived + end + + private + + def storage_path + Gitlab.config.gitlab.repository_downloads_path + end + + def file_path + @file_path ||= project.repository.archive_file_path(ref, storage_path, format) + end + + def pid_file_path + @pid_file_path ||= project.repository.archive_pid_file_path(ref, storage_path, format) + end + + def archived? + File.exist?(file_path) + end + + def archiving? + File.exist?(pid_file_path) + end + + def wait_until_archived(timeout = 5.0) + return archived? if timeout == 0.0 + + t1 = Time.now + + begin + sleep 0.1 + + success = archived? + + t2 = Time.now + end until success || t2 - t1 >= timeout + + success end end diff --git a/app/services/base_service.rb b/app/services/base_service.rb index 31b38aca53..6d9ed34591 100644 --- a/app/services/base_service.rb +++ b/app/services/base_service.rb @@ -1,16 +1,14 @@ class BaseService + include Gitlab::CurrentSettings + attr_accessor :project, :current_user, :params - def initialize(project, user, params) + def initialize(project, user, params = {}) @project, @current_user, @params = project, user, params.dup end def abilities - @abilities ||= begin - abilities = Six.new - abilities << Ability - abilities - end + Ability.abilities end def can?(object, action, subject) @@ -25,11 +23,44 @@ class BaseService EventCreateService.new end - def log_info message + def log_info(message) Gitlab::AppLogger.info message end def system_hook_service SystemHooksService.new end + + # Add an error to the specified model for restricted visibility levels + def deny_visibility_level(model, denied_visibility_level = nil) + denied_visibility_level ||= model.visibility_level + + level_name = 'Unknown' + Gitlab::VisibilityLevel.options.each do |name, level| + level_name = name if level == denied_visibility_level + end + + model.errors.add( + :visibility_level, + "#{level_name} visibility has been restricted by your GitLab administrator" + ) + end + + private + + def error(message, http_status = nil) + result = { + message: message, + status: :error + } + + result[:http_status] = http_status if http_status + result + end + + def success + { + status: :success + } + end end diff --git a/app/services/compare_service.rb b/app/services/compare_service.rb index c5e0470291..6aa9df4b19 100644 --- a/app/services/compare_service.rb +++ b/app/services/compare_service.rb @@ -4,8 +4,8 @@ class CompareService def execute(current_user, source_project, source_branch, target_project, target_branch) # Try to compare branches to get commits list and diffs # - # Note: Use satellite only when need to compare between to repos - # because satellites are slower then operations on bare repo + # Note: Use satellite only when need to compare between two repos + # because satellites are slower than operations on bare repo if target_project == source_project Gitlab::CompareResult.new( Gitlab::Git::Compare.new( diff --git a/app/services/create_branch_service.rb b/app/services/create_branch_service.rb index 98beeee835..cf7ae4345f 100644 --- a/app/services/create_branch_service.rb +++ b/app/services/create_branch_service.rb @@ -1,13 +1,42 @@ -class CreateBranchService - def execute(project, branch_name, ref, current_user) +require_relative 'base_service' + +class CreateBranchService < BaseService + def execute(branch_name, ref) + valid_branch = Gitlab::GitRefValidator.validate(branch_name) + if valid_branch == false + return error('Branch name invalid') + end + repository = project.repository + existing_branch = repository.find_branch(branch_name) + if existing_branch + return error('Branch already exists') + end + repository.add_branch(branch_name, ref) new_branch = repository.find_branch(branch_name) if new_branch - Event.create_ref_event(project, current_user, new_branch, 'add') - end + push_data = build_push_data(project, current_user, new_branch) - new_branch + EventCreateService.new.push(project, current_user, push_data) + project.execute_hooks(push_data.dup, :push_hooks) + project.execute_services(push_data.dup, :push_hooks) + + success(new_branch) + else + error('Invalid reference name') + end + end + + def success(branch) + out = super() + out[:branch] = branch + out + end + + def build_push_data(project, user, branch) + Gitlab::PushDataBuilder. + build(project, user, Gitlab::Git::BLANK_SHA, branch.target, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", []) end end diff --git a/app/services/create_snippet_service.rb b/app/services/create_snippet_service.rb new file mode 100644 index 0000000000..101a3df5ee --- /dev/null +++ b/app/services/create_snippet_service.rb @@ -0,0 +1,20 @@ +class CreateSnippetService < BaseService + def execute + if project.nil? + snippet = PersonalSnippet.new(params) + else + snippet = project.snippets.build(params) + end + + unless Gitlab::VisibilityLevel.allowed_for?(current_user, + params[:visibility_level]) + deny_visibility_level(snippet) + return snippet + end + + snippet.author = current_user + + snippet.save + snippet + end +end diff --git a/app/services/create_tag_service.rb b/app/services/create_tag_service.rb index 9776667740..25f9e20324 100644 --- a/app/services/create_tag_service.rb +++ b/app/services/create_tag_service.rb @@ -1,13 +1,45 @@ -class CreateTagService - def execute(project, tag_name, ref, current_user) +require_relative 'base_service' + +class CreateTagService < BaseService + def execute(tag_name, ref, message) + valid_tag = Gitlab::GitRefValidator.validate(tag_name) + if valid_tag == false + return error('Tag name invalid') + end + repository = project.repository - repository.add_tag(tag_name, ref) + existing_tag = repository.find_tag(tag_name) + if existing_tag + return error('Tag already exists') + end + + message.strip! if message + + repository.add_tag(tag_name, ref, message) new_tag = repository.find_tag(tag_name) if new_tag - Event.create_ref_event(project, current_user, new_tag, 'add', 'refs/tags') - end + push_data = create_push_data(project, current_user, new_tag) - new_tag + EventCreateService.new.push(project, current_user, push_data) + project.execute_hooks(push_data.dup, :tag_push_hooks) + project.execute_services(push_data.dup, :tag_push_hooks) + + success(new_tag) + else + error('Invalid reference name') + end + end + + def success(branch) + out = super() + out[:tag] = branch + out + end + + def create_push_data(project, user, tag) + commits = [project.repository.commit(tag.target)].compact + Gitlab::PushDataBuilder. + build(project, user, Gitlab::Git::BLANK_SHA, tag.target, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", commits, tag.message) end end diff --git a/app/services/delete_branch_service.rb b/app/services/delete_branch_service.rb index ce2d8093df..b19b112a0c 100644 --- a/app/services/delete_branch_service.rb +++ b/app/services/delete_branch_service.rb @@ -1,46 +1,56 @@ -class DeleteBranchService - def execute(project, branch_name, current_user) +require_relative 'base_service' + +class DeleteBranchService < BaseService + def execute(branch_name) repository = project.repository branch = repository.find_branch(branch_name) # No such branch unless branch - return error('No such branch') + return error('No such branch', 404) end if branch_name == repository.root_ref - return error('Cannot remove HEAD branch') + return error('Cannot remove HEAD branch', 405) end # Dont allow remove of protected branch if project.protected_branch?(branch_name) - return error('Protected branch cant be removed') + return error('Protected branch cant be removed', 405) end # Dont allow user to remove branch if he is not allowed to push unless current_user.can?(:push_code, project) - return error('You dont have push access to repo') + return error('You dont have push access to repo', 405) end if repository.rm_branch(branch_name) - Event.create_ref_event(project, current_user, branch, 'rm') + push_data = build_push_data(branch) + + EventCreateService.new.push(project, current_user, push_data) + project.execute_hooks(push_data.dup, :push_hooks) + project.execute_services(push_data.dup, :push_hooks) + success('Branch was removed') else - return error('Failed to remove branch') + error('Failed to remove branch') end end - def error(message) - { - message: message, - state: :error - } + def error(message, return_code = 400) + out = super(message) + out[:return_code] = return_code + out end def success(message) - { - message: message, - state: :success - } + out = super() + out[:message] = message + out + end + + def build_push_data(branch) + Gitlab::PushDataBuilder + .build(project, current_user, branch.target, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::BRANCH_REF_PREFIX}#{branch.name}", []) end end diff --git a/app/services/delete_tag_service.rb b/app/services/delete_tag_service.rb new file mode 100644 index 0000000000..0c83640113 --- /dev/null +++ b/app/services/delete_tag_service.rb @@ -0,0 +1,42 @@ +require_relative 'base_service' + +class DeleteTagService < BaseService + def execute(tag_name) + repository = project.repository + tag = repository.find_tag(tag_name) + + # No such tag + unless tag + return error('No such tag', 404) + end + + if repository.rm_tag(tag_name) + push_data = build_push_data(tag) + + EventCreateService.new.push(project, current_user, push_data) + project.execute_hooks(push_data.dup, :tag_push_hooks) + project.execute_services(push_data.dup, :tag_push_hooks) + + success('Tag was removed') + else + error('Failed to remove tag') + end + end + + def error(message, return_code = 400) + out = super(message) + out[:return_code] = return_code + out + end + + def success(message) + out = super() + out[:message] = message + out + end + + def build_push_data(tag) + Gitlab::PushDataBuilder + .build(project, current_user, tag.target, Gitlab::Git::BLANK_SHA, "#{Gitlab::Git::TAG_REF_PREFIX}#{tag.name}", []) + end +end diff --git a/app/services/event_create_service.rb b/app/services/event_create_service.rb index 8d8a5873e6..103d6b0a08 100644 --- a/app/services/event_create_service.rb +++ b/app/services/event_create_service.rb @@ -7,58 +7,78 @@ # class EventCreateService def open_issue(issue, current_user) - create_event(issue, current_user, Event::CREATED) + create_record_event(issue, current_user, Event::CREATED) end def close_issue(issue, current_user) - create_event(issue, current_user, Event::CLOSED) + create_record_event(issue, current_user, Event::CLOSED) end def reopen_issue(issue, current_user) - create_event(issue, current_user, Event::REOPENED) + create_record_event(issue, current_user, Event::REOPENED) end def open_mr(merge_request, current_user) - create_event(merge_request, current_user, Event::CREATED) + create_record_event(merge_request, current_user, Event::CREATED) end def close_mr(merge_request, current_user) - create_event(merge_request, current_user, Event::CLOSED) + create_record_event(merge_request, current_user, Event::CLOSED) end def reopen_mr(merge_request, current_user) - create_event(merge_request, current_user, Event::REOPENED) + create_record_event(merge_request, current_user, Event::REOPENED) end def merge_mr(merge_request, current_user) - create_event(merge_request, current_user, Event::MERGED) + create_record_event(merge_request, current_user, Event::MERGED) end def open_milestone(milestone, current_user) - create_event(milestone, current_user, Event::CREATED) + create_record_event(milestone, current_user, Event::CREATED) end def close_milestone(milestone, current_user) - create_event(milestone, current_user, Event::CLOSED) + create_record_event(milestone, current_user, Event::CLOSED) end def reopen_milestone(milestone, current_user) - create_event(milestone, current_user, Event::REOPENED) + create_record_event(milestone, current_user, Event::REOPENED) end def leave_note(note, current_user) - create_event(note, current_user, Event::COMMENTED) + create_record_event(note, current_user, Event::COMMENTED) + end + + def join_project(project, current_user) + create_event(project, current_user, Event::JOINED) + end + + def leave_project(project, current_user) + create_event(project, current_user, Event::LEFT) + end + + def create_project(project, current_user) + create_event(project, current_user, Event::CREATED) + end + + def push(project, current_user, push_data) + create_event(project, current_user, Event::PUSHED, data: push_data) end private - def create_event(record, current_user, status) - Event.create( - project: record.project, - target_id: record.id, - target_type: record.class.name, + def create_record_event(record, current_user, status) + create_event(record.project, current_user, status, target_id: record.id, target_type: record.class.name) + end + + def create_event(project, current_user, status, attributes = {}) + attributes.reverse_merge!( + project: project, action: status, author_id: current_user.id ) + + Event.create(attributes) end end diff --git a/app/services/files/base_service.rb b/app/services/files/base_service.rb index f1765d3897..bd24510095 100644 --- a/app/services/files/base_service.rb +++ b/app/services/files/base_service.rb @@ -10,20 +10,6 @@ module Files private - def error(message) - { - error: message, - status: :error - } - end - - def success - { - error: '', - status: :success - } - end - def repository project.repository end diff --git a/app/services/files/create_service.rb b/app/services/files/create_service.rb index 82e4d7b684..23833aa78e 100644 --- a/app/services/files/create_service.rb +++ b/app/services/files/create_service.rb @@ -3,41 +3,43 @@ require_relative "base_service" module Files class CreateService < BaseService def execute - allowed = if project.protected_branch?(ref) - can?(current_user, :push_code_to_protected_branches, project) - else - can?(current_user, :push_code, project) - end + allowed = Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref) unless allowed return error("You are not allowed to create file in this branch") end - unless repository.branch_names.include?(ref) - return error("You can only create files if you are on top of a branch") - end - file_name = File.basename(path) file_path = path - unless file_name =~ Gitlab::Regex.path_regex + unless file_name =~ Gitlab::Regex.file_name_regex return error( 'Your changes could not be committed, because the file name ' + - Gitlab::Regex.path_regex_message + Gitlab::Regex.file_name_regex_message ) end - blob = repository.blob_at_branch(ref, file_path) + if project.empty_repo? + # everything is ok because repo does not have a commits yet + else + unless repository.branch_names.include?(ref) + return error("You can only create files if you are on top of a branch") + end - if blob - return error("Your changes could not be committed, because file with such name exists") + blob = repository.blob_at_branch(ref, file_path) + + if blob + return error("Your changes could not be committed, because file with such name exists") + end end + new_file_action = Gitlab::Satellite::NewFileAction.new(current_user, project, ref, file_path) created_successfully = new_file_action.commit!( params[:content], params[:commit_message], - params[:encoding] + params[:encoding], + params[:new_branch] ) if created_successfully diff --git a/app/services/files/delete_service.rb b/app/services/files/delete_service.rb index ff5dc6ef34..1497a0f883 100644 --- a/app/services/files/delete_service.rb +++ b/app/services/files/delete_service.rb @@ -3,11 +3,7 @@ require_relative "base_service" module Files class DeleteService < BaseService def execute - allowed = if project.protected_branch?(ref) - can?(current_user, :push_code_to_protected_branches, project) - else - can?(current_user, :push_code, project) - end + allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref) unless allowed return error("You are not allowed to push into this branch") diff --git a/app/services/files/update_service.rb b/app/services/files/update_service.rb index a0f40154db..0724d3ae63 100644 --- a/app/services/files/update_service.rb +++ b/app/services/files/update_service.rb @@ -3,11 +3,7 @@ require_relative "base_service" module Files class UpdateService < BaseService def execute - allowed = if project.protected_branch?(ref) - can?(current_user, :push_code_to_protected_branches, project) - else - can?(current_user, :push_code, project) - end + allowed = ::Gitlab::GitAccess.new(current_user, project).can_push_to_branch?(ref) unless allowed return error("You are not allowed to push into this branch") @@ -24,17 +20,20 @@ module Files end edit_file_action = Gitlab::Satellite::EditFileAction.new(current_user, project, ref, path) - created_successfully = edit_file_action.commit!( + edit_file_action.commit!( params[:content], params[:commit_message], - params[:encoding] + params[:encoding], + params[:new_branch] ) - if created_successfully - success - else - error("Your changes could not be committed. Maybe the file was changed by another process or there was nothing to commit?") - end + success + rescue Gitlab::Satellite::CheckoutFailed => ex + error("Your changes could not be committed because ref '#{ref}' could not be checked out", 400) + rescue Gitlab::Satellite::CommitFailed => ex + error("Your changes could not be committed. Maybe there was nothing to commit?", 409) + rescue Gitlab::Satellite::PushFailed => ex + error("Your changes could not be committed. Maybe the file was changed by another process?", 409) end end end diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb index 715b569075..31e0167d24 100644 --- a/app/services/git_push_service.rb +++ b/app/services/git_push_service.rb @@ -1,5 +1,7 @@ class GitPushService attr_accessor :project, :user, :push_data, :push_commits + include Gitlab::CurrentSettings + include Gitlab::Access # This method will be called after each git update # and only if the provided user and project is present in GitLab. @@ -17,76 +19,66 @@ class GitPushService def execute(project, user, oldrev, newrev, ref) @project, @user = project, user - # Collect data for this git push - @push_commits = project.repository.commits_between(oldrev, newrev) - @push_data = post_receive_data(oldrev, newrev, ref) - - create_push_event - project.ensure_satellite_exists project.repository.expire_cache project.update_repository_size - if push_to_existing_branch?(ref, oldrev) - project.update_merge_requests(oldrev, newrev, ref, @user) - process_commit_messages(ref) - end - - if push_to_branch?(ref) - project.execute_hooks(@push_data.dup, :push_hooks) - project.execute_services(@push_data.dup) - end - - if push_to_new_branch?(ref, oldrev) + if push_remove_branch?(ref, newrev) + @push_commits = [] + elsif push_to_new_branch?(ref, oldrev) # Re-find the pushed commits. if is_default_branch?(ref) # Initial push to the default branch. Take the full history of that branch as "newly pushed". @push_commits = project.repository.commits(newrev) + + # Set protection on the default branch if configured + if (current_application_settings.default_branch_protection != PROTECTION_NONE) + developers_can_push = current_application_settings.default_branch_protection == PROTECTION_DEV_CAN_PUSH ? true : false + project.protected_branches.create({ name: project.default_branch, developers_can_push: developers_can_push }) + end else # Use the pushed commits that aren't reachable by the default branch # as a heuristic. This may include more commits than are actually pushed, but # that shouldn't matter because we check for existing cross-references later. @push_commits = project.repository.commits_between(project.default_branch, newrev) - end + # don't process commits for the initial push to the default branch + process_commit_messages(ref) + end + elsif push_to_existing_branch?(ref, oldrev) + # Collect data for this git push + @push_commits = project.repository.commits_between(oldrev, newrev) + project.update_merge_requests(oldrev, newrev, ref, @user) process_commit_messages(ref) end - end - # This method provide a sample data - # generated with post_receive_data method - # for given project - # - def sample_data(project, user) - @project, @user = project, user - @push_commits = project.repository.commits(project.default_branch, nil, 3) - post_receive_data(@push_commits.last.id, @push_commits.first.id, "refs/heads/#{project.default_branch}") + @push_data = build_push_data(oldrev, newrev, ref) + + EventCreateService.new.push(project, user, @push_data) + project.execute_hooks(@push_data.dup, :push_hooks) + project.execute_services(@push_data.dup, :push_hooks) end protected - def create_push_event - Event.create!( - project: project, - action: Event::PUSHED, - data: push_data, - author_id: push_data[:user_id] - ) - end - # Extract any GFM references from the pushed commit messages. If the configured issue-closing regex is matched, # close the referenced Issue. Create cross-reference Notes corresponding to any other referenced Mentionables. - def process_commit_messages ref + def process_commit_messages(ref) is_default_branch = is_default_branch?(ref) @push_commits.each do |commit| # Close issues if these commits were pushed to the project's default branch and the commit message matches the # closing regex. Exclude any mentioned Issues from cross-referencing even if the commits are being pushed to # a different branch. - issues_to_close = commit.closes_issues(project) - author = commit_user(commit) + issues_to_close = commit.closes_issues(project, user) + + # Load commit author only if needed. + # For push with 1k commits it prevents 900+ requests in database + author = nil + + if issues_to_close.present? && is_default_branch + author ||= commit_user(commit) - if !issues_to_close.empty? && is_default_branch issues_to_close.each do |issue| Issues::CloseService.new(project, author, {}).execute(issue, commit) end @@ -95,99 +87,46 @@ class GitPushService # Create cross-reference notes for any other references. Omit any issues that were referenced in an # issue-closing phrase, or have already been mentioned from this commit (probably from this commit # being pushed to a different branch). - refs = commit.references(project) - issues_to_close + refs = commit.references(project, user) - issues_to_close refs.reject! { |r| commit.has_mentioned?(r) } - refs.each do |r| - Note.create_cross_reference_note(r, commit, author, project) + + if refs.present? + author ||= commit_user(commit) + + refs.each do |r| + Note.create_cross_reference_note(r, commit, author, project) + end end end end - # Produce a hash of post-receive data - # - # data = { - # before: String, - # after: String, - # ref: String, - # user_id: String, - # user_name: String, - # project_id: String, - # repository: { - # name: String, - # url: String, - # description: String, - # homepage: String, - # }, - # commits: Array, - # total_commits_count: Fixnum - # } - # - def post_receive_data(oldrev, newrev, ref) - # Total commits count - push_commits_count = push_commits.size - - # Get latest 20 commits ASC - push_commits_limited = push_commits.last(20) - - # Hash to be passed as post_receive_data - data = { - before: oldrev, - after: newrev, - ref: ref, - user_id: user.id, - user_name: user.name, - project_id: project.id, - repository: { - name: project.name, - url: project.url_to_repo, - description: project.description, - homepage: project.web_url, - }, - commits: [], - total_commits_count: push_commits_count - } - - # For performance purposes maximum 20 latest commits - # will be passed as post receive hook data. - # - push_commits_limited.each do |commit| - data[:commits] << { - id: commit.id, - message: commit.safe_message, - timestamp: commit.committed_date.xmlschema, - url: "#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}/commit/#{commit.id}", - author: { - name: commit.author_name, - email: commit.author_email - } - } - end - - data + def build_push_data(oldrev, newrev, ref) + Gitlab::PushDataBuilder. + build(project, user, oldrev, newrev, ref, push_commits) end - def push_to_existing_branch? ref, oldrev - ref_parts = ref.split('/') - + def push_to_existing_branch?(ref, oldrev) # Return if this is not a push to a branch (e.g. new commits) - ref_parts[1] =~ /heads/ && oldrev != "0000000000000000000000000000000000000000" + Gitlab::Git.branch_ref?(ref) && !Gitlab::Git.blank_ref?(oldrev) end - def push_to_new_branch? ref, oldrev - ref_parts = ref.split('/') - - ref_parts[1] =~ /heads/ && oldrev == "0000000000000000000000000000000000000000" + def push_to_new_branch?(ref, oldrev) + Gitlab::Git.branch_ref?(ref) && Gitlab::Git.blank_ref?(oldrev) end - def push_to_branch? ref - ref =~ /refs\/heads/ + def push_remove_branch?(ref, newrev) + Gitlab::Git.branch_ref?(ref) && Gitlab::Git.blank_ref?(newrev) end - def is_default_branch? ref - ref == "refs/heads/#{project.default_branch}" + def push_to_branch?(ref) + Gitlab::Git.branch_ref?(ref) end - def commit_user commit - User.find_for_commit(commit.author_email, commit.author_name) || user + def is_default_branch?(ref) + Gitlab::Git.branch_ref?(ref) && Gitlab::Git.ref_name(ref) == project.default_branch + end + + def commit_user(commit) + commit.author || user end end diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb index 62eaf9b4f5..bf203bbd69 100644 --- a/app/services/git_tag_push_service.rb +++ b/app/services/git_tag_push_service.rb @@ -3,38 +3,35 @@ class GitTagPushService def execute(project, user, oldrev, newrev, ref) @project, @user = project, user - @push_data = create_push_data(oldrev, newrev, ref) - create_push_event - project.repository.expire_cache + @push_data = build_push_data(oldrev, newrev, ref) + + EventCreateService.new.push(project, user, @push_data) project.execute_hooks(@push_data.dup, :tag_push_hooks) + project.execute_services(@push_data.dup, :tag_push_hooks) + + project.repository.expire_cache + + true end private - def create_push_data(oldrev, newrev, ref) - data = { - ref: ref, - before: oldrev, - after: newrev, - user_id: user.id, - user_name: user.name, - project_id: project.id, - repository: { - name: project.name, - url: project.url_to_repo, - description: project.description, - homepage: project.web_url - } - } - end + def build_push_data(oldrev, newrev, ref) + commits = [] + message = nil - def create_push_event - Event.create!( - project: project, - action: Event::PUSHED, - data: push_data, - author_id: push_data[:user_id] - ) + if !Gitlab::Git.blank_ref?(newrev) + tag_name = Gitlab::Git.ref_name(ref) + tag = project.repository.find_tag(tag_name) + if tag && tag.target == newrev + commit = project.repository.commit(tag.target) + commits = [commit].compact + message = tag.message + end + end + + Gitlab::PushDataBuilder. + build(project, user, oldrev, newrev, ref, commits, message) end end diff --git a/app/services/gravatar_service.rb b/app/services/gravatar_service.rb index a69c7c7837..4bee0c26a6 100644 --- a/app/services/gravatar_service.rb +++ b/app/services/gravatar_service.rb @@ -1,6 +1,8 @@ class GravatarService + include Gitlab::CurrentSettings + def execute(email, size = nil) - if gravatar_config.enabled && email.present? + if current_application_settings.gravatar_enabled? && email.present? size = 40 if size.nil? || size <= 0 sprintf gravatar_url, diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb new file mode 100644 index 0000000000..5e1906ad2a --- /dev/null +++ b/app/services/issuable_base_service.rb @@ -0,0 +1,18 @@ +class IssuableBaseService < BaseService + private + + def create_assignee_note(issuable) + Note.create_assignee_change_note( + issuable, issuable.project, current_user, issuable.assignee) + end + + def create_milestone_note(issuable) + Note.create_milestone_change_note( + issuable, issuable.project, current_user, issuable.milestone) + end + + def create_labels_note(issuable, added_labels, removed_labels) + Note.create_labels_change_note( + issuable, issuable.project, current_user, added_labels, removed_labels) + end +end diff --git a/app/services/issues/base_service.rb b/app/services/issues/base_service.rb index 71b9ffc348..c3ca04a434 100644 --- a/app/services/issues/base_service.rb +++ b/app/services/issues/base_service.rb @@ -1,21 +1,19 @@ module Issues - class BaseService < ::BaseService + class BaseService < ::IssuableBaseService + + def hook_data(issue, action) + issue_data = issue.to_hook_data(current_user) + issue_url = Gitlab::UrlBuilder.new(:issue).build(issue.id) + issue_data[:object_attributes].merge!(url: issue_url, action: action) + issue_data + end private - def create_assignee_note(issue) - Note.create_assignee_change_note(issue, issue.project, current_user, issue.assignee) - end - def execute_hooks(issue, action = 'open') - issue_data = issue.to_hook_data - issue_url = Gitlab::UrlBuilder.new(:issue).build(issue.id) - issue_data[:object_attributes].merge!(url: issue_url, action: action) + issue_data = hook_data(issue, action) issue.project.execute_hooks(issue_data, :issue_hooks) - end - - def create_milestone_note(issue) - Note.create_milestone_change_note(issue, issue.project, current_user, issue.milestone) + issue.project.execute_services(issue_data, :issue_hooks) end end end diff --git a/app/services/issues/bulk_update_service.rb b/app/services/issues/bulk_update_service.rb index f72a346af6..eb07413ee9 100644 --- a/app/services/issues/bulk_update_service.rb +++ b/app/services/issues/bulk_update_service.rb @@ -1,38 +1,23 @@ module Issues class BulkUpdateService < BaseService def execute - update_data = params[:update] + issues_ids = params.delete(:issues_ids).split(",") + issue_params = params - issues_ids = update_data[:issues_ids].split(",") - milestone_id = update_data[:milestone_id] - assignee_id = update_data[:assignee_id] - status = update_data[:status] - - new_state = nil - - if status.present? - if status == 'closed' - new_state = :close - else - new_state = :reopen - end - end - - opts = {} - opts[:milestone_id] = milestone_id if milestone_id.present? - opts[:assignee_id] = assignee_id if assignee_id.present? + issue_params.delete(:state_event) unless issue_params[:state_event].present? + issue_params.delete(:milestone_id) unless issue_params[:milestone_id].present? + issue_params.delete(:assignee_id) unless issue_params[:assignee_id].present? issues = Issue.where(id: issues_ids) - issues = issues.select { |issue| can?(current_user, :modify_issue, issue) } - issues.each do |issue| - issue.update_attributes(opts) - issue.send new_state if new_state + next unless can?(current_user, :modify_issue, issue) + + Issues::UpdateService.new(issue.project, current_user, issue_params).execute(issue) end { - count: issues.count, - success: !issues.count.zero? + count: issues.count, + success: !issues.count.zero? } end end diff --git a/app/services/issues/close_service.rb b/app/services/issues/close_service.rb index ffed13a12e..f670019cc6 100644 --- a/app/services/issues/close_service.rb +++ b/app/services/issues/close_service.rb @@ -2,9 +2,9 @@ module Issues class CloseService < Issues::BaseService def execute(issue, commit = nil) if issue.close - notification_service.close_issue(issue, current_user) event_service.close_issue(issue, current_user) create_note(issue, commit) + notification_service.close_issue(issue, current_user) execute_hooks(issue, 'close') end diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb index a0e5714443..8f04a69287 100644 --- a/app/services/issues/update_service.rb +++ b/app/services/issues/update_service.rb @@ -8,18 +8,33 @@ module Issues Issues::ReopenService.new(project, current_user, {}).execute(issue) when 'close' Issues::CloseService.new(project, current_user, {}).execute(issue) + when 'task_check' + issue.update_nth_task(params[:task_num].to_i, true) + when 'task_uncheck' + issue.update_nth_task(params[:task_num].to_i, false) end - if params.present? && issue.update_attributes(params.except(:state_event)) + params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE + params[:milestone_id] = "" if params[:milestone_id] == IssuableFinder::NONE + + old_labels = issue.labels.to_a + + if params.present? && issue.update_attributes(params.except(:state_event, + :task_num)) issue.reset_events_cache + if issue.labels != old_labels + create_labels_note( + issue, issue.labels - old_labels, old_labels - issue.labels) + end + if issue.previous_changes.include?('milestone_id') create_milestone_note(issue) end if issue.previous_changes.include?('assignee_id') - notification_service.reassigned_issue(issue, current_user) create_assignee_note(issue) + notification_service.reassigned_issue(issue, current_user) end issue.notice_added_references(issue.project, current_user) diff --git a/app/services/merge_requests/auto_merge_service.rb b/app/services/merge_requests/auto_merge_service.rb index 20b88d1510..378b39bb9d 100644 --- a/app/services/merge_requests/auto_merge_service.rb +++ b/app/services/merge_requests/auto_merge_service.rb @@ -5,15 +5,16 @@ module MergeRequests # mark merge request as merged and execute all hooks and notifications # Called when you do merge via GitLab UI class AutoMergeService < BaseMergeService - def execute(merge_request, current_user, commit_message) + def execute(merge_request, commit_message) merge_request.lock_mr if Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message) merge_request.merge - notification.merge_mr(merge_request, current_user) create_merge_event(merge_request, current_user) - execute_project_hooks(merge_request) + create_note(merge_request) + notification_service.merge_mr(merge_request, current_user) + execute_hooks(merge_request) true else diff --git a/app/services/merge_requests/base_merge_service.rb b/app/services/merge_requests/base_merge_service.rb index 9bc50d3d16..9579573adf 100644 --- a/app/services/merge_requests/base_merge_service.rb +++ b/app/services/merge_requests/base_merge_service.rb @@ -1,20 +1,10 @@ module MergeRequests - class BaseMergeService + class BaseMergeService < MergeRequests::BaseService private - def notification - NotificationService.new - end - def create_merge_event(merge_request, current_user) EventCreateService.new.merge_mr(merge_request, current_user) end - - def execute_project_hooks(merge_request) - if merge_request.project - merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks) - end - end end end diff --git a/app/services/merge_requests/base_service.rb b/app/services/merge_requests/base_service.rb index 2907f3587d..f6e1ae6f28 100644 --- a/app/services/merge_requests/base_service.rb +++ b/app/services/merge_requests/base_service.rb @@ -1,24 +1,24 @@ module MergeRequests - class BaseService < ::BaseService - - private - - def create_assignee_note(merge_request) - Note.create_assignee_change_note(merge_request, merge_request.project, current_user, merge_request.assignee) - end + class BaseService < ::IssuableBaseService def create_note(merge_request) Note.create_status_change_note(merge_request, merge_request.target_project, current_user, merge_request.state, nil) end - def execute_hooks(merge_request) - if merge_request.project - merge_request.project.execute_hooks(merge_request.to_hook_data, :merge_request_hooks) - end + def hook_data(merge_request, action) + hook_data = merge_request.to_hook_data(current_user) + merge_request_url = Gitlab::UrlBuilder.new(:merge_request).build(merge_request.id) + hook_data[:object_attributes][:url] = merge_request_url + hook_data[:object_attributes][:action] = action + hook_data end - def create_milestone_note(merge_request) - Note.create_milestone_change_note(merge_request, merge_request.project, current_user, merge_request.milestone) + def execute_hooks(merge_request, action = 'open') + if merge_request.project + merge_data = hook_data(merge_request, action) + merge_request.project.execute_hooks(merge_data, :merge_request_hooks) + merge_request.project.execute_services(merge_data, :merge_request_hooks) + end end end end diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb index 1475973e54..a44b91166e 100644 --- a/app/services/merge_requests/build_service.rb +++ b/app/services/merge_requests/build_service.rb @@ -13,12 +13,9 @@ module MergeRequests merge_request.target_branch ||= merge_request.target_project.default_branch unless merge_request.target_branch && merge_request.source_branch - return build_failed(merge_request, "You must select source and target branches") + return build_failed(merge_request, nil) end - # Generate suggested MR title based on source branch name - merge_request.title = merge_request.source_branch.titleize.humanize - compare_result = CompareService.new.execute( current_user, merge_request.source_project, @@ -52,6 +49,15 @@ module MergeRequests merge_request.compare_failed = false end + commits = merge_request.compare_commits + if commits && commits.count == 1 + commit = commits.first + merge_request.title = commit.title + merge_request.description = commit.description.try(:strip) + else + merge_request.title = merge_request.source_branch.titleize.humanize + end + merge_request rescue Gitlab::Satellite::BranchesWithoutParent @@ -59,7 +65,7 @@ module MergeRequests end def build_failed(merge_request, message) - merge_request.errors.add(:base, message) + merge_request.errors.add(:base, message) unless message.nil? merge_request.compare_commits = [] merge_request.can_be_created = false merge_request diff --git a/app/services/merge_requests/close_service.rb b/app/services/merge_requests/close_service.rb index 64e37a23e6..47454f9f0c 100644 --- a/app/services/merge_requests/close_service.rb +++ b/app/services/merge_requests/close_service.rb @@ -7,9 +7,9 @@ module MergeRequests if merge_request.close event_service.close_mr(merge_request, current_user) - notification_service.close_mr(merge_request, current_user) create_note(merge_request) - execute_hooks(merge_request) + notification_service.close_mr(merge_request, current_user) + execute_hooks(merge_request, 'close') end merge_request diff --git a/app/services/merge_requests/merge_service.rb b/app/services/merge_requests/merge_service.rb index 680766140b..327ead4ff3 100644 --- a/app/services/merge_requests/merge_service.rb +++ b/app/services/merge_requests/merge_service.rb @@ -6,12 +6,13 @@ module MergeRequests # Called when you do merge via command line and push code # to target branch class MergeService < BaseMergeService - def execute(merge_request, current_user, commit_message) + def execute(merge_request, commit_message) merge_request.merge - notification.merge_mr(merge_request, current_user) create_merge_event(merge_request, current_user) - execute_project_hooks(merge_request) + create_note(merge_request) + notification_service.merge_mr(merge_request, current_user) + execute_hooks(merge_request, 'merge') true rescue diff --git a/app/services/merge_requests/refresh_service.rb b/app/services/merge_requests/refresh_service.rb new file mode 100644 index 0000000000..e9b526d1fb --- /dev/null +++ b/app/services/merge_requests/refresh_service.rb @@ -0,0 +1,94 @@ +module MergeRequests + class RefreshService < MergeRequests::BaseService + def execute(oldrev, newrev, ref) + return true unless Gitlab::Git.branch_ref?(ref) + + @oldrev, @newrev = oldrev, newrev + @branch_name = Gitlab::Git.ref_name(ref) + @fork_merge_requests = @project.fork_merge_requests.opened + @commits = @project.repository.commits_between(oldrev, newrev) + + close_merge_requests + reload_merge_requests + comment_mr_with_commits + + true + end + + private + + # Collect open merge requests that target same branch we push into + # and close if push to master include last commit from merge request + # We need this to close(as merged) merge requests that were merged into + # target branch manually + def close_merge_requests + commit_ids = @commits.map(&:id) + merge_requests = @project.merge_requests.opened.where(target_branch: @branch_name).to_a + merge_requests = merge_requests.select(&:last_commit) + + merge_requests = merge_requests.select do |merge_request| + commit_ids.include?(merge_request.last_commit.id) + end + + + merge_requests.uniq.select(&:source_project).each do |merge_request| + MergeRequests::MergeService. + new(merge_request.target_project, @current_user). + execute(merge_request, nil) + end + end + + def force_push? + Gitlab::ForcePushCheck.force_push?(@project, @oldrev, @newrev) + end + + # Refresh merge request diff if we push to source or target branch of merge request + # Note: we should update merge requests from forks too + def reload_merge_requests + merge_requests = @project.merge_requests.opened.by_branch(@branch_name).to_a + merge_requests += @fork_merge_requests.by_branch(@branch_name).to_a + merge_requests = filter_merge_requests(merge_requests) + + merge_requests.each do |merge_request| + + if merge_request.source_branch == @branch_name || force_push? + merge_request.reload_code + merge_request.mark_as_unchecked + else + mr_commit_ids = merge_request.commits.map(&:id) + push_commit_ids = @commits.map(&:id) + matches = mr_commit_ids & push_commit_ids + + if matches.any? + merge_request.reload_code + merge_request.mark_as_unchecked + else + merge_request.mark_as_unchecked + end + end + end + end + + # Add comment about pushing new commits to merge requests + def comment_mr_with_commits + merge_requests = @project.origin_merge_requests.opened.where(source_branch: @branch_name).to_a + merge_requests += @fork_merge_requests.where(source_branch: @branch_name).to_a + merge_requests = filter_merge_requests(merge_requests) + + merge_requests.each do |merge_request| + mr_commit_ids = Set.new(merge_request.commits.map(&:id)) + + new_commits, existing_commits = @commits.partition do |commit| + mr_commit_ids.include?(commit.id) + end + + Note.create_new_commits_note(merge_request, merge_request.project, + @current_user, new_commits, existing_commits, @oldrev) + end + end + + def filter_merge_requests(merge_requests) + merge_requests.uniq.select(&:source_project) + end + end +end diff --git a/app/services/merge_requests/reopen_service.rb b/app/services/merge_requests/reopen_service.rb index bd68919a55..8279ad2001 100644 --- a/app/services/merge_requests/reopen_service.rb +++ b/app/services/merge_requests/reopen_service.rb @@ -3,9 +3,9 @@ module MergeRequests def execute(merge_request) if merge_request.reopen event_service.reopen_mr(merge_request, current_user) - notification_service.reopen_mr(merge_request, current_user) create_note(merge_request) - execute_hooks(merge_request) + notification_service.reopen_mr(merge_request, current_user) + execute_hooks(merge_request, 'reopen') merge_request.reload_code merge_request.mark_as_unchecked end diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb index 6e416a0080..23af2656c3 100644 --- a/app/services/merge_requests/update_service.rb +++ b/app/services/merge_requests/update_service.rb @@ -17,22 +17,41 @@ module MergeRequests MergeRequests::ReopenService.new(project, current_user, {}).execute(merge_request) when 'close' MergeRequests::CloseService.new(project, current_user, {}).execute(merge_request) + when 'task_check' + merge_request.update_nth_task(params[:task_num].to_i, true) + when 'task_uncheck' + merge_request.update_nth_task(params[:task_num].to_i, false) end - if params.present? && merge_request.update_attributes(params.except(:state_event)) + params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE + params[:milestone_id] = "" if params[:milestone_id] == IssuableFinder::NONE + + old_labels = merge_request.labels.to_a + + if params.present? && merge_request.update_attributes( + params.except(:state_event, :task_num) + ) merge_request.reset_events_cache + if merge_request.labels != old_labels + create_labels_note( + merge_request, + merge_request.labels - old_labels, + old_labels - merge_request.labels + ) + end + if merge_request.previous_changes.include?('milestone_id') create_milestone_note(merge_request) end if merge_request.previous_changes.include?('assignee_id') - notification_service.reassigned_merge_request(merge_request, current_user) create_assignee_note(merge_request) + notification_service.reassigned_merge_request(merge_request, current_user) end merge_request.notice_added_references(merge_request.project, current_user) - execute_hooks(merge_request) + execute_hooks(merge_request, 'update') end merge_request diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb index f64006a4ed..e969061f22 100644 --- a/app/services/notes/create_service.rb +++ b/app/services/notes/create_service.rb @@ -17,10 +17,22 @@ module Notes note.references.each do |mentioned| Note.create_cross_reference_note(mentioned, note.noteable, note.author, note.project) end + + execute_hooks(note) end end note end + + def hook_data(note) + Gitlab::NoteDataBuilder.build(note, current_user) + end + + def execute_hooks(note) + note_data = hook_data(note) + # TODO: Support Webhooks + note.project.execute_services(note_data, :note_hooks) + end end end diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb new file mode 100644 index 0000000000..63431b8247 --- /dev/null +++ b/app/services/notes/update_service.rb @@ -0,0 +1,25 @@ +module Notes + class UpdateService < BaseService + def execute + note = project.notes.find(params[:note_id]) + note.note = params[:note] + if note.save + notification_service.new_note(note) + + # Skip system notes, like status changes and cross-references. + unless note.system + event_service.leave_note(note, note.author) + + # Create a cross-reference note if this Note contains GFM that + # names an issue, merge request, or commit. + note.references.each do |mentioned| + Note.create_cross_reference_note(mentioned, note.noteable, + note.author, note.project) + end + end + end + + note + end + end +end diff --git a/app/services/notification_service.rb b/app/services/notification_service.rb index 36d33e0d7c..cfed7964c3 100644 --- a/app/services/notification_service.rb +++ b/app/services/notification_service.rb @@ -92,6 +92,8 @@ class NotificationService # def merge_mr(merge_request, current_user) recipients = reject_muted_users([merge_request.author, merge_request.assignee], merge_request.target_project) + recipients = add_subscribed_users(recipients, merge_request) + recipients = reject_unsubscribed_users(recipients, merge_request) recipients = recipients.concat(project_watchers(merge_request.target_project)).uniq recipients.delete(current_user) @@ -107,7 +109,7 @@ class NotificationService # Notify new user with email after creation def new_user(user, token = nil) # Don't email omniauth created users - mailer.new_user_email(user.id, user.password, token) unless user.extern_uid? + mailer.new_user_email(user.id, token) unless user.identities.any? end # Notify users on new note in system @@ -118,34 +120,43 @@ class NotificationService return true unless note.noteable_type.present? # ignore gitlab service messages - return true if note.note =~ /\A_Status changed to closed_/ - return true if note.note =~ /\A_mentioned in / && note.system == true - - opts = { noteable_type: note.noteable_type, project_id: note.project_id } + return true if note.note.start_with?('Status changed to closed') + return true if note.cross_reference? && note.system == true target = note.noteable - if target.respond_to?(:participants) - recipients = target.participants - else - recipients = note.mentioned_users - end + + recipients = [] if note.commit_id.present? - opts.merge!(commit_id: note.commit_id) recipients << note.commit_author - else - opts.merge!(noteable_id: note.noteable_id) end - - # Get users who left comment in thread - recipients = recipients.concat(User.where(id: Note.where(opts).pluck(:author_id))) + + # Add all users participating in the thread (author, assignee, comment authors) + participants = + if target.respond_to?(:participants) + target.participants + elsif target.is_a?(Commit) + author_ids = Note.for_commit_id(target.id).pluck(:author_id).uniq + User.where(id: author_ids) + else + note.mentioned_users + end + recipients = recipients.concat(participants) # Merge project watchers recipients = recipients.concat(project_watchers(note.project)).compact.uniq + # Reject users with Mention notification level, except those mentioned in _this_ note. + recipients = reject_mention_users(recipients - note.mentioned_users, note.project) + recipients = recipients + note.mentioned_users + # Reject mutes users recipients = reject_muted_users(recipients, note.project) + recipients = add_subscribed_users(recipients, note.noteable) + + recipients = reject_unsubscribed_users(recipients, note.noteable) + # Reject author recipients.delete(note.author) @@ -157,20 +168,44 @@ class NotificationService end end - def new_team_member(users_project) - mailer.project_access_granted_email(users_project.id) + def invite_project_member(project_member, token) + mailer.project_member_invited_email(project_member.id, token) end - def update_team_member(users_project) - mailer.project_access_granted_email(users_project.id) + def accept_project_invite(project_member) + mailer.project_invite_accepted_email(project_member.id) end - def new_group_member(users_group) - mailer.group_access_granted_email(users_group.id) + def decline_project_invite(project_member) + mailer.project_invite_declined_email(project_member.project.id, project_member.invite_email, project_member.access_level, project_member.created_by_id) end - def update_group_member(users_group) - mailer.group_access_granted_email(users_group.id) + def new_project_member(project_member) + mailer.project_access_granted_email(project_member.id) + end + + def update_project_member(project_member) + mailer.project_access_granted_email(project_member.id) + end + + def invite_group_member(group_member, token) + mailer.group_member_invited_email(group_member.id, token) + end + + def accept_group_invite(group_member) + mailer.group_invite_accepted_email(group_member.id) + end + + def decline_group_invite(group_member) + mailer.group_invite_declined_email(group_member.group.id, group_member.invite_email, group_member.access_level, group_member.created_by_id) + end + + def new_group_member(group_member) + mailer.group_access_granted_email(group_member.id) + end + + def update_group_member(group_member) + mailer.group_access_granted_email(group_member.id) end def project_was_moved(project) @@ -186,20 +221,20 @@ class NotificationService # Get project users with WATCH notification level def project_watchers(project) - project_members = users_project_notification(project) + project_members = project_member_notification(project) - users_with_project_level_global = users_project_notification(project, Notification::N_GLOBAL) - users_with_group_level_global = users_group_notification(project, Notification::N_GLOBAL) + users_with_project_level_global = project_member_notification(project, Notification::N_GLOBAL) + users_with_group_level_global = group_member_notification(project, Notification::N_GLOBAL) users = users_with_global_level_watch([users_with_project_level_global, users_with_group_level_global].flatten.uniq) - users_with_project_setting = select_users_project_setting(project, users_with_project_level_global, users) - users_with_group_setting = select_users_group_setting(project, project_members, users_with_group_level_global, users) + users_with_project_setting = select_project_member_setting(project, users_with_project_level_global, users) + users_with_group_setting = select_group_member_setting(project, project_members, users_with_group_level_global, users) User.where(id: users_with_project_setting.concat(users_with_group_setting).uniq).to_a end - def users_project_notification(project, notification_level=nil) - project_members = project.users_projects + def project_member_notification(project, notification_level=nil) + project_members = project.project_members if notification_level project_members.where(notification_level: notification_level).pluck(:user_id) @@ -208,9 +243,9 @@ class NotificationService end end - def users_group_notification(project, notification_level) + def group_member_notification(project, notification_level) if project.group - project.group.users_groups.where(notification_level: notification_level).pluck(:user_id) + project.group.group_members.where(notification_level: notification_level).pluck(:user_id) else [] end @@ -224,8 +259,8 @@ class NotificationService end # Build a list of users based on project notifcation settings - def select_users_project_setting(project, global_setting, users_global_level_watch) - users = users_project_notification(project, Notification::N_WATCH) + def select_project_member_setting(project, global_setting, users_global_level_watch) + users = project_member_notification(project, Notification::N_WATCH) # If project setting is global, add to watch list if global setting is watch global_setting.each do |user_id| @@ -237,9 +272,9 @@ class NotificationService users end - # Build a list of users based on group notifcation settings - def select_users_group_setting(project, project_members, global_setting, users_global_level_watch) - uids = users_group_notification(project, Notification::N_WATCH) + # Build a list of users based on group notification settings + def select_group_member_setting(project, project_members, global_setting, users_global_level_watch) + uids = group_member_notification(project, Notification::N_WATCH) # Group setting is watch, add to users list if user is not project member users = [] @@ -263,35 +298,75 @@ class NotificationService # Also remove duplications and nil recipients def reject_muted_users(users, project = nil) users = users.to_a.compact.uniq + users = users.reject(&:blocked?) users.reject do |user| next user.notification.disabled? unless project - tm = project.users_projects.find_by(user_id: user.id) + member = project.project_members.find_by(user_id: user.id) - if !tm && project.group - tm = project.group.users_groups.find_by(user_id: user.id) + if !member && project.group + member = project.group.group_members.find_by(user_id: user.id) end # reject users who globally disabled notification and has no membership - next user.notification.disabled? unless tm + next user.notification.disabled? unless member # reject users who disabled notification in project - next true if tm.notification.disabled? + next true if member.notification.disabled? # reject users who have N_GLOBAL in project and disabled in global settings - tm.notification.global? && user.notification.disabled? + member.notification.global? && user.notification.disabled? end end - def new_resource_email(target, project, method) - if target.respond_to?(:participants) - recipients = target.participants - else - recipients = [] + # Remove users with notification level 'Mentioned' + def reject_mention_users(users, project = nil) + users = users.to_a.compact.uniq + + users.reject do |user| + next user.notification.mention? unless project + + member = project.project_members.find_by(user_id: user.id) + + if !member && project.group + member = project.group.group_members.find_by(user_id: user.id) + end + + # reject users who globally set mention notification and has no membership + next user.notification.mention? unless member + + # reject users who set mention notification in project + next true if member.notification.mention? + + # reject users who have N_MENTION in project and disabled in global settings + member.notification.global? && user.notification.mention? end - recipients = reject_muted_users(recipients, project) - recipients = recipients.concat(project_watchers(project)).uniq + end + + def reject_unsubscribed_users(recipients, target) + return recipients unless target.respond_to? :subscriptions + + recipients.reject do |user| + subscription = target.subscriptions.find_by_user_id(user.id) + subscription && !subscription.subscribed + end + end + + def add_subscribed_users(recipients, target) + return recipients unless target.respond_to? :subscriptions + + subscriptions = target.subscriptions + + if subscriptions.any? + recipients + subscriptions.where(subscribed: true).map(&:user) + else + recipients + end + end + + def new_resource_email(target, project, method) + recipients = build_recipients(target, project) recipients.delete(target.author) recipients.each do |recipient| @@ -300,8 +375,7 @@ class NotificationService end def close_resource_email(target, project, current_user, method) - recipients = reject_muted_users([target.author, target.assignee], project) - recipients = recipients.concat(project_watchers(project)).uniq + recipients = build_recipients(target, project) recipients.delete(current_user) recipients.each do |recipient| @@ -311,16 +385,7 @@ class NotificationService def reassign_resource_email(target, project, current_user, method) assignee_id_was = previous_record(target, "assignee_id") - - recipients = User.where(id: [target.assignee_id, assignee_id_was]) - - # Add watchers to email list - recipients = recipients.concat(project_watchers(project)) - - # reject users with disabled notifications - recipients = reject_muted_users(recipients, project) - - # Reject me from recipients if I reassign an item + recipients = build_recipients(target, project) recipients.delete(current_user) recipients.each do |recipient| @@ -329,8 +394,7 @@ class NotificationService end def reopen_resource_email(target, project, current_user, method, status) - recipients = reject_muted_users([target.author, target.assignee], project) - recipients = recipients.concat(project_watchers(project)).uniq + recipients = build_recipients(target, project) recipients.delete(current_user) recipients.each do |recipient| @@ -338,6 +402,22 @@ class NotificationService end end + def build_recipients(target, project) + recipients = + if target.respond_to?(:participants) + target.participants + else + [target.author, target.assignee] + end + + recipients = reject_muted_users(recipients, project) + recipients = reject_mention_users(recipients, project) + recipients = add_subscribed_users(recipients, target) + recipients = recipients.concat(project_watchers(project)).uniq + recipients = reject_unsubscribed_users(recipients, target) + recipients + end + def mailer Notify.delay end diff --git a/app/services/oauth2/access_token_validation_service.rb b/app/services/oauth2/access_token_validation_service.rb new file mode 100644 index 0000000000..6194f6ce91 --- /dev/null +++ b/app/services/oauth2/access_token_validation_service.rb @@ -0,0 +1,41 @@ +module Oauth2::AccessTokenValidationService + # Results: + VALID = :valid + EXPIRED = :expired + REVOKED = :revoked + INSUFFICIENT_SCOPE = :insufficient_scope + + class << self + def validate(token, scopes: []) + if token.expired? + return EXPIRED + + elsif token.revoked? + return REVOKED + + elsif !self.sufficient_scope?(token, scopes) + return INSUFFICIENT_SCOPE + + else + return VALID + end + end + + protected + # True if the token's scope is a superset of required scopes, + # or the required scopes is empty. + def sufficient_scope?(token, scopes) + if scopes.blank? + # if no any scopes required, the scopes of token is sufficient. + return true + else + # If there are scopes required, then check whether + # the set of authorized scopes is a superset of the set of required scopes + required_scopes = Set.new(scopes) + authorized_scopes = Set.new(token.scopes) + + return authorized_scopes >= required_scopes + end + end + end +end diff --git a/app/services/projects/autocomplete_service.rb b/app/services/projects/autocomplete_service.rb new file mode 100644 index 0000000000..7408e09ed1 --- /dev/null +++ b/app/services/projects/autocomplete_service.rb @@ -0,0 +1,15 @@ +module Projects + class AutocompleteService < BaseService + def initialize(project) + @project = project + end + + def issues + @project.issues.opened.select([:iid, :title]) + end + + def merge_requests + @project.merge_requests.opened.select([:iid, :title]) + end + end +end diff --git a/app/services/projects/create_service.rb b/app/services/projects/create_service.rb index 2bfb0f28d9..a7afcf8f64 100644 --- a/app/services/projects/create_service.rb +++ b/app/services/projects/create_service.rb @@ -7,17 +7,25 @@ module Projects def execute @project = Project.new(params) - # Reset visibility levet if is not allowed to set it - unless Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level]) - @project.visibility_level = default_features.visibility_level + # Make sure that the user is allowed to use the specified visibility + # level + unless Gitlab::VisibilityLevel.allowed_for?(current_user, + params[:visibility_level]) + deny_visibility_level(@project) + return @project end - # Parametrize path for project - # - # Ex. - # 'GitLab HQ'.parameterize => "gitlab-hq" - # - @project.path = @project.name.dup.parameterize unless @project.path.present? + # Set project name from path + if @project.name.present? && @project.path.present? + # if both name and path set - everything is ok + elsif @project.path.present? + # Set project name from path + @project.name = @project.path.dup + elsif @project.name.present? + # For compatibility - set path from name + # TODO: remove this in 8.0 + @project.path = @project.name.dup.parameterize + end # get namespace id namespace_id = params[:namespace_id] @@ -37,40 +45,18 @@ module Projects @project.creator = current_user - if @project.save - log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"") - system_hook_service.execute_hooks_for(@project, :create) + Project.transaction do + @project.save - unless @project.group - @project.users_projects.create( - project_access: UsersProject::MASTER, - user: current_user - ) - end - - @project.update_column(:last_activity_at, @project.created_at) - - if @project.import? - @project.import_start - else - GitlabShellWorker.perform_async( - :add_repository, - @project.path_with_namespace - ) - end - - if @project.wiki_enabled? - begin - # force the creation of a wiki, - ProjectWiki.new(@project, @project.owner).wiki - rescue ProjectWiki::CouldNotCreateWikiError => ex - # Prevent project observer crash - # if failed to create wiki - nil + unless @project.import? + unless @project.create_repository + raise 'Failed to create repository' end end end + after_create_actions if @project.persisted? + @project rescue => ex @project.errors.add(:base, "Can't save project. Please try again later") @@ -87,5 +73,24 @@ module Projects namespace = Namespace.find_by(id: namespace_id) current_user.can?(:create_projects, namespace) end + + def after_create_actions + log_info("#{@project.owner.name} created a new project \"#{@project.name_with_namespace}\"") + + @project.create_wiki if @project.wiki_enabled? + + event_service.create_project(@project, current_user) + system_hook_service.execute_hooks_for(@project, :create) + + unless @project.group + @project.team << [current_user, :master, current_user] + end + + @project.update_column(:last_activity_at, @project.created_at) + + if @project.import? + @project.import_start + end + end end end diff --git a/app/services/projects/fork_service.rb b/app/services/projects/fork_service.rb index 2f1c7b18aa..1e4deb6ed3 100644 --- a/app/services/projects/fork_service.rb +++ b/app/services/projects/fork_service.rb @@ -2,16 +2,32 @@ module Projects class ForkService < BaseService include Gitlab::ShellAdapter - def initialize(project, user) - @from_project, @current_user = project, user - end - def execute - project = @from_project.dup + @from_project = @project + + project_params = { + visibility_level: @from_project.visibility_level, + description: @from_project.description, + } + + project = Project.new(project_params) project.name = @from_project.name project.path = @from_project.path - project.namespace = current_user.namespace - project.creator = current_user + project.creator = @current_user + if @from_project.avatar.present? && @from_project.avatar.image? + project.avatar = @from_project.avatar + end + + if namespace = @params[:namespace] + project.namespace = namespace + else + project.namespace = @current_user.namespace + end + + unless @current_user.can?(:create_projects, project.namespace) + project.errors.add(:namespace, 'insufficient access rights') + return project + end # If the project cannot save, we do not want to trigger the project destroy # as this can have the side effect of deleting a repo attached to an existing @@ -22,23 +38,29 @@ module Projects #First save the DB entries as they can be rolled back if the repo fork fails project.build_forked_project_link(forked_to_project_id: project.id, forked_from_project_id: @from_project.id) if project.save - project.users_projects.create(project_access: UsersProject::MASTER, user: current_user) + project.team << [@current_user, :master, @current_user] end + #Now fork the repo unless gitlab_shell.fork_repository(@from_project.path_with_namespace, project.namespace.path) - raise "forking failed in gitlab-shell" + raise 'forking failed in gitlab-shell' end + project.ensure_satellite_exists end + + if @from_project.gitlab_ci? + ForkRegistrationWorker.perform_async(@from_project.id, project.id, @current_user.private_token) + end rescue => ex - project.errors.add(:base, "Fork transaction failed.") + project.errors.add(:base, 'Fork transaction failed.') project.destroy end else - project.errors.add(:base, "Invalid fork destination") + project.errors.add(:base, 'Invalid fork destination') end - project + project end end end diff --git a/app/services/projects/image_service.rb b/app/services/projects/image_service.rb deleted file mode 100644 index c79ddddd97..0000000000 --- a/app/services/projects/image_service.rb +++ /dev/null @@ -1,39 +0,0 @@ -module Projects - class ImageService < BaseService - include Rails.application.routes.url_helpers - def initialize(repository, params, root_url) - @repository, @params, @root_url = repository, params.dup, root_url - end - - def execute - uploader = FileUploader.new('uploads', upload_path, accepted_images) - image = @params['markdown_img'] - - if image && correct_mime_type?(image) - alt = image.original_filename - uploader.store!(image) - link = { - 'alt' => File.basename(alt, '.*'), - 'url' => File.join(@root_url, uploader.url) - } - else - link = nil - end - end - - protected - - def upload_path - base_dir = FileUploader.generate_dir - File.join(@repository.path_with_namespace, base_dir) - end - - def accepted_images - %w(png jpg jpeg gif) - end - - def correct_mime_type?(image) - accepted_images.map{ |format| image.content_type.include? format }.any? - end - end -end diff --git a/app/services/projects/participants_service.rb b/app/services/projects/participants_service.rb index c4d2c0963b..ae6260bcda 100644 --- a/app/services/projects/participants_service.rb +++ b/app/services/projects/participants_service.rb @@ -1,28 +1,25 @@ module Projects class ParticipantsService < BaseService - def initialize(project) - @project = project - end - def execute(note_type, note_id) - participating = if note_type && note_id - participants_in(note_type, note_id) - else - [] - end - team_members = sorted(@project.team.members) - participants = all_members + team_members + participating + participating = + if note_type && note_id + participants_in(note_type, note_id) + else + [] + end + project_members = sorted(project.team.members) + participants = all_members + groups + project_members + participating participants.uniq end def participants_in(type, id) users = case type when "Issue" - issue = @project.issues.find_by_iid(id) - issue ? issue.participants : [] + issue = project.issues.find_by_iid(id) + issue ? issue.participants(current_user) : [] when "MergeRequest" - merge_request = @project.merge_requests.find_by_iid(id) - merge_request ? merge_request.participants : [] + merge_request = project.merge_requests.find_by_iid(id) + merge_request ? merge_request.participants(current_user) : [] when "Commit" author_ids = Note.for_commit_id(id).pluck(:author_id).uniq User.where(id: author_ids) @@ -33,11 +30,21 @@ module Projects end def sorted(users) - users.uniq.to_a.compact.sort_by(&:username).map { |user| { username: user.username, name: user.name } } + users.uniq.to_a.compact.sort_by(&:username).map do |user| + { username: user.username, name: user.name } + end + end + + def groups + current_user.authorized_groups.sort_by(&:path).map do |group| + count = group.users.count + { username: group.path, name: "#{group.name} (#{count})" } + end end def all_members - [{ username: "all", name: "Project and Group Members" }] + count = project.team.members.flatten.count + [{ username: "all", name: "All Project and Group Members (#{count})" }] end end end diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb index e39fe882cb..489e03bd5e 100644 --- a/app/services/projects/transfer_service.rb +++ b/app/services/projects/transfer_service.rb @@ -12,7 +12,7 @@ module Projects class TransferError < StandardError; end def execute - namespace_id = params[:namespace_id] + namespace_id = params[:new_namespace_id] namespace = Namespace.find_by(id: namespace_id) if allowed_transfer?(current_user, project, namespace) @@ -43,6 +43,9 @@ module Projects project.namespace = new_namespace project.save! + # Notifications + project.send_move_instructions + # Move main repository unless gitlab_shell.mv_repository(old_path, new_path) raise TransferError.new('Cannot move project') diff --git a/app/services/projects/update_service.rb b/app/services/projects/update_service.rb index 36877a6167..69bdd045dd 100644 --- a/app/services/projects/update_service.rb +++ b/app/services/projects/update_service.rb @@ -2,8 +2,13 @@ module Projects class UpdateService < BaseService def execute # check that user is allowed to set specified visibility_level - unless can?(current_user, :change_visibility_level, project) && Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level]) - params[:visibility_level] = project.visibility_level + new_visibility = params[:visibility_level] + if new_visibility && new_visibility.to_i != project.visibility_level + unless can?(current_user, :change_visibility_level, project) && + Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility) + deny_visibility_level(project, new_visibility) + return project + end end new_branch = params[:default_branch] diff --git a/app/services/projects/upload_service.rb b/app/services/projects/upload_service.rb new file mode 100644 index 0000000000..992a7a7a1d --- /dev/null +++ b/app/services/projects/upload_service.rb @@ -0,0 +1,28 @@ +module Projects + class UploadService < BaseService + def initialize(project, file) + @project, @file = project, file + end + + def execute + return nil unless @file and @file.size <= max_attachment_size + + uploader = FileUploader.new(@project) + uploader.store!(@file) + + filename = uploader.image? ? uploader.file.basename : uploader.file.filename + + { + 'alt' => filename, + 'url' => uploader.secure_url, + 'is_image' => uploader.image? + } + end + + private + + def max_attachment_size + current_application_settings.max_attachment_size.megabytes.to_i + end + end +end diff --git a/app/services/search/global_service.rb b/app/services/search/global_service.rb index d213e1375e..0bcc50c81a 100644 --- a/app/services/search/global_service.rb +++ b/app/services/search/global_service.rb @@ -7,30 +7,12 @@ module Search end def execute - query = params[:search] - query = Shellwords.shellescape(query) if query.present? - return result unless query.present? - group = Group.find_by(id: params[:group_id]) if params[:group_id].present? projects = ProjectsFinder.new.execute(current_user) projects = projects.where(namespace_id: group.id) if group project_ids = projects.pluck(:id) - result[:projects] = projects.search(query).limit(20) - result[:merge_requests] = MergeRequest.in_projects(project_ids).search(query).order('updated_at DESC').limit(20) - result[:issues] = Issue.where(project_id: project_ids).search(query).order('updated_at DESC').limit(20) - result[:total_results] = %w(projects issues merge_requests).sum { |items| result[items.to_sym].size } - result - end - - def result - @result ||= { - projects: [], - merge_requests: [], - issues: [], - notes: [], - total_results: 0, - } + Gitlab::SearchResults.new(project_ids, params[:search]) end end end diff --git a/app/services/search/project_service.rb b/app/services/search/project_service.rb index 8aac18840e..f630c0a379 100644 --- a/app/services/search/project_service.rb +++ b/app/services/search/project_service.rb @@ -7,39 +7,9 @@ module Search end def execute - query = params[:search] - query = Shellwords.shellescape(query) if query.present? - return result unless query.present? - - if params[:search_code].present? - if !@project.empty_repo? - blobs = project.repository.search_files(query, - params[:repository_ref]) - else - blobs = Array.new - end - - blobs = Kaminari.paginate_array(blobs).page(params[:page]).per(20) - result[:blobs] = blobs - result[:total_results] = blobs.total_count - else - result[:merge_requests] = project.merge_requests.search(query).order('updated_at DESC').limit(20) - result[:issues] = project.issues.where("title like :query OR description like :query ", query: "%#{query}%").order('updated_at DESC').limit(20) - result[:notes] = Note.where(noteable_type: 'issue').where(project_id: project.id).where("note like :query", query: "%#{query}%").order('updated_at DESC').limit(20) - result[:total_results] = %w(issues merge_requests notes).sum { |items| result[items.to_sym].size } - end - - result - end - - def result - @result ||= { - merge_requests: [], - issues: [], - blobs: [], - notes: [], - total_results: 0, - } + Gitlab::ProjectSearchResults.new(project.id, + params[:search], + params[:repository_ref]) end end end diff --git a/app/services/search/snippet_service.rb b/app/services/search/snippet_service.rb new file mode 100644 index 0000000000..8ca0877321 --- /dev/null +++ b/app/services/search/snippet_service.rb @@ -0,0 +1,14 @@ +module Search + class SnippetService + attr_accessor :current_user, :params + + def initialize(user, params) + @current_user, @params = user, params.dup + end + + def execute + snippet_ids = Snippet.accessible_to(current_user).pluck(:id) + Gitlab::SnippetSearchResults.new(snippet_ids, params[:search]) + end + end +end diff --git a/app/services/system_hooks_service.rb b/app/services/system_hooks_service.rb index 41014f199d..c5d0b08845 100644 --- a/app/services/system_hooks_service.rb +++ b/app/services/system_hooks_service.rb @@ -18,10 +18,20 @@ class SystemHooksService def build_event_data(model, event) data = { event_name: build_event_name(model, event), - created_at: model.created_at + created_at: model.created_at.xmlschema } case model + when Key + data.merge!( + key: model.key, + id: model.id + ) + if model.user + data.merge!( + username: model.user.username + ) + end when Project owner = model.owner @@ -31,7 +41,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 : nil, + owner_email: owner.respond_to?(:email) ? owner.email : "", project_visibility: Project.visibility_levels.key(model.visibility_level_field).downcase }) when User @@ -40,24 +50,47 @@ class SystemHooksService email: model.email, user_id: model.id }) - when UsersProject + when ProjectMember data.merge!({ project_name: model.project.name, project_path: model.project.path, - project_id: model.project_id, + project_id: model.project.id, user_name: model.user.name, user_email: model.user.email, - project_access: model.human_access, + access_level: model.human_access, project_visibility: Project.visibility_levels.key(model.project.visibility_level_field).downcase }) + when Group + owner = model.owner + + data.merge!( + name: model.name, + path: model.path, + group_id: model.id, + owner_name: owner.respond_to?(:name) ? owner.name : nil, + owner_email: owner.respond_to?(:email) ? owner.email : nil, + ) + when GroupMember + data.merge!( + group_name: model.group.name, + group_path: model.group.path, + group_id: model.group.id, + user_name: model.user.name, + user_email: model.user.email, + user_id: model.user.id, + group_access: model.human_access, + ) end end def build_event_name(model, event) case model - when UsersProject + when ProjectMember return "user_add_to_team" if event == :create return "user_remove_from_team" if event == :destroy + when GroupMember + return 'user_add_to_group' if event == :create + return 'user_remove_from_group' if event == :destroy else "#{model.class.name.downcase}_#{event.to_s}" end diff --git a/app/services/test_hook_service.rb b/app/services/test_hook_service.rb index b6b1ef29b5..21ec2c01cb 100644 --- a/app/services/test_hook_service.rb +++ b/app/services/test_hook_service.rb @@ -1,9 +1,6 @@ class TestHookService def execute(hook, current_user) - data = GitPushService.new.sample_data(hook.project, current_user) + data = Gitlab::PushDataBuilder.build_sample(hook.project, current_user) hook.execute(data) - true - rescue SocketError - false end end diff --git a/app/services/update_snippet_service.rb b/app/services/update_snippet_service.rb new file mode 100644 index 0000000000..9d181c2d2a --- /dev/null +++ b/app/services/update_snippet_service.rb @@ -0,0 +1,22 @@ +class UpdateSnippetService < BaseService + attr_accessor :snippet + + def initialize(project, user, snippet, params) + super(project, user, params) + @snippet = snippet + end + + def execute + # check that user is allowed to set specified visibility_level + new_visibility = params[:visibility_level] + if new_visibility && new_visibility.to_i != snippet.visibility_level + unless can?(current_user, :change_visibility_level, snippet) && + Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility) + deny_visibility_level(snippet, new_visibility) + return snippet + end + end + + snippet.update_attributes(params) + end +end diff --git a/app/uploaders/attachment_uploader.rb b/app/uploaders/attachment_uploader.rb index b122b6c865..a9691bee46 100644 --- a/app/uploaders/attachment_uploader.rb +++ b/app/uploaders/attachment_uploader.rb @@ -3,8 +3,6 @@ class AttachmentUploader < CarrierWave::Uploader::Base storage :file - after :store, :reset_events_cache - def store_dir "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" end @@ -22,15 +20,7 @@ class AttachmentUploader < CarrierWave::Uploader::Base false end - def secure_url - Gitlab.config.gitlab.relative_url_root + "/files/#{model.class.to_s.underscore}/#{model.id}/#{file.filename}" - end - def file_storage? self.class.storage == CarrierWave::Storage::File end - - def reset_events_cache(file) - model.reset_events_cache if model.is_a?(User) - end end diff --git a/app/uploaders/avatar_uploader.rb b/app/uploaders/avatar_uploader.rb new file mode 100644 index 0000000000..7cad044555 --- /dev/null +++ b/app/uploaders/avatar_uploader.rb @@ -0,0 +1,32 @@ +# encoding: utf-8 + +class AvatarUploader < CarrierWave::Uploader::Base + storage :file + + after :store, :reset_events_cache + + def store_dir + "uploads/#{model.class.to_s.underscore}/#{mounted_as}/#{model.id}" + end + + def image? + img_ext = %w(png jpg jpeg gif bmp tiff) + if file.respond_to?(:extension) + img_ext.include?(file.extension.downcase) + else + # Not all CarrierWave storages respond to :extension + ext = file.path.split('.').last.downcase + img_ext.include?(ext) + end + rescue + false + end + + def file_storage? + self.class.storage == CarrierWave::Storage::File + end + + def reset_events_cache(file) + model.reset_events_cache if model.is_a?(User) + end +end diff --git a/app/uploaders/file_uploader.rb b/app/uploaders/file_uploader.rb index 0fa987c93f..f9673abbfe 100644 --- a/app/uploaders/file_uploader.rb +++ b/app/uploaders/file_uploader.rb @@ -2,40 +2,47 @@ class FileUploader < CarrierWave::Uploader::Base storage :file - def initialize(base_dir, path = '', allowed_extensions = nil) - @base_dir = base_dir - @path = path - @allowed_extensions = allowed_extensions + attr_accessor :project, :secret + + def initialize(project, secret = self.class.generate_secret) + @project = project + @secret = secret end def base_dir - @base_dir + "uploads" end def store_dir - File.join(@base_dir, @path) + File.join(base_dir, @project.path_with_namespace, @secret) end def cache_dir - File.join(@base_dir, 'tmp', @path) + File.join(base_dir, 'tmp', @project.path_with_namespace, @secret) end - def extension_white_list - @allowed_extensions + def self.generate_secret + SecureRandom.hex end - def store!(file) - @filename = self.class.generate_filename(file) - super + def secure_url + File.join(Gitlab.config.gitlab.url, @project.path_with_namespace, "uploads", @secret, file.filename) end - def self.generate_filename(file) - original_filename = File.basename(file.original_filename, '.*') - extension = File.extname(file.original_filename) - new_filename = Digest::MD5.hexdigest(original_filename) + extension + def file_storage? + self.class.storage == CarrierWave::Storage::File end - def self.generate_dir - SecureRandom.hex(5) + def image? + img_ext = %w(png jpg jpeg gif bmp tiff) + if file.respond_to?(:extension) + img_ext.include?(file.extension.downcase) + else + # Not all CarrierWave storages respond to :extension + ext = file.path.split('.').last.downcase + img_ext.include?(ext) + end + rescue + false end end diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml new file mode 100644 index 0000000000..4f3565c67e --- /dev/null +++ b/app/views/admin/application_settings/_form.html.haml @@ -0,0 +1,69 @@ += form_for @application_setting, url: admin_application_settings_path, html: { class: 'form-horizontal fieldset-form' } do |f| + - if @application_setting.errors.any? + #error_explanation + .alert.alert-danger + - @application_setting.errors.full_messages.each do |msg| + %p= msg + + %fieldset + %legend Features + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :signup_enabled do + = f.check_box :signup_enabled + Signup enabled + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :signin_enabled do + = f.check_box :signin_enabled + Signin enabled + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :gravatar_enabled do + = f.check_box :gravatar_enabled + Gravatar enabled + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :twitter_sharing_enabled do + = f.check_box :twitter_sharing_enabled, :'aria-describedby' => 'twitter_help_block' + %strong Twitter enabled + %span.help-block#twitter_help_block Show users a button to share their newly created public or internal projects on twitter + %fieldset + %legend Misc + .form-group + = f.label :default_projects_limit, class: 'control-label col-sm-2' + .col-sm-10 + = f.number_field :default_projects_limit, class: 'form-control' + .form-group + = f.label :default_branch_protection, class: 'control-label col-sm-2' + .col-sm-10 + = f.select :default_branch_protection, options_for_select(Gitlab::Access.protection_options, @application_setting.default_branch_protection), {}, class: 'form-control' + .form-group + = f.label :restricted_visibility_levels, class: 'control-label col-sm-2' + .col-sm-10 + - data_attrs = { toggle: 'buttons' } + .btn-group{ data: data_attrs } + - restricted_level_checkboxes('restricted-visibility-help').each do |level| + = level + %span.help-block#restricted-visibility-help Selected levels cannot be used by non-admin users for projects or snippets + .form-group + = f.label :home_page_url, class: 'control-label col-sm-2' + .col-sm-10 + = f.text_field :home_page_url, class: 'form-control', placeholder: 'http://company.example.com', :'aria-describedby' => 'home_help_block' + %span.help-block#home_help_block We will redirect non-logged in users to this page + .form-group + = f.label :sign_in_text, class: 'control-label col-sm-2' + .col-sm-10 + = f.text_area :sign_in_text, class: 'form-control', rows: 4 + .help-block Markdown enabled + .form-group + = f.label :max_attachment_size, 'Maximum attachment size (MB)', class: 'control-label col-sm-2' + .col-sm-10 + = f.number_field :max_attachment_size, class: 'form-control' + + .form-actions + = f.submit 'Save', class: 'btn btn-primary' diff --git a/app/views/admin/application_settings/show.html.haml b/app/views/admin/application_settings/show.html.haml new file mode 100644 index 0000000000..39b66647a5 --- /dev/null +++ b/app/views/admin/application_settings/show.html.haml @@ -0,0 +1,3 @@ +%h3.page-title Application settings +%hr += render 'form' diff --git a/app/views/admin/applications/_delete_form.html.haml b/app/views/admin/applications/_delete_form.html.haml new file mode 100644 index 0000000000..3147cbd659 --- /dev/null +++ b/app/views/admin/applications/_delete_form.html.haml @@ -0,0 +1,4 @@ +- submit_btn_css ||= 'btn btn-link btn-remove btn-sm' += form_tag admin_application_path(application) do + %input{:name => "_method", :type => "hidden", :value => "delete"}/ + = submit_tag 'Destroy', onclick: "return confirm('Are you sure?')", class: submit_btn_css \ No newline at end of file diff --git a/app/views/admin/applications/_form.html.haml b/app/views/admin/applications/_form.html.haml new file mode 100644 index 0000000000..fa4e6335c7 --- /dev/null +++ b/app/views/admin/applications/_form.html.haml @@ -0,0 +1,26 @@ += form_for [:admin, @application], url: @url, html: {class: 'form-horizontal', role: 'form'} do |f| + - if application.errors.any? + .alert.alert-danger + %button{ type: "button", class: "close", "data-dismiss" => "alert"} × + - application.errors.full_messages.each do |msg| + %p= msg + = content_tag :div, class: 'form-group' do + = f.label :name, class: 'col-sm-2 control-label' + .col-sm-10 + = f.text_field :name, class: 'form-control' + = doorkeeper_errors_for application, :name + = content_tag :div, class: 'form-group' do + = f.label :redirect_uri, class: 'col-sm-2 control-label' + .col-sm-10 + = f.text_area :redirect_uri, class: 'form-control' + = doorkeeper_errors_for application, :redirect_uri + %span.help-block + Use one line per URI + - if Doorkeeper.configuration.native_redirect_uri + %span.help-block + Use + %code= Doorkeeper.configuration.native_redirect_uri + for local tests + .form-actions + = f.submit 'Submit', class: "btn btn-primary wide" + = link_to "Cancel", admin_applications_path, class: "btn btn-default" diff --git a/app/views/admin/applications/edit.html.haml b/app/views/admin/applications/edit.html.haml new file mode 100644 index 0000000000..e408ae2f29 --- /dev/null +++ b/app/views/admin/applications/edit.html.haml @@ -0,0 +1,3 @@ +%h3.page-title Edit application +- @url = admin_application_path(@application) += render 'form', application: @application \ No newline at end of file diff --git a/app/views/admin/applications/index.html.haml b/app/views/admin/applications/index.html.haml new file mode 100644 index 0000000000..d550278710 --- /dev/null +++ b/app/views/admin/applications/index.html.haml @@ -0,0 +1,22 @@ +%h3.page-title + System OAuth applications +%p.light + System OAuth application does not belong to certain user and can be managed only by admins +%hr +%p= link_to 'New Application', new_admin_application_path, class: 'btn btn-success' +%table.table.table-striped + %thead + %tr + %th Name + %th Callback URL + %th Clients + %th + %th + %tbody.oauth-applications + - @applications.each do |application| + %tr{:id => "application_#{application.id}"} + %td= link_to application.name, admin_application_path(application) + %td= application.redirect_uri + %td= application.access_tokens.map(&:resource_owner_id).uniq.count + %td= link_to 'Edit', edit_admin_application_path(application), class: 'btn btn-link' + %td= render 'delete_form', application: application diff --git a/app/views/admin/applications/new.html.haml b/app/views/admin/applications/new.html.haml new file mode 100644 index 0000000000..7c62425f19 --- /dev/null +++ b/app/views/admin/applications/new.html.haml @@ -0,0 +1,3 @@ +%h3.page-title New application +- @url = admin_applications_path += render 'form', application: @application \ No newline at end of file diff --git a/app/views/admin/applications/show.html.haml b/app/views/admin/applications/show.html.haml new file mode 100644 index 0000000000..2abe390ce1 --- /dev/null +++ b/app/views/admin/applications/show.html.haml @@ -0,0 +1,26 @@ +%h3.page-title + Application: #{@application.name} + + +%table.table + %tr + %td + Application Id + %td + %code#application_id= @application.uid + %tr + %td + Secret: + %td + %code#secret= @application.secret + + %tr + %td + Callback url + %td + - @application.redirect_uri.split.each do |uri| + %div + %span.monospace= uri +.form-actions + = link_to 'Edit', edit_admin_application_path(@application), class: 'btn btn-primary wide pull-left' + = render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger prepend-left-10' diff --git a/app/views/admin/background_jobs/show.html.haml b/app/views/admin/background_jobs/show.html.haml index c79d1280dd..4ef8e878a7 100644 --- a/app/views/admin/background_jobs/show.html.haml +++ b/app/views/admin/background_jobs/show.html.haml @@ -8,7 +8,7 @@ .panel-body - if @sidekiq_processes.empty? %h4.cred - %i.icon-warning-sign + %i.fa.fa-exclamation-triangle There are no running sidekiq processes. Please restart GitLab - else %table.table @@ -25,20 +25,20 @@ - next unless process.match(/(sidekiq \d+\.\d+\.\d+.+$)/) - data = process.strip.split(' ') %tr - %td= Settings.gitlab.user + %td= gitlab_config.user - 5.times do %td= data.shift %td= data.join(' ') .clearfix %p - %i.icon-exclamation-sign + %i.fa.fa-exclamation-circle If '[25 of 25 busy]' is shown, restart GitLab with 'sudo service gitlab reload'. %p - %i.icon-exclamation-sign - If more than one sidekiq process is listed, stop GitLab, kill the remaining sidekiq processes (sudo pkill -u #{Settings.gitlab.user} -f sidekiq) and restart GitLab. + %i.fa.fa-exclamation-circle + If more than one sidekiq process is listed, stop GitLab, kill the remaining sidekiq processes (sudo pkill -u #{gitlab_config.user} -f sidekiq) and restart GitLab. .panel.panel-default - %iframe{src: sidekiq_path, width: '100%', height: 900, style: "border: none"} + %iframe{src: sidekiq_path, width: '100%', height: 970, style: "border: none"} diff --git a/app/views/admin/broadcast_messages/index.html.haml b/app/views/admin/broadcast_messages/index.html.haml index c58ca2c9a3..7e29311bf4 100644 --- a/app/views/admin/broadcast_messages/index.html.haml +++ b/app/views/admin/broadcast_messages/index.html.haml @@ -3,7 +3,7 @@ %p.light Broadcast messages are displayed for every user and can be used to notify users about scheduled maintenance, recent upgrades and more. .broadcast-message-preview - %i.icon-bullhorn + %i.fa.fa-bullhorn %span Your message here = form_for [:admin, @broadcast_message], html: { class: 'broadcast-message-form form-horizontal'} do |f| @@ -21,13 +21,11 @@ .form-group.js-toggle-colors-container.hide = f.label :color, "Background Color", class: 'control-label' .col-sm-10 - = f.text_field :color, placeholder: "#AA33EE", class: "form-control" - .light 6 character hex values starting with a # sign. + = f.color_field :color, value: "#AA33EE", class: "form-control" .form-group.js-toggle-colors-container.hide = f.label :font, "Font Color", class: 'control-label' .col-sm-10 - = f.text_field :font, placeholder: "#224466", class: "form-control" - .light 6 character hex values starting with a # sign. + = f.color_field :font, value: "#224466", class: "form-control" .form-group = f.label :starts_at, class: 'control-label' .col-sm-10.datetime-controls @@ -52,8 +50,8 @@ %strong #{broadcast_message.ends_at.to_s(:short)}   - = link_to [:admin, broadcast_message], method: :delete, remote: true, class: 'remove-row btn btn-tiny' do - %i.icon-remove.cred + = link_to [:admin, broadcast_message], method: :delete, remote: true, class: 'remove-row btn btn-xs' do + %i.fa.fa-times.cred .message= broadcast_message.message diff --git a/app/views/admin/dashboard/index.html.haml b/app/views/admin/dashboard/index.html.haml index 41760f8b1e..d1c586328a 100644 --- a/app/views/admin/dashboard/index.html.haml +++ b/app/views/admin/dashboard/index.html.haml @@ -1,69 +1,7 @@ -%h3.page-title - Admin area -%p.light - You can manage projects, users and other GitLab data from here. -%hr .admin-dashboard .row - .col-sm-4 - .light-well - %h4 Projects - .data - = link_to admin_projects_path do - %h1= Project.count - %hr - = link_to 'New Project', new_project_path, class: "btn btn-new" - .col-sm-4 - .light-well - %h4 Users - .data - = link_to admin_users_path do - %h1= User.count - %hr - = link_to 'New User', new_admin_user_path, class: "btn btn-new" - .col-sm-4 - .light-well - %h4 Groups - .data - = link_to admin_groups_path do - %h1= Group.count - %hr - = link_to 'New Group', new_admin_group_path, class: "btn btn-new" - - .row.prepend-top-10 .col-md-4 - %h4 Latest projects - %hr - - @projects.each do |project| - %p - = link_to project.name_with_namespace, [:admin, project], class: 'str-truncated' - %span.light.pull-right - #{time_ago_with_tooltip(project.created_at)} - - .col-md-4 - %h4 Latest users - %hr - - @users.each do |user| - %p - = link_to [:admin, user], class: 'str-truncated' do - = user.name - %span.light.pull-right - #{time_ago_with_tooltip(user.created_at)} - - .col-md-4 - %h4 Latest groups - %hr - - @groups.each do |group| - %p - = link_to [:admin, group], class: 'str-truncated' do - = group.name - %span.light.pull-right - #{time_ago_with_tooltip(group.created_at)} - - %br - .row - .col-md-4 - %h4 Stats + %h4 Statistics %hr %p Forks @@ -94,9 +32,9 @@ %span.light.pull-right = Milestone.count %p - Monthly active users + Active Users %span.light.pull-right - = User.where("current_sign_in_at > ?", 30.days.ago).count + = User.active.count .col-md-4 %h4 Features @@ -104,7 +42,7 @@ %p Sign up %span.light.pull-right - = boolean_to_icon gitlab_config.signup_enabled + = boolean_to_icon signup_enabled? %p LDAP %span.light.pull-right @@ -112,7 +50,7 @@ %p Gravatar %span.light.pull-right - = boolean_to_icon Gitlab.config.gravatar.enabled + = boolean_to_icon gravatar_enabled? %p OmniAuth %span.light.pull-right @@ -141,3 +79,59 @@ Rails %span.pull-right #{Rails::VERSION::STRING} + %hr + .row + .col-sm-4 + .light-well + %h4 Projects + .data + = link_to admin_namespaces_projects_path do + %h1= Project.count + %hr + = link_to('New Project', new_project_path, class: "btn btn-new") + .col-sm-4 + .light-well + %h4 Users + .data + = link_to admin_users_path do + %h1= User.count + %hr + = link_to 'New User', new_admin_user_path, class: "btn btn-new" + .col-sm-4 + .light-well + %h4 Groups + .data + = link_to admin_groups_path do + %h1= Group.count + %hr + = link_to 'New Group', new_admin_group_path, class: "btn btn-new" + + .row.prepend-top-10 + .col-md-4 + %h4 Latest projects + %hr + - @projects.each do |project| + %p + = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project], class: 'str-truncated' + %span.light.pull-right + #{time_ago_with_tooltip(project.created_at)} + + .col-md-4 + %h4 Latest users + %hr + - @users.each do |user| + %p + = link_to [:admin, user], class: 'str-truncated' do + = user.name + %span.light.pull-right + #{time_ago_with_tooltip(user.created_at)} + + .col-md-4 + %h4 Latest groups + %hr + - @groups.each do |group| + %p + = link_to [:admin, group], class: 'str-truncated' do + = group.name + %span.light.pull-right + #{time_ago_with_tooltip(group.created_at)} diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml new file mode 100644 index 0000000000..2ae83ab95f --- /dev/null +++ b/app/views/admin/deploy_keys/index.html.haml @@ -0,0 +1,27 @@ +.panel.panel-default + .panel-heading + Public deploy keys (#{@deploy_keys.count}) + .panel-head-actions + = link_to 'New Deploy Key', new_admin_deploy_key_path, class: "btn btn-new btn-sm" + - if @deploy_keys.any? + %table.table + %thead.panel-heading + %tr + %th Title + %th Fingerprint + %th Added at + %th + %tbody + - @deploy_keys.each do |deploy_key| + %tr + %td + = link_to admin_deploy_key_path(deploy_key) do + %strong= deploy_key.title + %td + %span + (#{deploy_key.fingerprint}) + %td + %span.cgray + added #{time_ago_with_tooltip(deploy_key.created_at)} + %td + = link_to 'Remove', admin_deploy_key_path(deploy_key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-sm btn-remove delete-key pull-right" diff --git a/app/views/admin/deploy_keys/new.html.haml b/app/views/admin/deploy_keys/new.html.haml new file mode 100644 index 0000000000..c00049424c --- /dev/null +++ b/app/views/admin/deploy_keys/new.html.haml @@ -0,0 +1,26 @@ +%h3.page-title New public deploy key +%hr + +%div + = form_for [:admin, @deploy_key], html: { class: 'deploy-key-form form-horizontal' } do |f| + -if @deploy_key.errors.any? + .alert.alert-danger + %ul + - @deploy_key.errors.full_messages.each do |msg| + %li= msg + + .form-group + = f.label :title, class: "control-label" + .col-sm-10= f.text_field :title, class: 'form-control' + .form-group + = f.label :key, class: "control-label" + .col-sm-10 + %p.light + Paste a machine public key here. Read more about how to generate it + = link_to "here", help_page_path("ssh", "README") + = f.text_area :key, class: "form-control thin_area", rows: 5 + + .form-actions + = f.submit 'Create', class: "btn-create btn" + = link_to "Cancel", admin_deploy_keys_path, class: "btn btn-cancel" + diff --git a/app/views/admin/deploy_keys/show.html.haml b/app/views/admin/deploy_keys/show.html.haml new file mode 100644 index 0000000000..cfa2adf92e --- /dev/null +++ b/app/views/admin/deploy_keys/show.html.haml @@ -0,0 +1,34 @@ +.row + .col-md-4 + .panel.panel-default + .panel-heading + Deploy Key + %ul.well-list + %li + %span.light Title: + %strong= @deploy_key.title + %li + %span.light Created on: + %strong= @deploy_key.created_at.stamp("Aug 21, 2011") + + .panel.panel-default + .panel-heading Projects (#{@deploy_key.deploy_keys_projects.count}) + - if @deploy_key.deploy_keys_projects.any? + %ul.well-list + - @deploy_key.projects.each do |project| + %li + %span + %strong + = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project] + .pull-right + = link_to disable_namespace_project_deploy_key_path(project.namespace, project, @deploy_key), data: { confirm: "Are you sure?" }, method: :put, class: "btn-xs btn btn-remove", title: 'Remove deploy key from project' do + %i.fa.fa-times.fa-inverse + + .col-md-8 + %p + %span.light Fingerprint: + %strong= @deploy_key.fingerprint + %pre.well-pre + = @deploy_key.key + .pull-right + = link_to 'Remove', admin_deploy_key_path(@deploy_key), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key" diff --git a/app/views/admin/groups/_form.html.haml b/app/views/admin/groups/_form.html.haml index 31990ee593..9e7751830a 100644 --- a/app/views/admin/groups/_form.html.haml +++ b/app/views/admin/groups/_form.html.haml @@ -2,55 +2,25 @@ - if @group.errors.any? .alert.alert-danger %span= @group.errors.full_messages.first - .form-group.group_name_holder - = f.label :name, class: 'control-label' do - Group name - .col-sm-10 - = f.text_field :name, placeholder: "Example Group", class: "form-control" - .form-group.group-description-holder - = f.label :description, "Details", class: 'control-label' - .col-sm-10 - = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4 + = render 'shared/group_form', f: f .form-group.group-description-holder = f.label :avatar, "Group avatar", class: 'control-label' .col-sm-10 - %a.choose-btn.btn.btn-small.js-choose-group-avatar-button - %i.icon-paper-clip - %span Choose File ... -   - %span.file_name.js-avatar-filename File name... - = f.file_field :avatar, class: "js-group-avatar-input hidden" - .light The maximum file size allowed is 100KB. + = render 'shared/choose_group_avatar_button', f: f - if @group.new_record? .form-group .col-sm-2 .col-sm-10 - .bs-callout.bs-callout-info - %ul - %li A group is a collection of several projects - %li Groups are private by default - %li Members of a group may only view projects they have permission to access - %li Group project URLs are prefixed with the group namespace - %li Existing projects may be moved into a group + .alert.alert-info + = render 'shared/group_tips' .form-actions = f.submit 'Create group', class: "btn btn-create" = link_to 'Cancel', admin_groups_path, class: "btn btn-cancel" - else - .form-group.group_name_holder - = f.label :path, class: 'control-label' do - %span Group path - .col-sm-10 - = f.text_field :path, placeholder: "example-group", class: "form-control danger" - .bs-callout.bs-callout-danger - %ul - %li Changing group path can have unintended side effects. - %li Renaming group path will rename directory for all related projects - %li It will change web url for access group and group projects. - %li It will change the git path to repositories under this group. .form-actions = f.submit 'Save changes', class: "btn btn-primary" = link_to 'Cancel', admin_group_path(@group), class: "btn btn-cancel" diff --git a/app/views/admin/groups/index.html.haml b/app/views/admin/groups/index.html.haml index 9a0d596792..4c53ff5570 100644 --- a/app/views/admin/groups/index.html.haml +++ b/app/views/admin/groups/index.html.haml @@ -8,9 +8,30 @@ %hr = form_tag admin_groups_path, method: :get, class: 'form-inline' do + = hidden_field_tag :sort, @sort .form-group = text_field_tag :name, params[:name], class: "form-control input-mn-300" - = submit_tag "Search", class: "btn submit btn-primary" + = button_tag "Search", class: "btn submit btn-primary" + + .pull-right + .dropdown.inline + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %span.light sort: + - if @sort.present? + = sort_options_hash[@sort] + - else + = sort_title_recently_created + %b.caret + %ul.dropdown-menu + %li + = link_to admin_groups_path(sort: sort_value_recently_created) do + = sort_title_recently_created + = link_to admin_groups_path(sort: sort_value_oldest_created) do + = sort_title_oldest_created + = link_to admin_groups_path(sort: sort_value_recently_updated) do + = sort_title_recently_updated + = link_to admin_groups_path(sort: sort_value_oldest_updated) do + = sort_title_oldest_updated %hr @@ -19,12 +40,12 @@ %li .clearfix .pull-right.prepend-top-10 - = link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-small" - = link_to 'Destroy', [:admin, group], data: {confirm: "REMOVE #{group.name}? Are you sure?"}, method: :delete, class: "btn btn-small btn-remove" + = link_to 'Edit', edit_admin_group_path(group), id: "edit_#{dom_id(group)}", class: "btn btn-sm" + = link_to 'Destroy', [:admin, group], data: {confirm: "REMOVE #{group.name}? Are you sure?"}, method: :delete, class: "btn btn-sm btn-remove" %h4 = link_to [:admin, group] do - %i.icon-folder-close + %i.fa.fa-folder = group.name → diff --git a/app/views/admin/groups/show.html.haml b/app/views/admin/groups/show.html.haml index 8634f46053..14996dcd6a 100644 --- a/app/views/admin/groups/show.html.haml +++ b/app/views/admin/groups/show.html.haml @@ -2,7 +2,7 @@ Group: #{@group.name} = link_to edit_admin_group_path(@group), class: "btn pull-right" do - %i.icon-edit + %i.fa.fa-pencil-square-o Edit %hr .row @@ -12,7 +12,7 @@ Group info: %ul.well-list %li - = image_tag group_icon(@group.path), class: "avatar s60" + = image_tag group_icon(@group), class: "avatar s60" %li %span.light Name: %strong= @group.name @@ -41,7 +41,7 @@ - @projects.each do |project| %li %strong - = link_to project.name_with_namespace, [:admin, project] + = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project] %span.label.label-gray = repository_size(project) %span.pull-right.light @@ -58,29 +58,34 @@ Read more about project permissions %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink" - = form_tag project_teams_update_admin_group_path(@group), id: "new_team_member", class: "bulk_import", method: :put do + = form_tag members_update_admin_group_path(@group), id: "new_project_member", class: "bulk_import", method: :put do %div - = users_select_tag(:user_ids, multiple: true) + = users_select_tag(:user_ids, multiple: true, email_user: true) %div.prepend-top-10 - = select_tag :group_access, options_for_select(UsersGroup.group_access_roles), class: "project-access-select select2" + = select_tag :access_level, options_for_select(GroupMember.access_level_roles), class: "project-access-select select2" %hr - = submit_tag 'Add users into group', class: "btn btn-create" + = button_tag 'Add users to group', class: "btn btn-create" .panel.panel-default .panel-heading %h3.panel-title Members %span.badge - #{@group.users_groups.count} + #{@group.group_members.count} %ul.well-list.group-users-list - @members.each do |member| - user = member.user - %li{class: dom_class(user)} + %li{class: dom_class(member), id: (dom_id(user) if user)} .list-item-name - %strong - = link_to user.name, admin_user_path(user) + - if user + %strong + = link_to user.name, admin_user_path(user) + - else + %strong + = member.invite_email + (invited) %span.pull-right.light = member.human_access - = link_to group_users_group_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do - %i.icon-minus.icon-white + = link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do + %i.fa.fa-minus.fa-inverse .panel-footer = paginate @members, param_name: 'members_page', theme: 'gitlab' diff --git a/app/views/admin/hooks/index.html.haml b/app/views/admin/hooks/index.html.haml index 0c5db0805f..7a9dc113f2 100644 --- a/app/views/admin/hooks/index.html.haml +++ b/app/views/admin/hooks/index.html.haml @@ -33,5 +33,5 @@ %strong= hook.url .pull-right - = link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-small" - = link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-remove btn-small" + = link_to 'Test Hook', admin_hook_test_path(hook), class: "btn btn-sm" + = link_to 'Remove', admin_hook_path(hook), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm" diff --git a/app/views/admin/keys/show.html.haml b/app/views/admin/keys/show.html.haml new file mode 100644 index 0000000000..5b23027b3a --- /dev/null +++ b/app/views/admin/keys/show.html.haml @@ -0,0 +1 @@ += render "profiles/keys/key_details", admin: true diff --git a/app/views/admin/logs/show.html.haml b/app/views/admin/logs/show.html.haml index cce8aeb02c..384c6ee9af 100644 --- a/app/views/admin/logs/show.html.haml +++ b/app/views/admin/logs/show.html.haml @@ -1,68 +1,25 @@ +- loggers = [Gitlab::GitLogger, Gitlab::AppLogger, + Gitlab::ProductionLogger, Gitlab::SidekiqLogger] %ul.nav.nav-tabs.log-tabs - %li.active - = link_to "githost.log", "#githost", 'data-toggle' => 'tab' - %li - = link_to "application.log", "#application", 'data-toggle' => 'tab' - %li - = link_to "production.log", "#production", 'data-toggle' => 'tab' - %li - = link_to "sidekiq.log", "#sidekiq", 'data-toggle' => 'tab' - + - loggers.each do |klass| + %li{ class: (klass == Gitlab::GitLogger ? 'active' : '') } + = link_to klass::file_name, "##{klass::file_name_noext}", + 'data-toggle' => 'tab' %p.light To prevent performance issues admin logs output the last 2000 lines .tab-content - .tab-pane.active#githost - .file-holder#README - .file-title - %i.icon-file - githost.log - .pull-right - = link_to '#', class: 'log-bottom' do - %i.icon-arrow-down - Scroll down - .file-content.logs - %ol - - Gitlab::GitLogger.read_latest.each do |line| - %li - %p= line - .tab-pane#application - .file-holder#README - .file-title - %i.icon-file - application.log - .pull-right - = link_to '#', class: 'log-bottom' do - %i.icon-arrow-down - Scroll down - .file-content.logs - %ol - - Gitlab::AppLogger.read_latest.each do |line| - %li - %p= line - .tab-pane#production - .file-holder#README - .file-title - %i.icon-file - production.log - .pull-right - = link_to '#', class: 'log-bottom' do - %i.icon-arrow-down - Scroll down - .file-content.logs - %ol - - Gitlab::Logger.read_latest_for('production.log').each do |line| - %li - %p= line - .tab-pane#sidekiq - .file-holder#README - .file-title - %i.icon-file - sidekiq.log - .pull-right - = link_to '#', class: 'log-bottom' do - %i.icon-arrow-down - Scroll down - .file-content.logs - %ol - - Gitlab::Logger.read_latest_for('sidekiq.log').each do |line| - %li - %p= line + - loggers.each do |klass| + .tab-pane{ class: (klass == Gitlab::GitLogger ? 'active' : ''), + id: klass::file_name_noext } + .file-holder#README + .file-title + %i.fa.fa-file + = klass::file_name + .pull-right + = link_to '#', class: 'log-bottom' do + %i.fa.fa-arrow-down + Scroll down + .file-content.logs + %ol + - klass.read_latest.each do |line| + %li + %p= line diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index 5ca6090f8d..3bbe10bc27 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -1,7 +1,9 @@ .row - .col-md-3 + = link_to '#aside', class: 'show-aside' do + %i.fa.fa-angle-left + %aside.col-md-3 .admin-filter - = form_tag admin_projects_path, method: :get, class: '' do + = form_tag admin_namespaces_projects_path, method: :get, class: '' do .form-group = label_tag :name, 'Name:' = text_field_tag :name, params[:name], class: "form-control" @@ -13,15 +15,13 @@ .form-group %strong Activity .checkbox - = label_tag :with_push, 'Not empty' - = check_box_tag :with_push, 1, params[:with_push] -   - %span.light Projects with push events + = label_tag :with_push do + = check_box_tag :with_push, 1, params[:with_push] + %span Projects with push events .checkbox - = label_tag :abandoned, 'Abandoned' - = check_box_tag :abandoned, 1, params[:abandoned] -   - %span.light No activity over 6 month + = label_tag :abandoned do + = check_box_tag :abandoned, 1, params[:abandoned] + %span No activity over 6 month %fieldset %strong Visibility level: @@ -35,49 +35,47 @@ = label %hr = hidden_field_tag :sort, params[:sort] - = submit_tag "Search", class: "btn submit btn-primary" - = link_to "Reset", admin_projects_path, class: "btn btn-cancel" + = button_tag "Search", class: "btn submit btn-primary" + = link_to "Reset", admin_namespaces_projects_path, class: "btn btn-cancel" - .col-md-9 + %section.col-md-9 .panel.panel-default .panel-heading Projects (#{@projects.total_count}) .panel-head-actions .dropdown.inline - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'} %span.light sort: - if @sort.present? - = @sort.humanize + = sort_options_hash[@sort] - else - Name + = sort_title_recently_created %b.caret %ul.dropdown-menu %li - = link_to admin_projects_path(sort: nil) do - Name - = link_to admin_projects_path(sort: 'newest') do - Newest - = link_to admin_projects_path(sort: 'oldest') do - Oldest - = link_to admin_projects_path(sort: 'recently_updated') do - Recently updated - = link_to admin_projects_path(sort: 'last_updated') do - Last updated - = link_to admin_projects_path(sort: 'largest_repository') do - Largest repository - = link_to 'New Project', new_project_path, class: "btn btn-new" + = link_to admin_namespaces_projects_path(sort: sort_value_recently_created) do + = sort_title_recently_created + = link_to admin_namespaces_projects_path(sort: sort_value_oldest_created) do + = sort_title_oldest_created + = link_to admin_namespaces_projects_path(sort: sort_value_recently_updated) do + = sort_title_recently_updated + = link_to admin_namespaces_projects_path(sort: sort_value_oldest_updated) do + = sort_title_oldest_updated + = link_to admin_namespaces_projects_path(sort: sort_value_largest_repo) do + = sort_title_largest_repo + = link_to 'New Project', new_project_path, class: "btn btn-sm btn-success" %ul.well-list - @projects.each do |project| %li .list-item-name %span{ class: visibility_level_color(project.visibility_level) } = visibility_level_icon(project.visibility_level) - = link_to project.name_with_namespace, [:admin, project] + = link_to project.name_with_namespace, [:admin, project.namespace.becomes(Namespace), project] .pull-right %span.label.label-gray = repository_size(project) - = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" - = link_to 'Destroy', [project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-small btn-remove" + = link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm" + = link_to 'Destroy', [project.namespace.becomes(Namespace), project], data: { confirm: remove_project_message(project) }, method: :delete, class: "btn btn-sm btn-remove" - if @projects.blank? .nothing-here-block 0 projects matches = paginate @projects, theme: "gitlab" diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index 66a72449f4..78684c692c 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -1,7 +1,7 @@ %h3.page-title Project: #{@project.name_with_namespace} = link_to edit_project_path(@project), class: "btn pull-right" do - %i.icon-edit + %i.fa.fa-pencil-square-o Edit %hr .row @@ -42,11 +42,11 @@ %li %span.light http: %strong - = link_to @project.http_url_to_repo + = link_to @project.http_url_to_repo, project_path(@project) %li %span.light ssh: %strong - = link_to @project.ssh_url_to_repo + = link_to @project.ssh_url_to_repo, project_path(@project) - if @project.repository.exists? %li %span.light fs: @@ -68,6 +68,11 @@ %strong.cred does not exist + - if @project.archived? + %li + %span.light archived: + %strong repository is read-only + %li %span.light access: %strong @@ -79,11 +84,11 @@ .panel-heading Transfer project .panel-body - = form_for @project, url: transfer_admin_project_path(@project), method: :put, html: { class: 'form-horizontal' } do |f| + = form_for @project, url: transfer_admin_namespace_project_path(@project.namespace, @project), method: :put, html: { class: 'form-horizontal' } do |f| .form-group - = f.label :namespace_id, "Namespace", class: 'control-label' + = f.label :new_namespace_id, "Namespace", class: 'control-label' .col-sm-10 - = namespace_select_tag :namespace_id, selected: params[:namespace_id], class: 'input-large' + = namespace_select_tag :new_namespace_id, selected: params[:namespace_id], class: 'input-large' .form-group .col-sm-2 @@ -95,13 +100,13 @@ .panel.panel-default .panel-heading %strong #{@group.name} - group members (#{@group.users_groups.count}) + group members (#{@group.group_members.count}) .pull-right - = link_to admin_group_path(@group), class: 'btn btn-small' do - %i.icon-edit + = link_to admin_group_path(@group), class: 'btn btn-xs' do + %i.fa.fa-pencil-square-o %ul.well-list - @group_members.each do |member| - = render 'users_groups/users_group', member: member, show_controls: false + = render 'groups/group_members/group_member', member: member, show_controls: false .panel-footer = paginate @group_members, param_name: 'group_members_page', theme: 'gitlab' @@ -111,22 +116,27 @@ %small (#{@project.users.count}) .pull-right - = link_to project_team_index_path(@project), class: "btn btn-tiny" do - %i.icon-edit + = link_to namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-xs" do + %i.fa.fa-pencil-square-o Manage Access - %ul.well-list.team_members - - @project_members.each do |users_project| - - user = users_project.user - %li.users_project + %ul.well-list.project_members + - @project_members.each do |project_member| + - user = project_member.user + %li.project_member .list-item-name - %strong - = link_to user.name, admin_user_path(user) + - if user + %strong + = link_to user.name, admin_user_path(user) + - else + %strong + = project_member.invite_email + (invited) .pull-right - - if users_project.owner? + - if project_member.owner? %span.light Owner - else - %span.light= users_project.human_access - = link_to project_team_member_path(@project, user), data: { confirm: remove_from_project_team_message(@project, user)}, method: :delete, remote: true, class: "btn btn-small btn-remove" do - %i.icon-remove + %span.light= project_member.human_access + = link_to namespace_project_project_member_path(@project.namespace, @project, project_member), data: { confirm: remove_from_project_team_message(@project, project_member)}, method: :delete, remote: true, class: "btn btn-sm btn-remove" do + %i.fa.fa-times .panel-footer = paginate @project_members, param_name: 'project_members_page', theme: 'gitlab' diff --git a/app/views/admin/services/_form.html.haml b/app/views/admin/services/_form.html.haml new file mode 100644 index 0000000000..eb7a099bfe --- /dev/null +++ b/app/views/admin/services/_form.html.haml @@ -0,0 +1,94 @@ +%h3.page-title + = @service.title + +%p #{@service.description} template + += form_for :service, url: admin_application_settings_service_path, method: :put, html: { class: 'form-horizontal fieldset-form' } do |f| + - if @service.errors.any? + #error_explanation + .alert.alert-danger + - @service.errors.full_messages.each do |msg| + %p= msg + - if @service.help.present? + .well + = preserve do + = markdown @service.help + + .form-group + = f.label :active, "Active", class: "control-label" + .col-sm-10 + = f.check_box :active + + - if @service.supported_events.length > 1 + .form-group + = f.label :url, "Trigger", class: 'control-label' + .col-sm-10 + - if @service.supported_events.include?("push") + %div + = f.check_box :push_events, class: 'pull-left' + .prepend-left-20 + = f.label :push_events, class: 'list-label' do + %strong Push events + %p.light + This url will be triggered by a push to the repository + - if @service.supported_events.include?("tag_push") + %div + = f.check_box :tag_push_events, class: 'pull-left' + .prepend-left-20 + = f.label :tag_push_events, class: 'list-label' do + %strong Tag push events + %p.light + This url will be triggered when a new tag is pushed to the repository + - if @service.supported_events.include?("note") + %div + = f.check_box :note_events, class: 'pull-left' + .prepend-left-20 + = f.label :note_events, class: 'list-label' do + %strong Comments + %p.light + This url will be triggered when someone adds a comment + - if @service.supported_events.include?("issue") + %div + = f.check_box :issues_events, class: 'pull-left' + .prepend-left-20 + = f.label :issues_events, class: 'list-label' do + %strong Issues events + %p.light + This url will be triggered when an issue is created + - if @service.supported_events.include?("merge_request") + %div + = f.check_box :merge_requests_events, class: 'pull-left' + .prepend-left-20 + = f.label :merge_requests_events, class: 'list-label' do + %strong Merge Request events + %p.light + This url will be triggered when a merge request is created + + - @service.fields.each do |field| + - name = field[:name] + - title = field[:title] || name.humanize + - value = @service.send(name) unless field[:type] == 'password' + - type = field[:type] + - placeholder = field[:placeholder] + - choices = field[:choices] + - default_choice = field[:default_choice] + - help = field[:help] + + .form-group + = f.label name, title, class: "control-label" + .col-sm-10 + - if type == 'text' + = f.text_field name, class: "form-control", placeholder: placeholder + - elsif type == 'textarea' + = f.text_area name, rows: 5, class: "form-control", placeholder: placeholder + - elsif type == 'checkbox' + = f.check_box name + - elsif type == 'select' + = f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" } + - elsif type == 'password' + = f.password_field name, class: 'form-control' + - if help + %span.help-block= help + + .form-actions + = f.submit 'Save', class: 'btn btn-save' diff --git a/app/views/admin/services/edit.html.haml b/app/views/admin/services/edit.html.haml new file mode 100644 index 0000000000..bcc5832792 --- /dev/null +++ b/app/views/admin/services/edit.html.haml @@ -0,0 +1 @@ += render 'form' diff --git a/app/views/admin/services/index.html.haml b/app/views/admin/services/index.html.haml new file mode 100644 index 0000000000..0093fb9776 --- /dev/null +++ b/app/views/admin/services/index.html.haml @@ -0,0 +1,22 @@ +%h3.page-title Service templates +%p.light Service template allows you to set default values for project services + +%table.table + %thead + %tr + %th + %th Service + %th Description + %th Last edit + - @services.sort_by(&:title).each do |service| + %tr + %td + = icon("copy", class: 'clgray') + %td + = link_to edit_admin_application_settings_service_path(service.id) do + %strong= service.title + %td + = service.description + %td.light + = time_ago_in_words service.updated_at + ago diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml index b8a1822444..25c1730ef7 100644 --- a/app/views/admin/users/index.html.haml +++ b/app/views/admin/users/index.html.haml @@ -1,5 +1,7 @@ .row - .col-md-3 + = link_to '#aside', class: 'show-aside' do + %i.fa.fa-angle-left + %aside.col-md-3 .admin-filter %ul.nav.nav-pills.nav-stacked %li{class: "#{'active' unless params[:filter]}"} @@ -22,25 +24,50 @@ = form_tag admin_users_path, method: :get, class: 'form-inline' do .form-group = search_field_tag :name, params[:name], placeholder: 'Name, email or username', class: 'form-control' - = button_tag type: 'submit', class: 'btn btn-primary' do - %i.icon-search + = button_tag class: 'btn btn-primary' do + %i.fa.fa-search %hr = link_to 'Reset', admin_users_path, class: "btn btn-cancel" - .col-md-9 + %section.col-md-9 .panel.panel-default .panel-heading Users (#{@users.total_count}) .panel-head-actions - = link_to 'New User', new_admin_user_path, class: "btn btn-new" + .dropdown.inline + %a.dropdown-toggle.btn.btn-sm{href: '#', "data-toggle" => "dropdown"} + %span.light sort: + - if @sort.present? + = sort_options_hash[@sort] + - else + = sort_title_name + %b.caret + %ul.dropdown-menu + %li + = link_to admin_users_path(sort: sort_value_name) do + = sort_title_name + = link_to admin_users_path(sort: sort_value_recently_signin) do + = sort_title_recently_signin + = link_to admin_users_path(sort: sort_value_oldest_signin) do + = sort_title_oldest_signin + = link_to admin_users_path(sort: sort_value_recently_created) do + = sort_title_recently_created + = link_to admin_users_path(sort: sort_value_oldest_created) do + = sort_title_oldest_created + = link_to admin_users_path(sort: sort_value_recently_updated) do + = sort_title_recently_updated + = link_to admin_users_path(sort: sort_value_oldest_updated) do + = sort_title_oldest_updated + + = link_to 'New User', new_admin_user_path, class: "btn btn-new btn-sm" %ul.well-list - @users.each do |user| %li .list-item-name - if user.blocked? - %i.icon-lock.cred + %i.fa.fa-lock.cred - else - %i.icon-user.cgreen + %i.fa.fa-user.cgreen = link_to user.name, [:admin, user] - if user.admin? %strong.cred (Admin) @@ -48,14 +75,14 @@ %span.cred It's you! .pull-right %span.light - %i.icon-envelope + %i.fa.fa-envelope = mail_to user.email, user.email, class: 'light'   - = link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: "btn btn-small" + = link_to 'Edit', edit_admin_user_path(user), id: "edit_#{dom_id(user)}", class: "btn btn-sm" - unless user == current_user - if user.blocked? - = link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn btn-small success" + = link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn btn-sm success" - else - = link_to 'Block', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-small btn-remove" - = link_to 'Destroy', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All tickets linked to this user will also be removed! Maybe block the user instead? Are you sure?" }, method: :delete, class: "btn btn-small btn-remove" + = link_to 'Block', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-sm btn-remove" + = link_to 'Destroy', [:admin, user], data: { confirm: "USER #{user.name} WILL BE REMOVED! All tickets linked to this user will also be removed! Maybe block the user instead? Are you sure?" }, method: :delete, class: "btn btn-sm btn-remove" = paginate @users, theme: "gitlab" diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml index 3c30ccd78b..3524f04c5e 100644 --- a/app/views/admin/users/show.html.haml +++ b/app/views/admin/users/show.html.haml @@ -8,7 +8,7 @@ .pull-right = link_to edit_admin_user_path(@user), class: "btn btn-grouped" do - %i.icon-edit + %i.fa.fa-pencil-square-o Edit %hr %ul.nav.nav-tabs @@ -20,6 +20,8 @@ %a{"data-toggle" => "tab", href: "#groups"} Groups %li %a{"data-toggle" => "tab", href: "#projects"} Projects + %li + %a{"data-toggle" => "tab", href: "#ssh-keys"} SSH keys .tab-content #account.tab-pane.active @@ -44,8 +46,8 @@ %li %span.light Secondary email: %strong= email.email - = link_to remove_email_admin_user_path(@user, email), data: { confirm: "Are you sure you want to remove #{email.email}?" }, method: :delete, class: "btn-tiny btn btn-remove pull-right", title: 'Remove secondary email', id: "remove_email_#{email.id}" do - %i.icon-remove + = link_to remove_email_admin_user_path(@user, email), data: { confirm: "Are you sure you want to remove #{email.email}?" }, method: :delete, class: "btn-xs btn btn-remove pull-right", title: 'Remove secondary email', id: "remove_email_#{email.id}" do + %i.fa.fa-times %li %span.light Can create groups: @@ -70,6 +72,14 @@ %strong.cred No + %li + %span.light Current sign-in at: + %strong + - if @user.current_sign_in_at + = @user.current_sign_in_at.stamp("Nov 12, 2031") + - else + never + %li %span.light Last sign-in at: %strong @@ -78,11 +88,16 @@ - else never + %li + %span.light Sign-in count: + %strong + = @user.sign_in_count + - if @user.ldap_user? %li %span.light LDAP uid: %strong - = @user.extern_uid + = @user.ldap_identity.extern_uid - if @user.created_by %li @@ -93,45 +108,49 @@ .col-md-6 - unless @user == current_user - if @user.blocked? - .alert.alert-info - %h4 This user is blocked - %p Blocking user has the following effects: - %ul - %li User will not be able to login - %li User will not be able to access git repositories - %li User will be removed from joined projects and groups - %li Personal projects will be left - %li Owned groups will be left - %br - = link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn btn-new", data: { confirm: 'Are you sure?' } + .panel.panel-info + .panel-heading + This user is blocked + .panel-body + %p Blocking user has the following effects: + %ul + %li User will not be able to login + %li User will not be able to access git repositories + %li Personal projects will be left + %li Owned groups will be left + %br + = link_to 'Unblock user', unblock_admin_user_path(@user), method: :put, class: "btn btn-info", data: { confirm: 'Are you sure?' } - else - .alert.alert-warning - %h4 Block this user - %p Blocking user has the following effects: - %ul - %li User will not be able to login - %li User will not be able to access git repositories - %li User will be removed from joined projects and groups - %li Personal projects will be left - %li Owned groups will be left - %br - = link_to 'Block user', block_admin_user_path(@user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put, class: "btn btn-remove" + .panel.panel-warning + .panel-heading + Block this user + .panel-body + %p Blocking user has the following effects: + %ul + %li User will not be able to login + %li User will not be able to access git repositories + %li User will be removed from joined projects and groups + %li Personal projects will be left + %li Owned groups will be left + %br + = link_to 'Block user', block_admin_user_path(@user), data: { confirm: 'USER WILL BE BLOCKED! Are you sure?' }, method: :put, class: "btn btn-warning" - .alert.alert-danger - %h4 + .panel.panel-danger + .panel-heading Remove user - %p Deleting a user has the following effects: - %ul - %li All user content like authored issues, snippets, comments will be removed - - rp = @user.personal_projects.count - - unless rp.zero? - %li #{pluralize rp, 'personal project'} will be removed and cannot be restored - - if @user.solo_owned_groups.present? - %li - Next groups with all content will be removed: - %strong #{@user.solo_owned_groups.map(&:name).join(', ')} - %br - = link_to 'Remove user', [:admin, @user], data: { confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-remove" + .panel-body + %p Deleting a user has the following effects: + %ul + %li All user content like authored issues, snippets, comments will be removed + - rp = @user.personal_projects.count + - unless rp.zero? + %li #{pluralize rp, 'personal project'} will be removed and cannot be restored + - if @user.solo_owned_groups.present? + %li + Next groups with all content will be removed: + %strong #{@user.solo_owned_groups.map(&:name).join(', ')} + %br + = link_to 'Remove user', [:admin, @user], data: { confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-remove" #profile.tab-pane .row @@ -151,20 +170,20 @@ = render 'users/profile', user: @user #groups.tab-pane - - if @user.users_groups.present? + - if @user.group_members.present? .panel.panel-default .panel-heading Groups: %ul.well-list - - @user.users_groups.each do |user_group| - - group = user_group.group - %li.users_group - %span{class: ("list-item-name" unless user_group.owner?)} + - @user.group_members.each do |group_member| + - group = group_member.group + %li.group_member + %span{class: ("list-item-name" unless group_member.owner?)} %strong= link_to group.name, admin_group_path(group) .pull-right - %span.light= user_group.human_access - - unless user_group.owner? - = link_to group_users_group_path(group, user_group), data: { confirm: remove_user_from_group_message(group, @user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do - %i.icon-remove.icon-white + %span.light= group_member.human_access + - unless group_member.owner? + = link_to group_group_member_path(group, group_member), data: { confirm: remove_user_from_group_message(group, group_member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do + %i.fa.fa-times.fa-inverse - else .nothing-here-block This user has no groups. @@ -188,19 +207,21 @@ .panel-heading Joined projects (#{@joined_projects.count}) %ul.well-list - @joined_projects.sort_by(&:name_with_namespace).each do |project| - - tm = project.team.find_tm(@user.id) - %li.users_project + - member = project.team.find_member(@user.id) + %li.project_member .list-item-name - = link_to admin_project_path(project), class: dom_class(project) do + = link_to admin_namespace_project_path(project.namespace, project), class: dom_class(project) do = project.name_with_namespace - - if tm + - if member .pull-right - - if tm.owner? + - if member.owner? %span.light Owner - else - %span.light= tm.human_access + %span.light= member.human_access - - if tm.respond_to? :project - = link_to project_team_member_path(project, @user), data: { confirm: remove_from_project_team_message(project, @user) }, remote: true, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from project' do - %i.icon-remove + - if member.respond_to? :project + = link_to namespace_project_project_member_path(project.namespace, project, member), data: { confirm: remove_from_project_team_message(project, member) }, remote: true, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove user from project' do + %i.fa.fa-times + #ssh-keys.tab-pane + = render 'profiles/keys/key_table', admin: true diff --git a/app/views/dashboard/_activities.html.haml b/app/views/dashboard/_activities.html.haml index fdf96dd6f5..c1fc1602d0 100644 --- a/app/views/dashboard/_activities.html.haml +++ b/app/views/dashboard/_activities.html.haml @@ -1,9 +1,4 @@ = render "events/event_last_push", event: @last_push = render 'shared/event_filter' - -- if @events.any? - .content_list -- else - .nothing-here-block Projects activity will be displayed here - +.content_list = spinner diff --git a/app/views/dashboard/_groups.html.haml b/app/views/dashboard/_groups.html.haml deleted file mode 100644 index cb9c18b796..0000000000 --- a/app/views/dashboard/_groups.html.haml +++ /dev/null @@ -1,20 +0,0 @@ -.panel.panel-default - .panel-heading.clearfix - = search_field_tag :filter_group, nil, placeholder: 'Filter by name', class: 'dash-filter form-control' - - if current_user.can_create_group? - %span.pull-right - = link_to new_group_path, class: "btn btn-new" do - %i.icon-plus - New group - %ul.well-list.dash-list - - groups.each do |group| - %li.group-row - = link_to group_path(id: group.path), class: dom_class(group) do - = image_tag group_icon(group.path), class: "avatar s24" - %span.group-name.filter-title - = truncate(group.name, length: 35) - %span.arrow - %i.icon-angle-right - - if groups.blank? - %li - .nothing-here-block You have no groups yet. diff --git a/app/views/dashboard/_project.html.haml b/app/views/dashboard/_project.html.haml deleted file mode 100644 index e326bee53a..0000000000 --- a/app/views/dashboard/_project.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -= link_to project_path(project), class: dom_class(project) do - .dash-project-access-icon - = visibility_level_icon(project.visibility_level) - %span.str-truncated - %span.namespace-name - - if project.namespace - = project.namespace.human_name - \/ - %span.project-name.filter-title - = project.name - %span.arrow - %i.icon-angle-right diff --git a/app/views/dashboard/_projects.html.haml b/app/views/dashboard/_projects.html.haml index 5a49bf0c6b..d676576067 100644 --- a/app/views/dashboard/_projects.html.haml +++ b/app/views/dashboard/_projects.html.haml @@ -1,25 +1,10 @@ .panel.panel-default .panel-heading.clearfix - = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'dash-filter form-control' - - if current_user.can_create_project? - %span.pull-right - = link_to new_project_path, class: "btn btn-new" do - %i.icon-plus - New project + .input-group + = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control' + - if current_user.can_create_project? + %span.input-group-btn + = link_to new_project_path, class: 'btn btn-success' do + New project - %ul.well-list.dash-list - - projects.each do |project| - %li.project-row - = render "project", project: project - - - if projects.blank? - %li - .nothing-here-block There are no projects here. - - if @projects_count > @projects_limit - %li.bottom - %span.light - #{@projects_limit} of #{pluralize(@projects_count, 'project')} displayed. - .pull-right - = link_to projects_dashboard_path do - Show all - %i.icon-angle-right + = render 'shared/projects_list', projects: @projects, projects_limit: 20 diff --git a/app/views/dashboard/_projects_filter.html.haml b/app/views/dashboard/_projects_filter.html.haml deleted file mode 100644 index e4fa2d59e8..0000000000 --- a/app/views/dashboard/_projects_filter.html.haml +++ /dev/null @@ -1,55 +0,0 @@ -%fieldset - %ul.nav.nav-pills.nav-stacked - = nav_tab :scope, nil do - = link_to projects_dashboard_filter_path(scope: nil) do - All - %span.pull-right - = current_user.authorized_projects.count - = nav_tab :scope, 'personal' do - = link_to projects_dashboard_filter_path(scope: 'personal') do - Personal - %span.pull-right - = current_user.personal_projects.count - = nav_tab :scope, 'joined' do - = link_to projects_dashboard_filter_path(scope: 'joined') do - Joined - %span.pull-right - = current_user.authorized_projects.joined(current_user).count - = nav_tab :scope, 'owned' do - = link_to projects_dashboard_filter_path(scope: 'owned') do - Owned - %span.pull-right - = current_user.owned_projects.count - -%fieldset - %legend Visibility - %ul.nav.nav-pills.nav-stacked.nav-small.visibility-filter - - Gitlab::VisibilityLevel.values.each do |level| - %li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' } - = link_to projects_dashboard_filter_path(visibility_level: level) do - = visibility_level_icon(level) - = visibility_level_label(level) - -- if @groups.present? - %fieldset - %legend Groups - %ul.nav.nav-pills.nav-stacked.nav-small - - @groups.each do |group| - %li{ class: (group.name == params[:group]) ? 'active' : 'light' } - = link_to projects_dashboard_filter_path(group: group.name) do - %i.icon-folder-close-alt - = group.name - %small.pull-right - = group.projects.count - - - -- if @tags.present? - %fieldset - %legend Tags - %ul.nav.nav-pills.nav-stacked.nav-small - - @tags.each do |tag| - %li{ class: (tag.name == params[:tag]) ? 'active' : 'light' } - = link_to projects_dashboard_filter_path(scope: params[:scope], tag: tag.name) do - %i.icon-tag - = tag.name diff --git a/app/views/dashboard/_sidebar.html.haml b/app/views/dashboard/_sidebar.html.haml index fed4b2776a..78f695be91 100644 --- a/app/views/dashboard/_sidebar.html.haml +++ b/app/views/dashboard/_sidebar.html.haml @@ -1,25 +1,3 @@ -%ul.nav.nav-tabs.dash-sidebar-tabs - %li.active - = link_to '#projects', 'data-toggle' => 'tab', id: 'sidebar-projects-tab' do - Projects - %span.badge= @projects_count - %li - = link_to '#groups', 'data-toggle' => 'tab', id: 'sidebar-groups-tab' do - Groups - %span.badge= @groups.count - -.tab-content - .tab-pane.active#projects - = render "projects", projects: @projects - .tab-pane#groups - = render "groups", groups: @groups - += render "dashboard/projects", projects: @projects .prepend-top-20 - %span.rss-icon - = link_to dashboard_path(:atom, { private_token: current_user.private_token }) do - %strong - %i.icon-rss - News Feed - -%hr -= render 'shared/promo' + = render 'shared/promo' diff --git a/app/views/dashboard/_zero_authorized_projects.html.haml b/app/views/dashboard/_zero_authorized_projects.html.haml index ff85e32bc4..4e7d663972 100644 --- a/app/views/dashboard/_zero_authorized_projects.html.haml +++ b/app/views/dashboard/_zero_authorized_projects.html.haml @@ -1,10 +1,11 @@ +- publicish_project_count = Project.publicish(current_user).count %h3.page-title Welcome to GitLab! %p.light Self hosted Git management application. %hr %div .dashboard-intro-icon - %i.icon-bookmark-empty - %div + %i.fa.fa-bookmark-o + .dashboard-intro-text %p.slead You don't have access to any projects right now. %br @@ -17,34 +18,36 @@ - if current_user.can_create_project? .link_holder = link_to new_project_path, class: "btn btn-new" do - New project » + %i.fa.fa-plus + New Project - if current_user.can_create_group? %hr %div .dashboard-intro-icon - %i.icon-group - %div + %i.fa.fa-users + .dashboard-intro-text %p.slead You can create a group for several dependent projects. %br Groups are the best way to manage projects and members. .link_holder = link_to new_group_path, class: "btn btn-new" do - New group » + %i.fa.fa-plus + New Group --if @publicish_project_count > 0 +-if publicish_project_count > 0 %hr %div .dashboard-intro-icon - %i.icon-globe - %div + %i.fa.fa-globe + .dashboard-intro-text %p.slead There are - %strong= @publicish_project_count + %strong= publicish_project_count public projects on this server. %br Public projects are an easy way to allow everyone to have read-only access. .link_holder - = link_to explore_projects_path, class: "btn btn-new" do - Browse public projects » + = link_to trending_explore_projects_path, class: "btn btn-new" do + Browse public projects diff --git a/app/views/dashboard/groups/index.html.haml b/app/views/dashboard/groups/index.html.haml new file mode 100644 index 0000000000..0cb7f764fa --- /dev/null +++ b/app/views/dashboard/groups/index.html.haml @@ -0,0 +1,40 @@ +%h3.page-title + Group Membership + - if current_user.can_create_group? + %span.pull-right + = link_to new_group_path, class: "btn btn-new" do + %i.fa.fa-plus + New Group +%p.light + Group members have access to all group projects. +%hr +.panel.panel-default + .panel-heading + %strong Groups + (#{@group_members.count}) + %ul.well-list + - @group_members.each do |group_member| + - group = group_member.group + %li + .pull-right + - if can?(current_user, :admin_group, group) + = link_to edit_group_path(group), class: "btn-sm btn btn-grouped" do + %i.fa.fa-cogs + Settings + + - if can?(current_user, :destroy_group_member, group_member) + = link_to leave_group_group_members_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-sm btn btn-grouped", title: 'Remove user from group' do + %i.fa.fa-sign-out + Leave + + = image_tag group_icon(group), class: "avatar s40 avatar-tile" + = link_to group, class: 'group-name' do + %strong= group.name + + as + %strong #{group_member.human_access} + + %div.light + #{pluralize(group.projects.count, "project")}, #{pluralize(group.users.count, "user")} + += paginate @group_members diff --git a/app/views/dashboard/issues.atom.builder b/app/views/dashboard/issues.atom.builder index f541355778..72e9e361dc 100644 --- a/app/views/dashboard/issues.atom.builder +++ b/app/views/dashboard/issues.atom.builder @@ -1,24 +1,13 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.title "#{current_user.name} issues" - xml.link :href => issues_dashboard_url(:atom, :private_token => current_user.private_token), :rel => "self", :type => "application/atom+xml" - xml.link :href => issues_dashboard_url(:private_token => current_user.private_token), :rel => "alternate", :type => "text/html" - xml.id issues_dashboard_url(:private_token => current_user.private_token) + xml.link href: issues_dashboard_url(:atom, private_token: current_user.private_token), rel: "self", type: "application/atom+xml" + xml.link href: issues_dashboard_url(private_token: current_user.private_token), rel: "alternate", type: "text/html" + xml.id issues_dashboard_url(private_token: current_user.private_token) xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? @issues.each do |issue| - xml.entry do - xml.id project_issue_url(issue.project, issue) - xml.link :href => project_issue_url(issue.project, issue) - xml.title truncate(issue.title, :length => 80) - xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, :width => "40", :height => "40", :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 + issue_to_atom(xml, issue) end end diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml index d3ff291eaa..db19a46cb2 100644 --- a/app/views/dashboard/issues.html.haml +++ b/app/views/dashboard/issues.html.haml @@ -5,10 +5,6 @@ List all issues from all projects you have access to. %hr -.row - .fixed.sidebar-expand-button.hidden-lg.hidden-md - %i.icon-list.icon-2x - .col-md-3.responsive-side - = render 'shared/filter', entity: 'issue' - .col-md-9 - = render 'shared/issues' +.append-bottom-20 + = render 'shared/issuable_filter' += render 'shared/issues' diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml index 7a9ea9f6f9..97a42461b4 100644 --- a/app/views/dashboard/merge_requests.html.haml +++ b/app/views/dashboard/merge_requests.html.haml @@ -5,10 +5,6 @@ %p.light List all merge requests from all projects you have access to. %hr -.row - .fixed.sidebar-expand-button.hidden-lg.hidden-md - %i.icon-list.icon-2x - .col-md-3.responsive-side - = render 'shared/filter', entity: 'merge_request' - .col-md-9 - = render 'shared/merge_requests' +.append-bottom-20 + = render 'shared/issuable_filter' += render 'shared/merge_requests' diff --git a/app/views/dashboard/milestones/_issue.html.haml b/app/views/dashboard/milestones/_issue.html.haml new file mode 100644 index 0000000000..f689b9698e --- /dev/null +++ b/app/views/dashboard/milestones/_issue.html.haml @@ -0,0 +1,10 @@ +%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid } + %span.milestone-row + - project = issue.project + %strong #{project.name_with_namespace} · + = link_to [project.namespace.becomes(Namespace), project, issue] do + %span.cgray ##{issue.iid} + = link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title + .pull-right.assignee-icon + - if issue.assignee + = image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16" diff --git a/app/views/dashboard/milestones/_issues.html.haml b/app/views/dashboard/milestones/_issues.html.haml new file mode 100644 index 0000000000..9f350b772b --- /dev/null +++ b/app/views/dashboard/milestones/_issues.html.haml @@ -0,0 +1,6 @@ +.panel.panel-default + .panel-heading= title + %ul{ class: "well-list issues-sortable-list" } + - if issues + - issues.each do |issue| + = render 'issue', issue: issue diff --git a/app/views/dashboard/milestones/_merge_request.html.haml b/app/views/dashboard/milestones/_merge_request.html.haml new file mode 100644 index 0000000000..8f5c4cce52 --- /dev/null +++ b/app/views/dashboard/milestones/_merge_request.html.haml @@ -0,0 +1,10 @@ +%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid } + %span.milestone-row + - project = merge_request.project + %strong #{project.name_with_namespace} · + = link_to [project.namespace.becomes(Namespace), project, merge_request] do + %span.cgray ##{merge_request.iid} + = link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title + .pull-right.assignee-icon + - if merge_request.assignee + = image_tag avatar_icon(merge_request.assignee.email, 16), class: "avatar s16" diff --git a/app/views/dashboard/milestones/_merge_requests.html.haml b/app/views/dashboard/milestones/_merge_requests.html.haml new file mode 100644 index 0000000000..50057e2c63 --- /dev/null +++ b/app/views/dashboard/milestones/_merge_requests.html.haml @@ -0,0 +1,6 @@ +.panel.panel-default + .panel-heading= title + %ul{ class: "well-list merge_requests-sortable-list" } + - if merge_requests + - merge_requests.each do |merge_request| + = render 'merge_request', merge_request: merge_request diff --git a/app/views/dashboard/milestones/_milestone.html.haml b/app/views/dashboard/milestones/_milestone.html.haml new file mode 100644 index 0000000000..21e730bb7f --- /dev/null +++ b/app/views/dashboard/milestones/_milestone.html.haml @@ -0,0 +1,20 @@ +%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } + %h4 + = link_to_gfm truncate(milestone.title, length: 100), dashboard_milestone_path(milestone.safe_title, title: milestone.title) + .row + .col-sm-6 + = link_to dashboard_milestone_path(milestone.safe_title, title: milestone.title) do + = pluralize milestone.issue_count, 'Issue' +   + = link_to dashboard_milestone_path(milestone.safe_title, title: milestone.title) do + = pluralize milestone.merge_requests_count, 'Merge Request' +   + %span.light #{milestone.percent_complete}% complete + + .col-sm-6 + = milestone_progress_bar(milestone) + %div + - milestone.milestones.each do |milestone| + = link_to milestone_path(milestone) do + %span.label.label-gray + = milestone.project.name_with_namespace diff --git a/app/views/dashboard/milestones/index.html.haml b/app/views/dashboard/milestones/index.html.haml new file mode 100644 index 0000000000..9944c0df81 --- /dev/null +++ b/app/views/dashboard/milestones/index.html.haml @@ -0,0 +1,20 @@ +%h3.page-title + Milestones + %span.pull-right #{@dashboard_milestones.count} milestones + +%p.light + List all milestones from all projects you have access to. + +%hr + += render 'shared/milestones_filter' +.milestones + .panel.panel-default + %ul.well-list + - if @dashboard_milestones.blank? + %li + .nothing-here-block No milestones to show + - else + - @dashboard_milestones.each do |milestone| + = render 'milestone', milestone: milestone + = paginate @dashboard_milestones, theme: "gitlab" diff --git a/app/views/dashboard/milestones/show.html.haml b/app/views/dashboard/milestones/show.html.haml new file mode 100644 index 0000000000..57cce9ab74 --- /dev/null +++ b/app/views/dashboard/milestones/show.html.haml @@ -0,0 +1,81 @@ +%h4.page-title + .issue-box{ class: "issue-box-#{@dashboard_milestone.closed? ? 'closed' : 'open'}" } + - if @dashboard_milestone.closed? + Closed + - else + Open + Milestone #{@dashboard_milestone.title} + +%hr +- if (@dashboard_milestone.total_items_count == @dashboard_milestone.closed_items_count) && @dashboard_milestone.active? + .alert.alert-success + %span All issues for this milestone are closed. You may close the milestone now. + +.description +%table.table + %thead + %tr + %th Project + %th Open issues + %th State + %th Due date + - @dashboard_milestone.milestones.each do |milestone| + %tr + %td + = link_to "#{milestone.project.name_with_namespace}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) + %td + = milestone.issues.opened.count + %td + - if milestone.closed? + Closed + - else + Open + %td + = milestone.expires_at + +.context + %p.lead + Progress: + #{@dashboard_milestone.closed_items_count} closed + – + #{@dashboard_milestone.open_items_count} open + = milestone_progress_bar(@dashboard_milestone) + +%ul.nav.nav-tabs + %li.active + = link_to '#tab-issues', 'data-toggle' => 'tab' do + Issues + %span.badge= @dashboard_milestone.issue_count + %li + = link_to '#tab-merge-requests', 'data-toggle' => 'tab' do + Merge Requests + %span.badge= @dashboard_milestone.merge_requests_count + %li + = link_to '#tab-participants', 'data-toggle' => 'tab' do + Participants + %span.badge= @dashboard_milestone.participants.count + +.tab-content + .tab-pane.active#tab-issues + .row + .col-md-6 + = render 'issues', title: "Open", issues: @dashboard_milestone.opened_issues + .col-md-6 + = render 'issues', title: "Closed", issues: @dashboard_milestone.closed_issues + + .tab-pane#tab-merge-requests + .row + .col-md-6 + = render 'merge_requests', title: "Open", merge_requests: @dashboard_milestone.opened_merge_requests + .col-md-6 + = render 'merge_requests', title: "Closed", merge_requests: @dashboard_milestone.closed_merge_requests + + .tab-pane#tab-participants + %ul.bordered-list + - @dashboard_milestone.participants.each do |user| + %li + = link_to user, title: user.name, class: "darken" do + = image_tag avatar_icon(user.email, 32), class: "avatar s32" + %strong= truncate(user.name, lenght: 40) + %br + %small.cgray= user.username diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml deleted file mode 100644 index 2714ebc53d..0000000000 --- a/app/views/dashboard/projects.html.haml +++ /dev/null @@ -1,73 +0,0 @@ -%h3.page-title - My Projects -.pull-right - .dropdown.inline - %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"} - %span.light sort: - - if @sort.present? - = @sort.humanize - - else - Name - %b.caret - %ul.dropdown-menu - %li - = link_to projects_dashboard_filter_path(sort: nil) do - Name - = link_to projects_dashboard_filter_path(sort: 'newest') do - Newest - = link_to projects_dashboard_filter_path(sort: 'oldest') do - Oldest - = link_to projects_dashboard_filter_path(sort: 'recently_updated') do - Recently updated - = link_to projects_dashboard_filter_path(sort: 'last_updated') do - Last updated -%p.light - All projects you have access to are listed here. Public projects are not included here unless you are a member -%hr -.row - .col-md-3.hidden-sm.hidden-xs.side-filters - = render "projects_filter" - .col-md-9 - %ul.bordered-list.my-projects.top-list - - @projects.each do |project| - %li.my-project-row - %h4.project-title - .project-access-icon - = visibility_level_icon(project.visibility_level) - = link_to project_path(project), class: dom_class(project) do - = project.name_with_namespace - - - if current_user.can_leave_project?(project) - .pull-right - = link_to leave_project_team_members_path(project), data: { confirm: "Leave project?"}, method: :delete, remote: true, class: "btn-tiny btn remove-row", title: 'Leave project' do - %i.icon-signout - Leave - - - if project.forked_from_project - %small.pull-right - %i.icon-code-fork - Forked from: - = link_to project.forked_from_project.name_with_namespace, project_path(project.forked_from_project) - .project-info - .pull-right - - if project.archived? - %span.label - %i.icon-archive - Archived - - project.tags.each do |tag| - %span.label.label-info - %i.icon-tag - = tag.name - - if project.description.present? - %p= truncate project.description, length: 100 - .last-activity - %span.light Last activity: - %span.date= project_last_activity(project) - - - - if @projects.blank? - %li - .nothing-here-block There are no projects here. - .bottom - = paginate @projects, theme: "gitlab" - diff --git a/app/views/dashboard/projects/starred.html.haml b/app/views/dashboard/projects/starred.html.haml new file mode 100644 index 0000000000..f4ad2b294b --- /dev/null +++ b/app/views/dashboard/projects/starred.html.haml @@ -0,0 +1,23 @@ +- if @projects.any? + .dashboard.row + %section.activities.col-md-8 + = render 'dashboard/activities' + %aside.col-md-4 + .panel.panel-default + .panel-heading.clearfix + .input-group + = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control' + - if current_user.can_create_project? + %span.input-group-btn + = link_to new_project_path, class: 'btn btn-success' do + New project + + = render 'shared/projects_list', projects: @projects, + projects_limit: 20, stars: true, avatar: false + + = link_to '#aside', class: 'show-aside' do + %i.fa.fa-angle-left + +- else + %h3 You don't have starred projects yet + %p.slead Visit project page and press on star icon and it will appear on this page. diff --git a/app/views/dashboard/show.atom.builder b/app/views/dashboard/show.atom.builder index f4cf24ccd9..da631ecb33 100644 --- a/app/views/dashboard/show.atom.builder +++ b/app/views/dashboard/show.atom.builder @@ -1,29 +1,12 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.title "Dashboard feed#{" - #{current_user.name}" if current_user.name.present?}" - xml.link :href => dashboard_url(:atom), :rel => "self", :type => "application/atom+xml" - xml.link :href => dashboard_url, :rel => "alternate", :type => "text/html" + xml.link href: dashboard_url(:atom), rel: "self", type: "application/atom+xml" + xml.link href: dashboard_url, rel: "alternate", type: "text/html" xml.id projects_url xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? @events.each do |event| - if event.proper? - 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.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, :width => "40", :height => "40", :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 + event_to_atom(xml, event) end end diff --git a/app/views/dashboard/show.html.haml b/app/views/dashboard/show.html.haml index 6a08b1aa64..fa8946011b 100644 --- a/app/views/dashboard/show.html.haml +++ b/app/views/dashboard/show.html.haml @@ -1,12 +1,11 @@ -- if @has_authorized_projects +- if @projects.any? .dashboard.row %section.activities.col-md-8 = render 'activities' - %aside.side.col-md-4.left.responsive-side + %aside.col-md-4 = render 'sidebar' - - .fixed.sidebar-expand-button.hidden-lg.hidden-md - %i.icon-list.icon-2x + = link_to '#aside', class: 'show-aside' do + %i.fa.fa-angle-left - else = render "zero_authorized_projects" diff --git a/app/views/devise/confirmations/new.html.haml b/app/views/devise/confirmations/new.html.haml old mode 100755 new mode 100644 index 08e1749086..970ba14711 --- a/app/views/devise/confirmations/new.html.haml +++ b/app/views/devise/confirmations/new.html.haml @@ -1,13 +1,14 @@ -.login-box.panel.panel-default - .panel-heading - %h3.panel-title Resend confirmation instructions - .panel-body +.login-box + .login-heading + %h3 Resend confirmation instructions + .login-body = form_for(resource, as: resource_name, url: confirmation_path(resource_name), html: { method: :post }) do |f| .devise-errors = devise_error_messages! .clearfix.append-bottom-20 = f.email_field :email, placeholder: 'Email', class: "form-control", required: true - .clearfix.append-bottom-10 + .clearfix = f.submit "Resend confirmation instructions", class: 'btn btn-success' - .panel-footer - = render 'devise/shared/sign_in_link' + +.clearfix.prepend-top-20 + = render 'devise/shared/sign_in_link' diff --git a/app/views/devise/mailer/confirmation_instructions.html.erb b/app/views/devise/mailer/confirmation_instructions.html.erb index cb1291cf3b..c6fa8f0ee3 100644 --- a/app/views/devise/mailer/confirmation_instructions.html.erb +++ b/app/views/devise/mailer/confirmation_instructions.html.erb @@ -6,4 +6,4 @@

    You can confirm your account through the link below:

    <% end %> -

    <%= link_to 'Confirm my account', confirmation_url(@resource, confirmation_token: @token) %>

    +

    <%= link_to 'Confirm your account', confirmation_url(@resource, confirmation_token: @token) %>

    diff --git a/app/views/devise/mailer/reset_password_instructions.html.erb b/app/views/devise/mailer/reset_password_instructions.html.erb index 7913e88beb..23b31da92d 100644 --- a/app/views/devise/mailer/reset_password_instructions.html.erb +++ b/app/views/devise/mailer/reset_password_instructions.html.erb @@ -2,7 +2,7 @@

    Someone has requested a link to change your password, and you can do this through the link below.

    -

    <%= link_to 'Change my password', edit_password_url(@resource, reset_password_token: @token) %>

    +

    <%= link_to 'Change your password', edit_password_url(@resource, reset_password_token: @token) %>

    If you didn't request this, please ignore this email.

    Your password won't change until you access the link above and create a new one.

    diff --git a/app/views/devise/mailer/unlock_instructions.html.erb b/app/views/devise/mailer/unlock_instructions.html.erb index 8c2a4f0c2d..79d6c761d8 100644 --- a/app/views/devise/mailer/unlock_instructions.html.erb +++ b/app/views/devise/mailer/unlock_instructions.html.erb @@ -4,4 +4,4 @@

    Click the link below to unlock your account:

    -

    <%= link_to 'Unlock my account', unlock_url(@resource, unlock_token: @token) %>

    +

    <%= link_to 'Unlock your account', unlock_url(@resource, unlock_token: @token) %>

    diff --git a/app/views/devise/passwords/edit.html.haml b/app/views/devise/passwords/edit.html.haml index efcd029617..56048e99c1 100644 --- a/app/views/devise/passwords/edit.html.haml +++ b/app/views/devise/passwords/edit.html.haml @@ -1,7 +1,7 @@ -.login-box.panel.panel-default - .panel-heading - %h3.panel-title Change your password - .panel-body +.login-box + .login-heading + %h3 Change your password + .login-body = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :put }) do |f| .devise-errors = devise_error_messages! @@ -10,9 +10,10 @@ = f.password_field :password, class: "form-control top", placeholder: "New password", required: true %div = f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm new password", required: true - .clearfix.append-bottom-10 - = f.submit "Change my password", class: "btn btn-primary" - .panel-footer - %p - = link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) - = render 'devise/shared/sign_in_link' + .clearfix + = f.submit "Change your password", class: "btn btn-primary" + +.clearfix.prepend-top-20 + %p + = link_to "Didn't receive confirmation instructions?", new_confirmation_path(resource_name) + = render 'devise/shared/sign_in_link' diff --git a/app/views/devise/passwords/new.html.haml b/app/views/devise/passwords/new.html.haml old mode 100755 new mode 100644 index bf44dee5ad..e8820daf58 --- a/app/views/devise/passwords/new.html.haml +++ b/app/views/devise/passwords/new.html.haml @@ -1,13 +1,14 @@ -.login-box.panel.panel-default - .panel-heading - %h3.panel-title Reset password - .panel-body +.login-box + .login-heading + %h3 Reset password + .login-body = form_for(resource, as: resource_name, url: password_path(resource_name), html: { method: :post }) do |f| .devise-errors = devise_error_messages! .clearfix.append-bottom-20 = f.email_field :email, placeholder: "Email", class: "form-control", required: true - .clearfix.append-bottom-10 + .clearfix = f.submit "Reset password", class: "btn-primary btn" - .panel-footer - = render 'devise/shared/sign_in_link' + +.clearfix.prepend-top-20 + = render 'devise/shared/sign_in_link' diff --git a/app/views/devise/registrations/edit.html.erb b/app/views/devise/registrations/edit.html.erb index b11817af95..f379e71ae5 100644 --- a/app/views/devise/registrations/edit.html.erb +++ b/app/views/devise/registrations/edit.html.erb @@ -21,8 +21,8 @@
    <%= f.submit "Update", class: "input_button" %>
    <% end %> -

    Cancel my account

    +

    Cancel your account

    -

    Unhappy? <%= link_to "Cancel my account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %>.

    +

    Unhappy? <%= link_to "Cancel your account", registration_path(resource_name), data: { confirm: "Are you sure?" }, method: :delete %>.

    <%= link_to "Back", :back %> diff --git a/app/views/devise/registrations/new.html.haml b/app/views/devise/registrations/new.html.haml index 52d484949b..d3e37f7494 100644 --- a/app/views/devise/registrations/new.html.haml +++ b/app/views/devise/registrations/new.html.haml @@ -1,27 +1,3 @@ -.login-box.panel.panel-success - .panel-heading - %h3.panel-title Sign up - .panel-body - = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| - .devise-errors - = devise_error_messages! - %div - = f.text_field :name, class: "form-control top", placeholder: "Name", required: true - %div - = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true - %div - = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true - %div - = f.password_field :password, class: "form-control middle", placeholder: "Password", required: true - %div - = f.password_field :password_confirmation, class: "form-control bottom", placeholder: "Confirm password", required: true - %div - = f.submit "Sign up", class: "btn-create btn" - .panel-footer - %p - %span.light - Have an account? - %strong - = link_to "Sign in", new_session_path(resource_name) - %p - = link_to "Forgot your password?", new_password_path(resource_name) += render 'devise/shared/signup_box' + += render 'devise/shared/sign_in_link' \ No newline at end of file diff --git a/app/views/devise/sessions/_new_base.html.haml b/app/views/devise/sessions/_new_base.html.haml index 4e19604489..54a3972677 100644 --- a/app/views/devise/sessions/_new_base.html.haml +++ b/app/views/devise/sessions/_new_base.html.haml @@ -2,11 +2,11 @@ = f.text_field :login, class: "form-control top", placeholder: "Username or Email", autofocus: "autofocus" = f.password_field :password, class: "form-control bottom", placeholder: "Password" - if devise_mapping.rememberable? - .clearfix.append-bottom-10 - %label.checkbox.remember_me{for: "user_remember_me"} + .remember-me.checkbox + %label{for: "user_remember_me"} = f.check_box :remember_me %span Remember me + .pull-right + = link_to "Forgot your password?", new_password_path(resource_name) %div - = f.submit "Sign in", class: "btn-save btn" - .pull-right - = link_to "Forgot your password?", new_password_path(resource_name), class: "btn" + = f.submit "Sign in", class: "btn btn-save" diff --git a/app/views/devise/sessions/_new_ldap.html.haml b/app/views/devise/sessions/_new_ldap.html.haml index 6c5a878e90..812e22373a 100644 --- a/app/views/devise/sessions/_new_ldap.html.haml +++ b/app/views/devise/sessions/_new_ldap.html.haml @@ -1,5 +1,4 @@ -= form_tag(user_omniauth_callback_path(:ldap), id: 'new_ldap_user' ) do - = text_field_tag :username, nil, {class: "form-control top", placeholder: "LDAP Login", autofocus: "autofocus"} += form_tag(user_omniauth_callback_path(server['provider_name']), id: 'new_ldap_user' ) do + = text_field_tag :username, nil, {class: "form-control top", placeholder: "#{server['label']} Login", autofocus: "autofocus"} = password_field_tag :password, nil, {class: "form-control bottom", placeholder: "Password"} - %br/ - = submit_tag "LDAP Sign in", class: "btn-save btn" + = button_tag "#{server['label']} Sign in", class: "btn-save btn" diff --git a/app/views/devise/sessions/_oauth_providers.html.haml b/app/views/devise/sessions/_oauth_providers.html.haml deleted file mode 100644 index 935bc6af50..0000000000 --- a/app/views/devise/sessions/_oauth_providers.html.haml +++ /dev/null @@ -1,11 +0,0 @@ -- providers = (enabled_oauth_providers - [:ldap]) -- if providers.present? - %hr - %div{:'data-no-turbolink' => 'data-no-turbolink'} - %span Sign in with:   - - providers.each do |provider| - %span - - if default_providers.include?(provider) - = link_to authbutton(provider, 32), omniauth_authorize_path(resource_name, provider) - - else - = link_to provider.to_s.titleize, omniauth_authorize_path(resource_name, provider), class: "btn" diff --git a/app/views/devise/sessions/new.html.haml b/app/views/devise/sessions/new.html.haml index f53d6f09da..89e4e229ac 100644 --- a/app/views/devise/sessions/new.html.haml +++ b/app/views/devise/sessions/new.html.haml @@ -1,42 +1,18 @@ -.login-box.panel.panel-primary - .panel-heading - %h3.panel-title Sign in - .panel-body - - if ldap_enabled? && gitlab_config.signin_enabled - %ul.nav.nav-tabs - %li.active - = link_to 'LDAP', '#tab-ldap', 'data-toggle' => 'tab' - %li - = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' - .tab-content - %div#tab-ldap.tab-pane.active - = render partial: 'devise/sessions/new_ldap' - %div#tab-signin.tab-pane - = render partial: 'devise/sessions/new_base' +%div + - if signin_enabled? || ldap_enabled? + = render 'devise/shared/signin_box' - - elsif ldap_enabled? - = render partial: 'devise/sessions/new_ldap' - - elsif gitlab_config.signin_enabled - = render partial: 'devise/sessions/new_base' - - else - %div - No authentication methods configured. + -# Omniauth fits between signin/ldap signin and signup and does not have a surrounding box + - if Gitlab.config.omniauth.enabled && devise_mapping.omniauthable? + .clearfix.prepend-top-20 + = render 'devise/shared/omniauth_box' - = render 'devise/sessions/oauth_providers' if Gitlab.config.omniauth.enabled && devise_mapping.omniauthable? + -# Signup only makes sense if you can also sign-in + - if signin_enabled? && signup_enabled? + .prepend-top-20 + = render 'devise/shared/signup_box' - .panel-footer - - if gitlab_config.signup_enabled - %p - %span.light - Don't have an account? - %strong - = link_to "Sign up", new_registration_path(resource_name) - - %p - %span.light Did not receive confirmation email? - = link_to "Send again", new_confirmation_path(resource_name) - - - - if extra_config.has_key?('sign_in_text') - %hr - = markdown(extra_config.sign_in_text) + -# Show a message if none of the mechanisms above are enabled + - if !signin_enabled? && !ldap_enabled? && !(Gitlab.config.omniauth.enabled && devise_mapping.omniauthable?) + %div + No authentication methods configured. diff --git a/app/views/devise/shared/_omniauth_box.html.haml b/app/views/devise/shared/_omniauth_box.html.haml new file mode 100644 index 0000000000..b647b906b7 --- /dev/null +++ b/app/views/devise/shared/_omniauth_box.html.haml @@ -0,0 +1,10 @@ +%p + %span.light + Sign in with   + - providers = additional_providers + - providers.each do |provider| + %span.light + - if default_providers.include?(provider) + = link_to oauth_image_tag(provider), omniauth_authorize_path(resource_name, provider), class: 'oauth-image-link' + - else + = link_to provider.to_s.titleize, omniauth_authorize_path(resource_name, provider), class: "btn" diff --git a/app/views/devise/shared/_signin_box.html.haml b/app/views/devise/shared/_signin_box.html.haml new file mode 100644 index 0000000000..c76574db45 --- /dev/null +++ b/app/views/devise/shared/_signin_box.html.haml @@ -0,0 +1,26 @@ +.login-box + - if signup_enabled? + .login-heading + %h3 Existing user? Sign in + - else + .login-heading + %h3 Sign in + .login-body + - if ldap_enabled? + %ul.nav.nav-tabs + - @ldap_servers.each_with_index do |server, i| + %li{class: (:active if i.zero?)} + = link_to server['label'], "#tab-#{server['provider_name']}", 'data-toggle' => 'tab' + - if signin_enabled? + %li + = link_to 'Standard', '#tab-signin', 'data-toggle' => 'tab' + .tab-content + - @ldap_servers.each_with_index do |server, i| + %div.tab-pane{id: "tab-#{server['provider_name']}", class: (:active if i.zero?)} + = render 'devise/sessions/new_ldap', server: server + - if signin_enabled? + %div#tab-signin.tab-pane + = render 'devise/sessions/new_base' + + - elsif signin_enabled? + = render 'devise/sessions/new_base' diff --git a/app/views/devise/shared/_signup_box.html.haml b/app/views/devise/shared/_signup_box.html.haml new file mode 100644 index 0000000000..9dc6aeffd5 --- /dev/null +++ b/app/views/devise/shared/_signup_box.html.haml @@ -0,0 +1,27 @@ +.login-box + - if signin_enabled? + .login-heading + %h3 New user? Create an account + - else + .login-heading + %h3 Create an account + .login-body + = form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| + .devise-errors + = devise_error_messages! + %div + = f.text_field :name, class: "form-control top", placeholder: "Name", required: true + %div + = f.text_field :username, class: "form-control middle", placeholder: "Username", required: true + %div + = f.email_field :email, class: "form-control middle", placeholder: "Email", required: true + .form-group.append-bottom-20#password-strength + = f.password_field :password, class: "form-control bottom", id: "user_password_sign_up", placeholder: "Password", required: true + %div + = f.submit "Sign up", class: "btn-create btn" + +.clearfix.prepend-top-20 + %p + %span.light Didn't receive a confirmation email? + = succeed '.' do + = link_to "Request a new one", new_confirmation_path(resource_name) diff --git a/app/views/doorkeeper/applications/_delete_form.html.haml b/app/views/doorkeeper/applications/_delete_form.html.haml new file mode 100644 index 0000000000..6a5c917049 --- /dev/null +++ b/app/views/doorkeeper/applications/_delete_form.html.haml @@ -0,0 +1,4 @@ +- submit_btn_css ||= 'btn btn-link btn-remove btn-sm' += form_tag oauth_application_path(application) do + %input{:name => "_method", :type => "hidden", :value => "delete"}/ + = submit_tag 'Destroy', onclick: "return confirm('Are you sure?')", class: submit_btn_css \ No newline at end of file diff --git a/app/views/doorkeeper/applications/_form.html.haml b/app/views/doorkeeper/applications/_form.html.haml new file mode 100644 index 0000000000..a5fec2fabd --- /dev/null +++ b/app/views/doorkeeper/applications/_form.html.haml @@ -0,0 +1,24 @@ += form_for application, url: doorkeeper_submit_path(application), html: {class: 'form-horizontal', role: 'form'} do |f| + - if application.errors.any? + .alert.alert-danger{"data-alert" => ""} + %p Whoops! Check your form for possible errors + = content_tag :div, class: "form-group#{' has-error' if application.errors[:name].present?}" do + = f.label :name, class: 'col-sm-2 control-label' + .col-sm-10 + = f.text_field :name, class: 'form-control' + = doorkeeper_errors_for application, :name + = content_tag :div, class: "form-group#{' has-error' if application.errors[:redirect_uri].present?}" do + = f.label :redirect_uri, class: 'col-sm-2 control-label' + .col-sm-10 + = f.text_area :redirect_uri, class: 'form-control' + = doorkeeper_errors_for application, :redirect_uri + %span.help-block + Use one line per URI + - if Doorkeeper.configuration.native_redirect_uri + %span.help-block + Use + %code= Doorkeeper.configuration.native_redirect_uri + for local tests + .form-actions + = f.submit 'Submit', class: "btn btn-primary wide" + = link_to "Cancel", applications_profile_path, class: "btn btn-default" diff --git a/app/views/doorkeeper/applications/edit.html.haml b/app/views/doorkeeper/applications/edit.html.haml new file mode 100644 index 0000000000..61584eb9c4 --- /dev/null +++ b/app/views/doorkeeper/applications/edit.html.haml @@ -0,0 +1,2 @@ +%h3.page-title Edit application += render 'form', application: @application \ No newline at end of file diff --git a/app/views/doorkeeper/applications/index.html.haml b/app/views/doorkeeper/applications/index.html.haml new file mode 100644 index 0000000000..e5be4b4bca --- /dev/null +++ b/app/views/doorkeeper/applications/index.html.haml @@ -0,0 +1,16 @@ +%h3.page-title Your applications +%p= link_to 'New Application', new_oauth_application_path, class: 'btn btn-success' +%table.table.table-striped + %thead + %tr + %th Name + %th Callback URL + %th + %th + %tbody + - @applications.each do |application| + %tr{:id => "application_#{application.id}"} + %td= link_to application.name, oauth_application_path(application) + %td= application.redirect_uri + %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link' + %td= render 'delete_form', application: application \ No newline at end of file diff --git a/app/views/doorkeeper/applications/new.html.haml b/app/views/doorkeeper/applications/new.html.haml new file mode 100644 index 0000000000..655845e4af --- /dev/null +++ b/app/views/doorkeeper/applications/new.html.haml @@ -0,0 +1,2 @@ +%h3.page-title New application += render 'form', application: @application \ No newline at end of file diff --git a/app/views/doorkeeper/applications/show.html.haml b/app/views/doorkeeper/applications/show.html.haml new file mode 100644 index 0000000000..82e78b4af1 --- /dev/null +++ b/app/views/doorkeeper/applications/show.html.haml @@ -0,0 +1,26 @@ +%h3.page-title + Application: #{@application.name} + + +%table.table + %tr + %td + Application Id + %td + %code#application_id= @application.uid + %tr + %td + Secret: + %td + %code#secret= @application.secret + + %tr + %td + Callback url + %td + - @application.redirect_uri.split.each do |uri| + %div + %span.monospace= uri +.form-actions + = link_to 'Edit', edit_oauth_application_path(@application), class: 'btn btn-primary wide pull-left' + = render 'delete_form', application: @application, submit_btn_css: 'btn btn-danger prepend-left-10' diff --git a/app/views/doorkeeper/authorizations/error.html.haml b/app/views/doorkeeper/authorizations/error.html.haml new file mode 100644 index 0000000000..7561ec85ed --- /dev/null +++ b/app/views/doorkeeper/authorizations/error.html.haml @@ -0,0 +1,3 @@ +%h3.page-title An error has occurred +%main{:role => "main"} + %pre= @pre_auth.error_response.body[:error_description] \ No newline at end of file diff --git a/app/views/doorkeeper/authorizations/new.html.haml b/app/views/doorkeeper/authorizations/new.html.haml new file mode 100644 index 0000000000..15f9ee266c --- /dev/null +++ b/app/views/doorkeeper/authorizations/new.html.haml @@ -0,0 +1,28 @@ +%h3.page-title Authorize required +%main{:role => "main"} + %p.h4 + Authorize + %strong.text-info= @pre_auth.client.name + to use your account? + - if @pre_auth.scopes + #oauth-permissions + %p This application will be able to: + %ul.text-info + - @pre_auth.scopes.each do |scope| + %li= t scope, scope: [:doorkeeper, :scopes] + %hr/ + .actions + = form_tag oauth_authorization_path, method: :post do + = hidden_field_tag :client_id, @pre_auth.client.uid + = hidden_field_tag :redirect_uri, @pre_auth.redirect_uri + = hidden_field_tag :state, @pre_auth.state + = hidden_field_tag :response_type, @pre_auth.response_type + = hidden_field_tag :scope, @pre_auth.scope + = submit_tag "Authorize", class: "btn btn-success wide pull-left" + = form_tag oauth_authorization_path, method: :delete do + = hidden_field_tag :client_id, @pre_auth.client.uid + = hidden_field_tag :redirect_uri, @pre_auth.redirect_uri + = hidden_field_tag :state, @pre_auth.state + = hidden_field_tag :response_type, @pre_auth.response_type + = hidden_field_tag :scope, @pre_auth.scope + = submit_tag "Deny", class: "btn btn-danger prepend-left-10" \ No newline at end of file diff --git a/app/views/doorkeeper/authorizations/show.html.haml b/app/views/doorkeeper/authorizations/show.html.haml new file mode 100644 index 0000000000..9a40200719 --- /dev/null +++ b/app/views/doorkeeper/authorizations/show.html.haml @@ -0,0 +1,3 @@ +%h3.page-title Authorization code: +%main{:role => "main"} + %code#authorization_code= params[:code] \ No newline at end of file diff --git a/app/views/doorkeeper/authorized_applications/_delete_form.html.haml b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml new file mode 100644 index 0000000000..4bba72167e --- /dev/null +++ b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml @@ -0,0 +1,4 @@ +- submit_btn_css ||= 'btn btn-link btn-remove' += form_tag oauth_authorized_application_path(application) do + %input{:name => "_method", :type => "hidden", :value => "delete"}/ + = submit_tag 'Revoke', onclick: "return confirm('Are you sure?')", class: 'btn btn-link btn-remove btn-sm' \ No newline at end of file diff --git a/app/views/doorkeeper/authorized_applications/index.html.haml b/app/views/doorkeeper/authorized_applications/index.html.haml new file mode 100644 index 0000000000..814cdc987e --- /dev/null +++ b/app/views/doorkeeper/authorized_applications/index.html.haml @@ -0,0 +1,16 @@ +%header.page-header + %h1 Your authorized applications +%main{:role => "main"} + %table.table.table-striped + %thead + %tr + %th Application + %th Created At + %th + %th + %tbody + - @applications.each do |application| + %tr + %td= application.name + %td= application.created_at.strftime('%Y-%m-%d %H:%M:%S') + %td= render 'delete_form', application: application \ No newline at end of file diff --git a/app/views/events/_commit.html.haml b/app/views/events/_commit.html.haml index 0e03e116e7..c86ce9ae65 100644 --- a/app/views/events/_commit.html.haml +++ b/app/views/events/_commit.html.haml @@ -1,5 +1,5 @@ %li.commit .commit-row-title - = link_to commit[:id][0..8], project_commit_path(project, commit[:id]), class: "commit_short_id", alt: '' + = link_to truncate_sha(commit[:id]), namespace_project_commit_path(project.namespace, project, commit[:id]), class: "commit_short_id", alt: ''   = gfm event_commit_title(commit[:message]), project diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml index 6138331537..02b1dec753 100644 --- a/app/views/events/_event.html.haml +++ b/app/views/events/_event.html.haml @@ -3,13 +3,14 @@ .event-item-timestamp #{time_ago_with_tooltip(event.created_at)} - = cache event do + = cache [event, current_user] do = image_tag avatar_icon(event.author_email, 24), class: "avatar s24", alt:'' - if event.push? = render "events/event/push", event: event - - elsif event.note? + - elsif event.commented? = render "events/event/note", event: event + - elsif event.created_project? + = render "events/event/created_project", event: event - else - = render "events/event/common", event: event - + = render "events/event/common", event: event \ No newline at end of file diff --git a/app/views/events/_event_issue.atom.haml b/app/views/events/_event_issue.atom.haml index 56801107d0..0edb61ea24 100644 --- a/app/views/events/_event_issue.atom.haml +++ b/app/views/events/_event_issue.atom.haml @@ -1,2 +1,3 @@ -%div{:xmlns => "http://www.w3.org/1999/xhtml"} - %p= markdown issue.description +%div{xmlns: "http://www.w3.org/1999/xhtml"} + - if issue.description.present? + = markdown(issue.description, xhtml: true) diff --git a/app/views/events/_event_last_push.html.haml b/app/views/events/_event_last_push.html.haml index 4c9a39bcc2..d2f0005142 100644 --- a/app/views/events/_event_last_push.html.haml +++ b/app/views/events/_event_last_push.html.haml @@ -2,13 +2,13 @@ .event-last-push .event-last-push-text %span You pushed to - = link_to project_commits_path(event.project, event.ref_name) do + = link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) do %strong= event.ref_name at %strong= link_to_project event.project #{time_ago_with_tooltip(event.created_at)} .pull-right - = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-create btn-small" do + = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-create btn-sm" do Create Merge Request %hr diff --git a/app/views/events/_event_merge_request.atom.haml b/app/views/events/_event_merge_request.atom.haml index dea256bb7f..1a8b62abea 100644 --- a/app/views/events/_event_merge_request.atom.haml +++ b/app/views/events/_event_merge_request.atom.haml @@ -1,2 +1,3 @@ %div{xmlns: "http://www.w3.org/1999/xhtml"} - %p= markdown merge_request.description + - if merge_request.description.present? + = markdown(merge_request.description, xhtml: true) diff --git a/app/views/events/_event_note.atom.haml b/app/views/events/_event_note.atom.haml index 96039ad18d..b49c331ccf 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"} - %p= markdown note.note +%div{xmlns: "http://www.w3.org/1999/xhtml"} + = markdown(note.note, xhtml: true) diff --git a/app/views/events/_event_push.atom.haml b/app/views/events/_event_push.atom.haml index 17228c430c..5d14def8f7 100644 --- a/app/views/events/_event_push.atom.haml +++ b/app/views/events/_event_push.atom.haml @@ -2,11 +2,11 @@ - event.commits.first(15).each do |commit| %p %strong= commit[:author][:name] - = link_to "(##{commit[:id][0...8]})", project_commit_path(event.project, id: commit[:id]) + = link_to "(##{truncate_sha(commit[:id])})", namespace_project_commit_path(event.project.namespace, event.project, id: commit[:id]) %i at = commit[:timestamp].to_time.to_s(:short) - %blockquote= markdown(escape_once(commit[:message])) + %blockquote= markdown(escape_once(commit[:message]), xhtml: true) - if event.commits_count > 15 %p %i diff --git a/app/views/events/_events.html.haml b/app/views/events/_events.html.haml index 3d62d47886..68c19df092 100644 --- a/app/views/events/_events.html.haml +++ b/app/views/events/_events.html.haml @@ -1 +1 @@ -= render @events += render partial: 'events/event', collection: @events diff --git a/app/views/events/event/_common.html.haml b/app/views/events/event/_common.html.haml index a9d3adf41d..a39e62e9da 100644 --- a/app/views/events/event/_common.html.haml +++ b/app/views/events/event/_common.html.haml @@ -1,15 +1,17 @@ .event-title %span.author_name= link_to_author event - %span.event_label{class: event.action_name}= event_action_name(event) + %span.event_label{class: event.action_name} + = event_action_name(event) + - if event.target - %strong= link_to "##{event.target_iid}", [event.project, event.target] - - else - %strong= gfm event.target_title - at + %strong= link_to "##{event.target_iid}", [event.project.namespace.becomes(Namespace), event.project, event.target] + at + - if event.project = link_to_project event.project - else = event.project_name + - if event.target.respond_to?(:title) .event-body .event-note diff --git a/app/views/events/event/_created_project.html.haml b/app/views/events/event/_created_project.html.haml new file mode 100644 index 0000000000..552525f4a0 --- /dev/null +++ b/app/views/events/event/_created_project.html.haml @@ -0,0 +1,27 @@ +.event-title + %span.author_name= link_to_author event + %span.event_label{class: event.action_name} + = event_action_name(event) + + - if event.project + = link_to_project event.project + - else + = event.project_name + +- if current_user == event.author && !event.project.private? && twitter_sharing_enabled? + .event-body + .event-note + .md + %p + Congratulations! Why not share your accomplishment with the world? + + %a.twitter-share-button{ | + href: "https://twitter.com/share", | + "data-url" => event.project.web_url, | + "data-text" => "I just #{event.project.imported? ? "imported" : "created"} a new project in GitLab! GitLab is version control on your server.", | + "data-size" => "medium", | + "data-related" => "gitlab", | + "data-hashtags" => "gitlab", | + "data-count" => "none"} + Tweet + %script{src: "//platform.twitter.com/widgets.js"} diff --git a/app/views/events/event/_note.html.haml b/app/views/events/event/_note.html.haml index ad2afbce14..4ef18c0906 100644 --- a/app/views/events/event/_note.html.haml +++ b/app/views/events/event/_note.html.haml @@ -1,6 +1,10 @@ .event-title %span.author_name= link_to_author event - %span.event_label commented on #{event_note_title_html(event)} at + %span.event_label + = event.action_name + = event_note_title_html(event) + at + - if event.project = link_to_project event.project - else @@ -9,14 +13,14 @@ .event-body .event-note .md - %i.icon-comment-alt.event-note-icon + %i.fa.fa-comment-o.event-note-icon = event_note(event.target.note) - note = event.target - if note.attachment.url - if note.attachment.image? - = link_to note.attachment.secure_url, target: '_blank' do - = image_tag note.attachment.secure_url, class: 'note-image-attach' + = link_to note.attachment.url, target: '_blank' do + = image_tag note.attachment.url, class: 'note-image-attach' - else - = link_to note.attachment.secure_url, target: "_blank", class: 'note-file-attach' do - %i.icon-paper-clip + = link_to note.attachment.url, target: "_blank", class: 'note-file-attach' do + %i.fa.fa-paperclip = note.attachment_identifier diff --git a/app/views/events/event/_push.html.haml b/app/views/events/event/_push.html.haml index 1bca64c7d5..60d7978b13 100644 --- a/app/views/events/event/_push.html.haml +++ b/app/views/events/event/_push.html.haml @@ -1,10 +1,10 @@ .event-title %span.author_name= link_to_author event - %span.event_label.pushed #{event.push_action_name} #{event.ref_type} + %span.event_label.pushed #{event.action_name} #{event.ref_type} - if event.rm_ref? %strong= event.ref_name - else - = link_to project_commits_path(event.project, event.ref_name) do + = link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) do %strong= event.ref_name at = link_to_project event.project @@ -21,5 +21,11 @@ %li.commits-stat - if event.commits_count > 2 %span ... and #{event.commits_count - 2} more commits. - = link_to project_compare_path(event.project, from: event.commit_from, to: event.commit_to) do - %strong Compare → #{event.commit_from[0..7]}...#{event.commit_to[0..7]} + - if event.md_ref? + - from = event.commit_from + - from_label = truncate_sha(from) + - else + - from = event.project.default_branch + - from_label = from + = link_to namespace_project_compare_path(event.project.namespace, event.project, from: from, to: event.commit_to) do + %strong Compare → #{from_label}...#{truncate_sha(event.commit_to)} diff --git a/app/views/explore/groups/index.html.haml b/app/views/explore/groups/index.html.haml index 80ddd5c1bd..2ea6cb1865 100644 --- a/app/views/explore/groups/index.html.haml +++ b/app/views/explore/groups/index.html.haml @@ -1,32 +1,31 @@ .clearfix .pull-left = form_tag explore_groups_path, method: :get, class: 'form-inline form-tiny' do |f| + = hidden_field_tag :sort, @sort .form-group = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "groups_search" .form-group - = submit_tag 'Search', class: "btn btn-primary wide" + = button_tag 'Search', class: "btn btn-primary wide" .pull-right .dropdown.inline - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} %span.light sort: - if @sort.present? - = @sort.humanize + = sort_options_hash[@sort] - else - Name + = sort_title_recently_created %b.caret %ul.dropdown-menu %li - = link_to explore_groups_path(sort: nil) do - Name - = link_to explore_groups_path(sort: 'newest') do - Newest - = link_to explore_groups_path(sort: 'oldest') do - Oldest - = link_to explore_groups_path(sort: 'recently_updated') do - Recently updated - = link_to explore_groups_path(sort: 'last_updated') do - Last updated + = link_to explore_groups_path(sort: sort_value_recently_created) do + = sort_title_recently_created + = link_to explore_groups_path(sort: sort_value_oldest_created) do + = sort_title_oldest_created + = link_to explore_groups_path(sort: sort_value_recently_updated) do + = sort_title_recently_updated + = link_to explore_groups_path(sort: sort_value_oldest_updated) do + = sort_title_oldest_updated %hr @@ -36,7 +35,7 @@ .clearfix %h4 = link_to group_path(id: group.path) do - %i.icon-group + %i.fa.fa-users = group.name .clearfix %p diff --git a/app/views/explore/projects/_filter.html.haml b/app/views/explore/projects/_filter.html.haml new file mode 100644 index 0000000000..b3963a9d90 --- /dev/null +++ b/app/views/explore/projects/_filter.html.haml @@ -0,0 +1,67 @@ +.pull-left + = form_tag explore_projects_filter_path, method: :get, class: 'form-inline form-tiny' do |f| + .form-group + = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "projects_search" + .form-group + = button_tag 'Search', class: "btn btn-primary wide" + +.pull-right.hidden-sm.hidden-xs + - if current_user + .dropdown.inline.append-right-10 + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %i.fa.fa-globe + %span.light Visibility: + - if params[:visibility_level].present? + = visibility_level_label(params[:visibility_level].to_i) + - else + Any + %b.caret + %ul.dropdown-menu + %li + = link_to explore_projects_filter_path(visibility_level: nil) do + Any + - Gitlab::VisibilityLevel.values.each do |level| + %li{ class: (level.to_s == params[:visibility_level]) ? 'active' : 'light' } + = link_to explore_projects_filter_path(visibility_level: level) do + = visibility_level_icon(level) + = visibility_level_label(level) + + - if @tags.present? + .dropdown.inline.append-right-10 + %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %i.fa.fa-tags + %span.light Tags: + - if params[:tag].present? + = params[:tag] + - else + Any + %b.caret + %ul.dropdown-menu + %li + = link_to explore_projects_filter_path(tag: nil) do + Any + + - @tags.each do |tag| + %li{ class: (tag.name == params[:tag]) ? 'active' : 'light' } + = link_to explore_projects_filter_path(tag: tag.name) do + %i.fa.fa-tag + = tag.name + + .dropdown.inline + %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} + %span.light sort: + - if @sort.present? + = sort_options_hash[@sort] + - else + = sort_title_recently_created + %b.caret + %ul.dropdown-menu + %li + = link_to explore_projects_filter_path(sort: sort_value_recently_created) do + = sort_title_recently_created + = link_to explore_projects_filter_path(sort: sort_value_oldest_created) do + = sort_title_oldest_created + = link_to explore_projects_filter_path(sort: sort_value_recently_updated) do + = sort_title_recently_updated + = link_to explore_projects_filter_path(sort: sort_value_oldest_updated) do + = sort_title_oldest_updated diff --git a/app/views/explore/projects/_project.html.haml b/app/views/explore/projects/_project.html.haml index 0b4be2ef5c..d65fb52937 100644 --- a/app/views/explore/projects/_project.html.haml +++ b/app/views/explore/projects/_project.html.haml @@ -1,26 +1,24 @@ %li - .project-access-icon - = visibility_level_icon(project.visibility_level) - - .project-description - %h4.project-title - = link_to project.name_with_namespace, project - - - if current_page?(starred_explore_projects_path) - %strong.pull-right - = pluralize project.star_count, 'star' + %h4.project-title + .project-access-icon + = visibility_level_icon(project.visibility_level) + = link_to project.name_with_namespace, [project.namespace.becomes(Namespace), project] + %span.pull-right + %i.fa.fa-star + = project.star_count + .project-info - if project.description.present? %p.project-description.str-truncated = project.description .repo-info - unless project.empty_repo? - = link_to pluralize(project.repository.round_commit_count, 'commit'), project_commits_path(project, project.default_branch) + = link_to pluralize(project.repository.round_commit_count, 'commit'), namespace_project_commits_path(project.namespace, project, project.default_branch) · - = link_to pluralize(project.repository.branch_names.count, 'branch'), project_branches_path(project) + = link_to pluralize(project.repository.branch_names.count, 'branch'), namespace_project_branches_path(project.namespace, project) · - = link_to pluralize(project.repository.tag_names.count, 'tag'), project_tags_path(project) + = link_to pluralize(project.repository.tag_names.count, 'tag'), namespace_project_tags_path(project.namespace, project) - else - %i.icon-warning-sign + %i.fa.fa-exclamation-triangle Empty repository diff --git a/app/views/explore/projects/index.html.haml b/app/views/explore/projects/index.html.haml index c8bf78385e..5086b58cd0 100644 --- a/app/views/explore/projects/index.html.haml +++ b/app/views/explore/projects/index.html.haml @@ -1,32 +1,5 @@ .clearfix - .pull-left - = form_tag explore_projects_path, method: :get, class: 'form-inline form-tiny' do |f| - .form-group - = search_field_tag :search, params[:search], placeholder: "Filter by name", class: "form-control search-text-input input-mn-300", id: "projects_search" - .form-group - = submit_tag 'Search', class: "btn btn-primary wide" - - .pull-right - .dropdown.inline - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} - %span.light sort: - - if @sort.present? - = @sort.humanize - - else - Name - %b.caret - %ul.dropdown-menu - %li - = link_to explore_projects_path(sort: nil) do - Name - = link_to explore_projects_path(sort: 'newest') do - Newest - = link_to explore_projects_path(sort: 'oldest') do - Oldest - = link_to explore_projects_path(sort: 'recently_updated') do - Recently updated - = link_to explore_projects_path(sort: 'last_updated') do - Last updated + = render 'filter' %hr .public-projects diff --git a/app/views/explore/projects/starred.html.haml b/app/views/explore/projects/starred.html.haml index 9c793d4050..420f069375 100644 --- a/app/views/explore/projects/starred.html.haml +++ b/app/views/explore/projects/starred.html.haml @@ -1,6 +1,6 @@ .explore-trending-block %p.lead - %i.icon-comments-alt + %i.fa.fa-star See most starred projects %hr .public-projects diff --git a/app/views/explore/projects/trending.html.haml b/app/views/explore/projects/trending.html.haml index 18bb1ac0ba..9cad923893 100644 --- a/app/views/explore/projects/trending.html.haml +++ b/app/views/explore/projects/trending.html.haml @@ -1,6 +1,6 @@ .explore-trending-block %p.lead - %i.icon-comments-alt + %i.fa.fa-comments-o See most discussed projects for last month %hr .public-projects diff --git a/app/views/groups/_filter.html.haml b/app/views/groups/_filter.html.haml deleted file mode 100644 index 393be3f1d1..0000000000 --- a/app/views/groups/_filter.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -= form_tag group_filter_path(entity), method: 'get' do - %fieldset - %ul.nav.nav-pills.nav-stacked - %li{class: ("active" if (params[:status] == 'active' || !params[:status]))} - = link_to group_filter_path(entity, status: 'active') do - Active - %li{class: ("active" if params[:status] == 'closed')} - = link_to group_filter_path(entity, status: 'closed') do - Closed - %li{class: ("active" if params[:status] == 'all')} - = link_to group_filter_path(entity, status: 'all') do - All diff --git a/app/views/groups/_new_group_member.html.haml b/app/views/groups/_new_group_member.html.haml deleted file mode 100644 index 3ab9276c54..0000000000 --- a/app/views/groups/_new_group_member.html.haml +++ /dev/null @@ -1,11 +0,0 @@ -= form_for @users_group, url: group_users_groups_path(@group), html: { class: 'form-horizontal users-group-form' } do |f| - .form-group - = f.label :user_ids, "People", class: 'control-label' - .col-sm-10= users_select_tag(:user_ids, multiple: true, class: 'input-large') - - .form-group - = f.label :group_access, "Group Access", class: 'control-label' - .col-sm-10= select_tag :group_access, options_for_select(UsersGroup.group_access_roles, @users_group.group_access), class: "project-access-select select2" - - .form-actions - = f.submit 'Add users into group', class: "btn btn-create" diff --git a/app/views/groups/_projects.html.haml b/app/views/groups/_projects.html.haml index 2ebff21b81..4f8aec1c67 100644 --- a/app/views/groups/_projects.html.haml +++ b/app/views/groups/_projects.html.haml @@ -1,21 +1,10 @@ .panel.panel-default - .panel-heading - Projects (#{projects.count}) - - if can? current_user, :create_projects, @group - .panel-head-actions - = link_to new_project_path(namespace_id: @group.id), class: "btn btn-new" do - %i.icon-plus - New project - %ul.well-list - - if projects.blank? - .nothing-here-block This group has no projects yet - - projects.each do |project| - %li.project-row - = link_to project_path(project), class: dom_class(project) do - .dash-project-access-icon - = visibility_level_icon(project.visibility_level) - %span.str-truncated - %span.project-name - = project.name - %span.arrow - %i.icon-angle-right + .panel-heading.clearfix + .input-group + = search_field_tag :filter_projects, nil, placeholder: 'Filter by name', class: 'projects-list-filter form-control' + - if can? current_user, :create_projects, @group + %span.input-group-btn + = link_to new_project_path(namespace_id: @group.id), class: 'btn btn-success' do + New project + + = render 'shared/projects_list', projects: @projects, projects_limit: 20 diff --git a/app/views/groups/_settings_nav.html.haml b/app/views/groups/_settings_nav.html.haml index 32eae31a47..e6aee22e52 100644 --- a/app/views/groups/_settings_nav.html.haml +++ b/app/views/groups/_settings_nav.html.haml @@ -1,10 +1,11 @@ -%ul.nav.nav-pills.nav-stacked.nav-stacked-menu +%ul.sidebar-subnav = nav_link(path: 'groups#edit') do - = link_to edit_group_path(@group) do - %i.icon-edit - Group + = link_to edit_group_path(@group), title: 'Group' do + %i.fa.fa-pencil-square-o + %span + Group = nav_link(path: 'groups#projects') do - = link_to projects_group_path(@group) do - %i.icon-folder-close - Projects - + = link_to projects_group_path(@group), title: 'Projects' do + %i.fa.fa-folder + %span + Projects diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 97f22df044..49e7180bf9 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -1,56 +1,37 @@ -.row - .col-md-2 - = render 'settings_nav' - .col-md-10 - .panel.panel-default - .panel-heading - %strong= @group.name - group settings: - .panel-body - = form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f| - - if @group.errors.any? - .alert.alert-danger - %span= @group.errors.full_messages.first - .form-group - = f.label :name, class: 'control-label' do - Group name - .col-sm-10 - = f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control left" +.panel.panel-default + .panel-heading + %strong= @group.name + group settings: + .panel-body + = form_for @group, html: { multipart: true, class: "form-horizontal" }, authenticity_token: true do |f| + - if @group.errors.any? + .alert.alert-danger + %span= @group.errors.full_messages.first + = render 'shared/group_form', f: f - .form-group.group-description-holder - = f.label :description, "Details", class: 'control-label' - .col-sm-10 - = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4 + .form-group + .col-sm-2 + .col-sm-10 + = image_tag group_icon(@group), alt: '', class: 'avatar group-avatar s160' + %p.light + - if @group.avatar? + You can change your group avatar here + - else + You can upload a group avatar here + = render 'shared/choose_group_avatar_button', f: f + - if @group.avatar? + %hr + = link_to 'Remove avatar', group_avatar_path(@group.to_param), data: { confirm: "Group avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar" - .form-group - .col-sm-2 - .col-sm-10 - = image_tag group_icon(@group.to_param), alt: '', class: 'avatar s160' - %p.light - - if @group.avatar? - You can change your group avatar here - - else - You can upload a group avatar here - %a.choose-btn.btn.btn-small.js-choose-group-avatar-button - %i.icon-paper-clip - %span Choose File ... -   - %span.file_name.js-avatar-filename File name... - = f.file_field :avatar, class: "js-group-avatar-input hidden" - .light The maximum file size allowed is 100KB. - - if @group.avatar? - %hr - = link_to 'Remove avatar', group_avatar_path(@group.to_param), data: { confirm: "Group avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar" + .form-actions + = f.submit 'Save group', class: "btn btn-save" - .form-actions - = f.submit 'Save group', class: "btn btn-save" +.panel.panel-danger + .panel-heading Remove group + .panel-body + %p + Removing group will cause all child projects and resources to be removed. + %br + %strong Removed group can not be restored! - .panel.panel-danger - .panel-heading Remove group - .panel-body - %p - Removing group will cause all child projects and resources to be removed. - %br - %strong Removed group can not be restored! - - = link_to 'Remove Group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove" + = link_to 'Remove Group', @group, data: {confirm: 'Removed group can not be restored! Are you sure?'}, method: :delete, class: "btn btn-remove" diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml new file mode 100644 index 0000000000..56b1948a47 --- /dev/null +++ b/app/views/groups/group_members/_group_member.html.haml @@ -0,0 +1,54 @@ +- user = member.user +- return unless user || member.invite? +- show_roles = true if show_roles.nil? + +%li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)} + %span{class: ("list-item-name" if show_controls)} + - if member.user + = image_tag avatar_icon(user.email, 16), class: "avatar s16", alt: '' + %strong= user.name + %span.cgray= user.username + - if user == current_user + %span.label.label-success It's you + - if user.blocked? + %label.label.label-danger + %strong Blocked + - else + = image_tag avatar_icon(member.invite_email, 16), class: "avatar s16", alt: '' + %strong + = member.invite_email + %span.cgray + invited + - if member.created_by + by + = link_to member.created_by.name, user_path(member.created_by) + = time_ago_with_tooltip(member.created_at) + + - if show_controls && can?(current_user, :admin_group, @group) + = link_to resend_invite_group_group_member_path(@group, member), method: :post, class: "btn-xs btn", title: 'Resend invite' do + Resend invite + + - if show_roles + %span.pull-right + %strong= member.human_access + - if show_controls + - if can?(current_user, :modify_group_member, member) + = button_tag class: "btn-xs btn js-toggle-button", + title: 'Edit access level', type: 'button' do + %i.fa.fa-pencil-square-o + - if can?(current_user, :destroy_group_member, member) +   + - if current_user == user + = link_to leave_group_group_members_path(@group), data: { confirm: leave_group_message(@group.name)}, method: :delete, class: "btn-xs btn btn-remove", title: 'Remove user from group' do + %i.fa.fa-minus.fa-inverse + - else + = link_to group_group_member_path(@group, member), data: { confirm: remove_user_from_group_message(@group, member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from group' do + %i.fa.fa-minus.fa-inverse + + .edit-member.hide.js-toggle-content + %br + = form_for [@group, member], remote: true do |f| + .prepend-top-10 + = f.select :access_level, options_for_select(GroupMember.access_level_roles, member.access_level), {}, class: 'form-control' + .prepend-top-10 + = f.submit 'Save', class: 'btn btn-save btn-sm' diff --git a/app/views/groups/group_members/_new_group_member.html.haml b/app/views/groups/group_members/_new_group_member.html.haml new file mode 100644 index 0000000000..3361d7e2a8 --- /dev/null +++ b/app/views/groups/group_members/_new_group_member.html.haml @@ -0,0 +1,18 @@ += form_for @group_member, url: group_group_members_path(@group), html: { class: 'form-horizontal users-group-form' } do |f| + .form-group + = f.label :user_ids, "People", class: 'control-label' + .col-sm-10 + = users_select_tag(:user_ids, multiple: true, class: 'input-large', scope: :all, email_user: true) + .help-block + Search for existing users or invite new ones using their email address. + + .form-group + = f.label :access_level, "Group Access", class: 'control-label' + .col-sm-10 + = select_tag :access_level, options_for_select(GroupMember.access_level_roles, @group_member.access_level), class: "project-access-select select2" + .help-block + Read more about role permissions + %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink" + + .form-actions + = f.submit 'Add users to group', class: "btn btn-create" diff --git a/app/views/groups/members.html.haml b/app/views/groups/group_members/index.html.haml similarity index 70% rename from app/views/groups/members.html.haml rename to app/views/groups/group_members/index.html.haml index 19819c9612..c0c9cd170a 100644 --- a/app/views/groups/members.html.haml +++ b/app/views/groups/group_members/index.html.haml @@ -1,4 +1,5 @@ - show_roles = should_user_see_group_roles?(current_user, @group) + %h3.page-title Group members - if show_roles @@ -10,16 +11,16 @@ %hr .clearfix.js-toggle-container - = form_tag members_group_path(@group), method: :get, class: 'form-inline member-search-form' do + = form_tag group_group_members_path(@group), method: :get, class: 'form-inline member-search-form' do .form-group = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input input-mn-300' } - = submit_tag 'Search', class: 'btn' + = button_tag 'Search', class: 'btn' - - if current_user && current_user.can?(:manage_group, @group) + - if current_user && current_user.can?(:admin_group, @group) .pull-right - = link_to '#', class: 'btn btn-new js-toggle-button' do + = button_tag class: 'btn btn-new js-toggle-button', type: 'button' do Add members - %i.icon-chevron-down + %i.fa.fa-chevron-down .js-toggle-content.hide.new-group-member-holder = render "new_group_member" @@ -32,7 +33,8 @@ (#{@members.total_count}) %ul.well-list - @members.each do |member| - = render 'users_groups/users_group', member: member, show_roles: show_roles, show_controls: true + = render 'groups/group_members/group_member', member: member, show_roles: show_roles, show_controls: true + = paginate @members, theme: 'gitlab' :coffeescript diff --git a/app/views/users_groups/update.js.haml b/app/views/groups/group_members/update.js.haml similarity index 100% rename from app/views/users_groups/update.js.haml rename to app/views/groups/group_members/update.js.haml diff --git a/app/views/groups/issues.atom.builder b/app/views/groups/issues.atom.builder index f2005193f8..240001967f 100644 --- a/app/views/groups/issues.atom.builder +++ b/app/views/groups/issues.atom.builder @@ -7,18 +7,7 @@ xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://sear xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? @issues.each do |issue| - xml.entry do - xml.id project_issue_url(issue.project, issue) - xml.link :href => project_issue_url(issue.project, issue) - xml.title truncate(issue.title, :length => 80) - xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, :width => "40", :height => "40", :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 + issue_to_atom(xml, issue) end end diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml index 0152ae8683..6c0d89c4e7 100644 --- a/app/views/groups/issues.html.haml +++ b/app/views/groups/issues.html.haml @@ -9,10 +9,6 @@ To see all issues you should visit #{link_to 'dashboard', issues_dashboard_path} page. %hr -.row - .fixed.sidebar-expand-button.hidden-lg.hidden-md - %i.icon-list.icon-2x - .col-md-3.responsive-side - = render 'shared/filter', entity: 'issue' - .col-md-9 - = render 'shared/issues' +.append-bottom-20 + = render 'shared/issuable_filter' += render 'shared/issues' diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml index 71d346d046..1ad7490563 100644 --- a/app/views/groups/merge_requests.html.haml +++ b/app/views/groups/merge_requests.html.haml @@ -8,10 +8,6 @@ - if current_user To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page. %hr -.row - .fixed.sidebar-expand-button.hidden-lg.hidden-md - %i.icon-list.icon-2x - .col-md-3.responsive-side - = render 'shared/filter', entity: 'merge_request' - .col-md-9 - = render 'shared/merge_requests' +.append-bottom-20 + = render 'shared/issuable_filter' += render 'shared/merge_requests' diff --git a/app/views/groups/milestones/_issue.html.haml b/app/views/groups/milestones/_issue.html.haml index c95c2e8967..09f9b4b896 100644 --- a/app/views/groups/milestones/_issue.html.haml +++ b/app/views/groups/milestones/_issue.html.haml @@ -2,9 +2,9 @@ %span.milestone-row - project = issue.project %strong #{project.name} · - = link_to [project, issue] do + = link_to [project.namespace.becomes(Namespace), project, issue] do %span.cgray ##{issue.iid} - = link_to_gfm issue.title, [project, issue], title: issue.title + = link_to_gfm issue.title, [project.namespace.becomes(Namespace), project, issue], title: issue.title .pull-right.assignee-icon - if issue.assignee - = image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16" + = image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16", alt: '' diff --git a/app/views/groups/milestones/_merge_request.html.haml b/app/views/groups/milestones/_merge_request.html.haml index e0c903bfdb..d0d1426762 100644 --- a/app/views/groups/milestones/_merge_request.html.haml +++ b/app/views/groups/milestones/_merge_request.html.haml @@ -2,9 +2,9 @@ %span.milestone-row - project = merge_request.project %strong #{project.name} · - = link_to [project, merge_request] do + = link_to [project.namespace.becomes(Namespace), project, merge_request] do %span.cgray ##{merge_request.iid} - = link_to_gfm merge_request.title, [project, merge_request], title: merge_request.title + = link_to_gfm merge_request.title, [project.namespace.becomes(Namespace), project, merge_request], title: merge_request.title .pull-right.assignee-icon - if merge_request.assignee - = image_tag avatar_icon(merge_request.assignee.email, 16), class: "avatar s16" + = image_tag avatar_icon(merge_request.assignee.email, 16), class: "avatar s16", alt: '' diff --git a/app/views/groups/milestones/_milestone.html.haml b/app/views/groups/milestones/_milestone.html.haml new file mode 100644 index 0000000000..30093d2d05 --- /dev/null +++ b/app/views/groups/milestones/_milestone.html.haml @@ -0,0 +1,25 @@ +%li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } + .pull-right + - if can?(current_user, :admin_group, @group) + - if milestone.closed? + = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen" + - else + = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close" + %h4 + = link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title) + .row + .col-sm-6 + = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do + = pluralize milestone.issue_count, 'Issue' +   + = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do + = pluralize milestone.merge_requests_count, 'Merge Request' +   + %span.light #{milestone.percent_complete}% complete + .col-sm-6 + = milestone_progress_bar(milestone) + %div + - milestone.milestones.each do |milestone| + = link_to milestone_path(milestone) do + %span.label.label-gray + = milestone.project.name diff --git a/app/views/groups/milestones/index.html.haml b/app/views/groups/milestones/index.html.haml index 54e901173f..008d5a6bd2 100644 --- a/app/views/groups/milestones/index.html.haml +++ b/app/views/groups/milestones/index.html.haml @@ -9,42 +9,14 @@ %hr -.row - .fixed.sidebar-expand-button.hidden-lg.hidden-md - %i.icon-list.icon-2x - .col-md-3.responsive-side - = render 'groups/filter', entity: 'milestone' - .col-md-9 - .panel.panel-default - %ul.well-list - - if @group_milestones.blank? - %li - .nothing-here-block No milestones to show - - else - - @group_milestones.each do |milestone| - %li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone.milestones.first) } - .pull-right - - if can?(current_user, :manage_group, @group) - - if milestone.closed? - = link_to 'Reopen Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-small btn-grouped btn-reopen" - - else - = link_to 'Close Milestone', group_milestone_path(@group, milestone.safe_title, title: milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-small btn-close" - %h4 - = link_to_gfm truncate(milestone.title, length: 100), group_milestone_path(@group, milestone.safe_title, title: milestone.title) - %div - %div - = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do - = pluralize milestone.issue_count, 'Issue' -   - = link_to group_milestone_path(@group, milestone.safe_title, title: milestone.title) do - = pluralize milestone.merge_requests_count, 'Merge Request' -   - %span.light #{milestone.percent_complete}% complete - .progress.progress-info - .progress-bar{style: "width: #{milestone.percent_complete}%;"} - %div - %br - - milestone.projects.each do |project| - %span.label.label-default - = project.name - = paginate @group_milestones, theme: "gitlab" += render 'shared/milestones_filter' +.milestones + .panel.panel-default + %ul.well-list + - if @group_milestones.blank? + %li + .nothing-here-block No milestones to show + - else + - @group_milestones.each do |milestone| + = render 'milestone', milestone: milestone + = paginate @group_milestones, theme: "gitlab" diff --git a/app/views/groups/milestones/show.html.haml b/app/views/groups/milestones/show.html.haml index 411d1822be..fb32f2caa4 100644 --- a/app/views/groups/milestones/show.html.haml +++ b/app/views/groups/milestones/show.html.haml @@ -1,52 +1,51 @@ -%h3.page-title +%h4.page-title + .issue-box{ class: "issue-box-#{@group_milestone.closed? ? 'closed' : 'open'}" } + - if @group_milestone.closed? + Closed + - else + Open Milestone #{@group_milestone.title} .pull-right - - if can?(current_user, :manage_group, @group) + - if can?(current_user, :admin_group, @group) - if @group_milestone.active? - = link_to 'Close Milestone', group_milestone_path(@group, @group_milestone.safe_title, title: @group_milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-small btn-close" + = link_to 'Close Milestone', group_milestone_path(@group, @group_milestone.safe_title, title: @group_milestone.title, milestone: {state_event: :close }), method: :put, class: "btn btn-sm btn-close" - else - = link_to 'Reopen Milestone', group_milestone_path(@group, @group_milestone.safe_title, title: @group_milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-small btn-grouped btn-reopen" + = link_to 'Reopen Milestone', group_milestone_path(@group, @group_milestone.safe_title, title: @group_milestone.title, milestone: {state_event: :activate }), method: :put, class: "btn btn-sm btn-grouped btn-reopen" +%hr - if (@group_milestone.total_items_count == @group_milestone.closed_items_count) && @group_milestone.active? .alert.alert-success %span All issues for this milestone are closed. You may close the milestone now. -.back-link - = link_to group_milestones_path(@group) do - ← To milestones list - -.issue-box{ class: "issue-box-#{@group_milestone.closed? ? 'closed' : 'open'}" } - .state.clearfix - .state-label - - if @group_milestone.closed? - Closed - - else - Open - - %h4.title - = gfm escape_once(@group_milestone.title) - - .description - - @group_milestone.milestones.each do |milestone| - %hr - %h4 - = link_to "#{milestone.project.name} - #{milestone.title}", project_milestone_path(milestone.project, milestone) - %span.pull-right= milestone.expires_at +.description +%table.table + %thead + %tr + %th Project + %th Open issues + %th State + %th Due date + - @group_milestone.milestones.each do |milestone| + %tr + %td + = link_to "#{milestone.project.name}", namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) + %td + = milestone.issues.opened.count + %td - if milestone.closed? - %span.label.label-danger #{milestone.state} - = preserve do - - if milestone.description.present? - = milestone.description + Closed + - else + Open + %td + = milestone.expires_at - .context - %p - Progress: - #{@group_milestone.closed_items_count} closed - – - #{@group_milestone.open_items_count} open - - .progress.progress-info - .progress-bar{style: "width: #{@group_milestone.percent_complete}%;"} +.context + %p.lead + Progress: + #{@group_milestone.closed_items_count} closed + – + #{@group_milestone.open_items_count} open + = milestone_progress_bar(@group_milestone) %ul.nav.nav-tabs %li.active diff --git a/app/views/groups/new.html.haml b/app/views/groups/new.html.haml index cdc087f949..6e17cdaef6 100644 --- a/app/views/groups/new.html.haml +++ b/app/views/groups/new.html.haml @@ -2,37 +2,18 @@ - if @group.errors.any? .alert.alert-danger %span= @group.errors.full_messages.first - .form-group - = f.label :name, class: 'control-label' do - Group name - .col-sm-10 - = f.text_field :name, placeholder: "Ex. OpenSource", class: "form-control", tabindex: 1, autofocus: true - .form-group.group-description-holder - = f.label :description, "Details", class: 'control-label' - .col-sm-10 - = f.text_area :description, maxlength: 250, class: "form-control js-gfm-input", rows: 4, tabindex: 2 + = render 'shared/group_form', f: f, autofocus: true .form-group.group-description-holder = f.label :avatar, "Group avatar", class: 'control-label' .col-sm-10 - %a.choose-btn.btn.btn-small.js-choose-group-avatar-button - %i.icon-paper-clip - %span Choose File ... -   - %span.file_name.js-avatar-filename File name... - = f.file_field :avatar, class: "js-group-avatar-input hidden" - .light The maximum file size allowed is 100KB. + = render 'shared/choose_group_avatar_button', f: f .form-group .col-sm-2 .col-sm-10 - %ul - %li A group is a collection of several projects - %li Groups are private by default - %li Members of a group may only view projects they have permission to access - %li Group project URLs are prefixed with the group namespace - %li Existing projects may be moved into a group + = render 'shared/group_tips' .form-actions = f.submit 'Create group', class: "btn btn-create", tabindex: 3 diff --git a/app/views/groups/projects.html.haml b/app/views/groups/projects.html.haml index a5e752d776..0d547984cc 100644 --- a/app/views/groups/projects.html.haml +++ b/app/views/groups/projects.html.haml @@ -1,29 +1,25 @@ -.row - .col-md-2 - = render 'settings_nav' - .col-md-10 - .panel.panel-default - .panel-heading - %strong= @group.name - projects: - - if can? current_user, :manage_group, @group - .panel-head-actions - = link_to new_project_path(namespace_id: @group.id), class: "btn btn-new" do - %i.icon-plus - New Project - %ul.well-list - - @projects.each do |project| - %li - .list-item-name - = visibility_level_icon(project.visibility_level) - %strong= link_to project.name_with_namespace, project - %span.label.label-gray - = repository_size(project) - .pull-right - = link_to 'Members', project_team_index_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" - = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" - = link_to 'Remove', project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-small btn-remove" - - if @projects.blank? - .nothing-here-block This group has no projects yet +.panel.panel-default + .panel-heading + %strong= @group.name + projects: + - if can? current_user, :admin_group, @group + .panel-head-actions + = link_to new_project_path(namespace_id: @group.id), class: "btn btn-sm btn-success" do + %i.fa.fa-plus + New Project + %ul.well-list + - @projects.each do |project| + %li + .list-item-name + = visibility_level_icon(project.visibility_level) + %strong= link_to project.name_with_namespace, project + %span.label.label-gray + = repository_size(project) + .pull-right + = link_to 'Members', namespace_project_project_members_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm" + = link_to 'Edit', edit_namespace_project_path(project.namespace, project), id: "edit_#{dom_id(project)}", class: "btn btn-sm" + = link_to 'Remove', project, data: { confirm: remove_project_message(project)}, method: :delete, class: "btn btn-sm btn-remove" + - if @projects.blank? + .nothing-here-block This group has no projects yet - = paginate @projects, theme: "gitlab" += paginate @projects, theme: "gitlab" diff --git a/app/views/groups/show.atom.builder b/app/views/groups/show.atom.builder index e07bb7d2fb..c78bd1bd26 100644 --- a/app/views/groups/show.atom.builder +++ b/app/views/groups/show.atom.builder @@ -1,28 +1,12 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.title "Group feed - #{@group.name}" - xml.link :href => group_path(@group, :atom), :rel => "self", :type => "application/atom+xml" - xml.link :href => group_path(@group), :rel => "alternate", :type => "text/html" + xml.link href: group_path(@group, :atom), rel: "self", type: "application/atom+xml" + xml.link href: group_path(@group), rel: "alternate", type: "text/html" xml.id projects_url xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? @events.each do |event| - if event.proper? - xml.entry do - event_link = event_feed_url(event) - event_title = event_feed_title(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.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(event.author_email) - xml.author do |author| - xml.name event.author_name - xml.email event.author_email - end - xml.summary event_title - end - end + event_to_atom(xml, event) end end diff --git a/app/views/groups/show.html.haml b/app/views/groups/show.html.haml index 6db393c882..8df9366ecb 100644 --- a/app/views/groups/show.html.haml +++ b/app/views/groups/show.html.haml @@ -1,37 +1,22 @@ .dashboard - %section.activities.col-md-8.hidden-sm.hidden-xs - - if current_user - = render "events/event_last_push", event: @last_push - = link_to dashboard_path, class: 'btn btn-tiny' do - ← To dashboard -   - %span.cgray - Currently you are only seeing events from the + .header-with-avatar.clearfix + = image_tag group_icon(@group), class: "avatar group-avatar s90" + %h3 = @group.name - group - %hr - = render 'shared/event_filter' - - if @events.any? + .username + @#{@group.path} + - if @group.description.present? + .description + = escaped_autolink(@group.description) + %hr + .row + %section.activities.col-md-8 + - if current_user + = render "events/event_last_push", event: @last_push + = render 'shared/event_filter' .content_list - - else - .nothing-here-block Project activity will be displayed here - = spinner - %aside.side.col-md-4 - .light-well.append-bottom-20 - = image_tag group_icon(@group.path), class: "avatar s90" - .clearfix.light - %h3.page-title - = @group.name - - if @group.description.present? - %p - = auto_link @group.description, link: :urls - = render "projects", projects: @projects - - if current_user - .prepend-top-20 - = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed" do - %strong - %i.icon-rss - News Feed - - %hr - = render 'shared/promo' + = spinner + %aside.side.col-md-4 + = render "projects", projects: @projects + = link_to '#aside', class: 'show-aside' do + %i.fa.fa-angle-left diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml index 500e5dc65e..7b21ca30d8 100644 --- a/app/views/help/_shortcuts.html.haml +++ b/app/views/help/_shortcuts.html.haml @@ -3,30 +3,207 @@ .modal-content .modal-header %a.close{href: "#", "data-dismiss" => "modal"} × - %h3 Keyboard Shortcuts - .modal-body - %h5 Global Shortcuts - %p - %span.label.label-inverse s - – - Focus Search - %p - %span.label.label-inverse ? - – - Show this dialog + %h4 + Keyboard Shortcuts + %small + = link_to '(Show all)', '#', class: 'js-more-help-button' + .modal-body.shortcuts-cheatsheet + .col-lg-4 + %table.shortcut-mappings + %tbody + %tr + %th + %th Global Shortcuts + %tr + %td.shortcut + .key s + %td Focus Search + %tr + %td.shortcut + .key ? + %td Show this dialog + %tbody + %tr + %th + %th Project Files browsing + %tr + %td.shortcut + .key + %i.fa.fa-arrow-up + %td Move selection up + %tr + %td.shortcut + .key + %i.fa.fa-arrow-down + %td Move selection down + %tr + %td.shortcut + .key enter + %td Open Selection - %h5 Project Files browsing - %p - %span.label.label-inverse - %i.icon-arrow-up - – - Move selection up - %p - %span.label.label-inverse - %i.icon-arrow-down - – - Move selection down - %p - %span.label.label-inverse Enter - – - Open selection + .col-lg-4 + %table.shortcut-mappings + %tbody{ class: 'hidden-shortcut project', style: 'display:none' } + %tr + %th + %th Global Dashboard + %tr + %td.shortcut + .key g + .key a + %td + Go to the activity feed + %tr + %td.shortcut + .key g + .key p + %td + Go to projects + %tr + %td.shortcut + .key g + .key i + %td + Go to issues + %tr + %td.shortcut + .key g + .key m + %td + Go to merge requests + %tbody + %tr + %th + %th Project + %tr + %td.shortcut + .key g + .key p + %td + Go to the project's activity feed + %tr + %td.shortcut + .key g + .key f + %td + Go to files + %tr + %td.shortcut + .key g + .key c + %td + Go to commits + %tr + %td.shortcut + .key g + .key n + %td + Go to network graph + %tr + %td.shortcut + .key g + .key g + %td + Go to graphs + %tr + %td.shortcut + .key g + .key i + %td + Go to issues + %tr + %td.shortcut + .key g + .key m + %td + Go to merge requests + %tr + %td.shortcut + .key g + .key s + %td + Go to snippets + .col-lg-4 + %table.shortcut-mappings + %tbody{ class: 'hidden-shortcut network', style: 'display:none' } + %tr + %th + %th Network Graph + %tr + %td.shortcut + .key + %i.fa.fa-arrow-left + \/ + .key h + %td Scroll left + %tr + %td.shortcut + .key + %i.fa.fa-arrow-right + \/ + .key l + %td Scroll right + %tr + %td.shortcut + .key + %i.fa.fa-arrow-up + \/ + .key k + %td Scroll up + %tr + %td.shortcut + .key + %i.fa.fa-arrow-down + \/ + .key j + %td Scroll down + %tr + %td.shortcut + .key + shift + %i.fa.fa-arrow-up + \/ + .key + shift k + %td Scroll to top + %tr + %td.shortcut + .key + shift + %i.fa.fa-arrow-down + \/ + .key + shift j + %td Scroll to bottom + %tbody{ class: 'hidden-shortcut issues', style: 'display:none' } + %tr + %th + %th Issues + %tr + %td.shortcut + .key a + %td Change assignee + %tr + %td.shortcut + .key m + %td Change milestone + %tbody{ class: 'hidden-shortcut merge_reuests', style: 'display:none' } + %tr + %th + %th Merge Requests + %tr + %td.shortcut + .key a + %td Change assignee + %tr + %td.shortcut + .key m + %td Change milestone + + +:javascript + $('.js-more-help-button').click(function(e){ + $(this).remove() + $('.hidden-shortcut').show() + e.preventDefault() + }); diff --git a/app/views/help/index.html.haml b/app/views/help/index.html.haml index 219693af09..af39dfeac5 100644 --- a/app/views/help/index.html.haml +++ b/app/views/help/index.html.haml @@ -14,7 +14,7 @@ %br Used by more than 100,000 organizations, GitLab is the most popular solution to manage git repositories on-premises. %br - Read more about GitLab at #{link_to "www.gitlab.com", "https://www.gitlab.com/", target: "_blank"}. + Read more about GitLab at #{link_to promo_host, promo_url, target: '_blank'}. %hr @@ -34,11 +34,17 @@ %ul.well-list %li See our website for - = link_to "getting help", "https://www.gitlab.com/getting-help/" + = link_to 'getting help', promo_url + '/getting-help/' %li Use the - = link_to "search bar", '#', onclick: "$('#search').focus();" + = link_to 'search bar', '#', onclick: 'Shortcuts.focusSearch(event)' on the top of this page %li Use - = link_to "shortcuts", '#', onclick: "new Shortcuts()" + = link_to 'shortcuts', '#', onclick: 'Shortcuts.showHelp(event)' + %li + Get a support + = link_to 'subscription', 'https://about.gitlab.com/pricing/' + %li + = link_to 'Compare', 'https://about.gitlab.com/features/#compare' + GitLab editions diff --git a/app/views/help/show.html.haml b/app/views/help/show.html.haml index 67f9cc41cf..cc1be6a717 100644 --- a/app/views/help/show.html.haml +++ b/app/views/help/show.html.haml @@ -1,2 +1,2 @@ .documentation.wiki - = markdown File.read(Rails.root.join('doc', @category, @file + '.md')) + = markdown @markdown.gsub('$your_email', current_user.email) diff --git a/app/views/help/ui.html.haml b/app/views/help/ui.html.haml new file mode 100644 index 0000000000..246a6c1bdf --- /dev/null +++ b/app/views/help/ui.html.haml @@ -0,0 +1,227 @@ +- lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed fermentum nisi sapien, non consequat lectus aliquam ultrices. Suspendisse sodales est euismod nunc condimentum, a consectetur diam ornare." + +.gitlab-ui-dev-kit + %h1 GitLab UI development kit + %p.light + Use page inspector in your browser to check element classes and structure + of examples below. + %hr + %ul + %li + = link_to 'Blocks', '#blocks' + %li + = link_to 'Lists', '#lists' + %li + = link_to 'Tables', '#tables' + %li + = link_to 'Buttons', '#buttons' + %li + = link_to 'Panels', '#panels' + %li + = link_to 'Alerts', '#alerts' + %li + = link_to 'Forms', '#forms' + %li + = link_to 'Files', '#file' + %li + = link_to 'Markdown', '#markdown' + + %h2#blocks Blocks + + %h3 + %code .well + + + .well + %h4 Something + = lorem + + + %h2#lists Lists + + %h3 + %code .well-list + %ul.well-list + %li + One item + %li + One item + %li + One item + + %h3 + %code .panel .well-list + + .panel.panel-default + .panel-heading Your list + %ul.well-list + %li + One item + %li + One item + %li + One item + + %h3 + %code .bordered-list + %ul.bordered-list + %li + One item + %li + One item + %li + One item + + + + %h2#tables Tables + + .example + %table.table + %thead + %tr + %th # + %th First Name + %th Last Name + %th Username + %tbody + %tr + %td 1 + %td Mark + %td Otto + %td @mdo + %tr + %td 2 + %td Jacob + %td Thornton + %td @fat + %tr + %td 3 + %td Larry + %td the Bird + %td @twitter + + + %h2#buttons Buttons + + .example + %button.btn.btn-default{:type => "button"} Default + %button.btn.btn-primary{:type => "button"} Primary + %button.btn.btn-success{:type => "button"} Success + %button.btn.btn-info{:type => "button"} Info + %button.btn.btn-warning{:type => "button"} Warning + %button.btn.btn-danger{:type => "button"} Danger + %button.btn.btn-link{:type => "button"} Link + + %h2#panels Panels + + .row + .col-md-6 + .panel.panel-success + .panel-heading Success + .panel-body + = lorem + .panel.panel-primary + .panel-heading Primary + .panel-body + = lorem + .panel.panel-info + .panel-heading Info + .panel-body + = lorem + .col-md-6 + .panel.panel-warning + .panel-heading Warning + .panel-body + = lorem + .panel.panel-danger + .panel-heading Danger + .panel-body + = lorem + + %h2#alert Alerts + + .row + .col-md-6 + .alert.alert-success + = lorem + .alert.alert-primary + = lorem + .alert.alert-info + = lorem + .col-md-6 + .alert.alert-warning + = lorem + .alert.alert-danger + = lorem + + %h2#forms Forms + + %h3 + %code form.horizontal-form + + %form.form-horizontal + .form-group + %label.col-sm-2.control-label{:for => "inputEmail3"} Email + .col-sm-10 + %input#inputEmail3.form-control{:placeholder => "Email", :type => "email"}/ + .form-group + %label.col-sm-2.control-label{:for => "inputPassword3"} Password + .col-sm-10 + %input#inputPassword3.form-control{:placeholder => "Password", :type => "password"}/ + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + %label + %input{:type => "checkbox"}/ + Remember me + .form-group + .col-sm-offset-2.col-sm-10 + %button.btn.btn-default{:type => "submit"} Sign in + + %h3 + %code form + + %form + .form-group + %label{:for => "exampleInputEmail1"} Email address + %input#exampleInputEmail1.form-control{:placeholder => "Enter email", :type => "email"}/ + .form-group + %label{:for => "exampleInputPassword1"} Password + %input#exampleInputPassword1.form-control{:placeholder => "Password", :type => "password"}/ + .checkbox + %label + %input{:type => "checkbox"}/ + Remember me + %button.btn.btn-default{:type => "submit"} Sign in + + %h2#file File + %h3 + %code .file-holder + + - blob = Snippet.new(content: "Wow\nSuch\nFile") + .example + .file-holder + .file-title + Awesome file + .file-actions + .btn-group + %a.btn Edit + %a.btn Remove + .file-contenta.code + = render 'shared/file_highlight', blob: blob + + + %h2#markdown Markdown + %h3 + %code .md or .wiki and others + + Markdown rendering has a bit different css and presented in next UI elements: + + %ul + %li comment + %li issue, merge request description + %li wiki page + %li help page + + You can check how markdown rendered at #{link_to 'Markdown help page', help_page_path("markdown", "markdown")}. diff --git a/app/views/import/base/create.js.haml b/app/views/import/base/create.js.haml new file mode 100644 index 0000000000..90a6f5f9d2 --- /dev/null +++ b/app/views/import/base/create.js.haml @@ -0,0 +1,25 @@ +- if @already_been_taken + :plain + target_field = $("tr#repo_#{@repo_id} .import-target") + origin_target = target_field.text() + project_name = "#{@project_name}" + origin_namespace = "#{@target_namespace}" + target_field.empty() + target_field.append("

    This namespace already been taken! Please choose another one

    ") + target_field.append("") + target_field.append("/" + project_name) + target_field.data("project_name", project_name) + target_field.find('input').prop("value", origin_namespace) +- elsif @access_denied + :plain + job = $("tr#repo_#{@repo_id}") + job.find(".import-actions").html("

    Access denied! Please verify you can add deploy keys to this repository.

    ") +- else + :plain + job = $("tr#repo_#{@repo_id}") + job.attr("id", "project_#{@project.id}") + target_field = job.find(".import-target") + target_field.empty() + target_field.append('#{link_to @project.path_with_namespace, [@project.namespace.becomes(Namespace), @project]}') + $("table.import-jobs tbody").prepend(job) + job.addClass("active").find(".import-actions").html(" started") diff --git a/app/views/import/bitbucket/status.html.haml b/app/views/import/bitbucket/status.html.haml new file mode 100644 index 0000000000..4e49bbbc7f --- /dev/null +++ b/app/views/import/bitbucket/status.html.haml @@ -0,0 +1,45 @@ +%h3.page-title + %i.fa.fa-bitbucket + Import projects from Bitbucket + +%p.light + Select projects you want to import. +%hr +%p + = button_tag 'Import all projects', class: "btn btn-success js-import-all" + +%table.table.import-jobs + %thead + %tr + %th From Bitbucket + %th To GitLab + %th Status + %tbody + - @already_added_projects.each do |project| + %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} + %td + = link_to project.import_source, "https://bitbucket.org/#{project.import_source}", target: "_blank" + %td + %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] + %td.job-status + - if project.import_status == 'finished' + %span + %i.fa.fa-check + done + - elsif project.import_status == 'started' + %i.fa.fa-spinner.fa-spin + started + - else + = project.human_import_status_name + + - @repos.each do |repo| + %tr{id: "repo_#{repo["owner"]}___#{repo["slug"]}"} + %td + = link_to "#{repo["owner"]}/#{repo["slug"]}", "https://bitbucket.org/#{repo["owner"]}/#{repo["slug"]}", target: "_blank" + %td.import-target + = "#{repo["owner"]}/#{repo["slug"]}" + %td.import-actions.job-status + = button_tag "Import", class: "btn js-add-to-import" + +:coffeescript + new ImporterStatus("#{jobs_import_bitbucket_path}", "#{import_bitbucket_path}") diff --git a/app/views/import/github/status.html.haml b/app/views/import/github/status.html.haml new file mode 100644 index 0000000000..f0bc3e6b1a --- /dev/null +++ b/app/views/import/github/status.html.haml @@ -0,0 +1,45 @@ +%h3.page-title + %i.fa.fa-github + Import projects from GitHub + +%p.light + Select projects you want to import. +%hr +%p + = button_tag 'Import all projects', class: "btn btn-success js-import-all" + +%table.table.import-jobs + %thead + %tr + %th From GitHub + %th To GitLab + %th Status + %tbody + - @already_added_projects.each do |project| + %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} + %td + = link_to project.import_source, "https://github.com/#{project.import_source}", target: "_blank" + %td + %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] + %td.job-status + - if project.import_status == 'finished' + %span + %i.fa.fa-check + done + - elsif project.import_status == 'started' + %i.fa.fa-spinner.fa-spin + started + - else + = project.human_import_status_name + + - @repos.each do |repo| + %tr{id: "repo_#{repo.id}"} + %td + = link_to repo.full_name, "https://github.com/#{repo.full_name}", target: "_blank" + %td.import-target + = repo.full_name + %td.import-actions.job-status + = button_tag "Import", class: "btn js-add-to-import" + +:coffeescript + new ImporterStatus("#{jobs_import_github_path}", "#{import_github_path}") diff --git a/app/views/import/gitlab/status.html.haml b/app/views/import/gitlab/status.html.haml new file mode 100644 index 0000000000..33b0a21acf --- /dev/null +++ b/app/views/import/gitlab/status.html.haml @@ -0,0 +1,45 @@ +%h3.page-title + %i.fa.fa-heart + Import projects from GitLab.com + +%p.light + Select projects you want to import. +%hr +%p + = button_tag 'Import all projects', class: "btn btn-success js-import-all" + +%table.table.import-jobs + %thead + %tr + %th From GitLab.com + %th To this GitLab instance + %th Status + %tbody + - @already_added_projects.each do |project| + %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} + %td + = link_to project.import_source, "https://gitlab.com/#{project.import_source}", target: "_blank" + %td + %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] + %td.job-status + - if project.import_status == 'finished' + %span + %i.fa.fa-check + done + - elsif project.import_status == 'started' + %i.fa.fa-spinner.fa-spin + started + - else + = project.human_import_status_name + + - @repos.each do |repo| + %tr{id: "repo_#{repo["id"]}"} + %td + = link_to repo["path_with_namespace"], "https://gitlab.com/#{repo["path_with_namespace"]}", target: "_blank" + %td.import-target + = repo["path_with_namespace"] + %td.import-actions.job-status + = button_tag "Import", class: "btn js-add-to-import" + +:coffeescript + new ImporterStatus("#{jobs_import_gitlab_path}", "#{import_gitlab_path}") diff --git a/app/views/import/gitorious/status.html.haml b/app/views/import/gitorious/status.html.haml new file mode 100644 index 0000000000..78c5e957be --- /dev/null +++ b/app/views/import/gitorious/status.html.haml @@ -0,0 +1,45 @@ +%h3.page-title + %i.icon-gitorious.icon-gitorious-big + Import projects from Gitorious.org + +%p.light + Select projects you want to import. +%hr +%p + = button_tag 'Import all projects', class: "btn btn-success js-import-all" + +%table.table.import-jobs + %thead + %tr + %th From Gitorious.org + %th To GitLab + %th Status + %tbody + - @already_added_projects.each do |project| + %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} + %td + = link_to project.import_source, "https://gitorious.org/#{project.import_source}", target: "_blank" + %td + %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] + %td.job-status + - if project.import_status == 'finished' + %span + %i.fa.fa-check + done + - elsif project.import_status == 'started' + %i.fa.fa-spinner.fa-spin + started + - else + = project.human_import_status_name + + - @repos.each do |repo| + %tr{id: "repo_#{repo.id}"} + %td + = link_to repo.full_name, "https://gitorious.org/#{repo.full_name}", target: "_blank" + %td.import-target + = repo.full_name + %td.import-actions.job-status + = button_tag "Import", class: "btn js-add-to-import" + +:coffeescript + new ImporterStatus("#{jobs_import_gitorious_path}", "#{import_gitorious_path}") diff --git a/app/views/import/google_code/new.html.haml b/app/views/import/google_code/new.html.haml new file mode 100644 index 0000000000..ce78fec205 --- /dev/null +++ b/app/views/import/google_code/new.html.haml @@ -0,0 +1,60 @@ +%h3.page-title + %i.fa.fa-google + Import projects from Google Code +%hr + += form_tag callback_import_google_code_path, class: 'form-horizontal', multipart: true do + %p + Follow the steps below to export your Google Code project data. + In the next step, you'll be able to select the projects you want to import. + %ol + %li + %p + Go to + #{link_to "Google Takeout", "https://www.google.com/settings/takeout", target: "_blank"}. + %li + %p + Make sure you're logged into the account that owns the projects you'd like to import. + %li + %p + Click the Select none button on the right, since we only need "Google Code Project Hosting". + %li + %p + Scroll down to Google Code Project Hosting and enable the switch on the right. + %li + %p + Choose Next at the bottom of the page. + %li + %p + Leave the "File type" and "Delivery method" options on their default values. + %li + %p + Choose Create archive and wait for archiving to complete. + %li + %p + Click the Download button and wait for downloading to complete. + %li + %p + Find the downloaded ZIP file and decompress it. + %li + %p + Find the newly extracted Takeout/Google Code Project Hosting/GoogleCodeProjectHosting.json file. + %li + %p + Upload GoogleCodeProjectHosting.json here: + %p + %input{type: "file", name: "dump_file", id: "dump_file"} + %li + %p + Do you want to customize how Google Code email addresses and usernames are imported into GitLab? + %p + = label_tag :create_user_map_0 do + = radio_button_tag :create_user_map, 0, true + No, directly import the existing email addresses and usernames. + %p + = label_tag :create_user_map_1 do + = radio_button_tag :create_user_map, 1, false + Yes, let me map Google Code users to full names or GitLab users. + %li + %p + = submit_tag 'Continue to the next step', class: "btn btn-create" diff --git a/app/views/import/google_code/new_user_map.html.haml b/app/views/import/google_code/new_user_map.html.haml new file mode 100644 index 0000000000..9c6824ecad --- /dev/null +++ b/app/views/import/google_code/new_user_map.html.haml @@ -0,0 +1,42 @@ +%h3.page-title + %i.fa.fa-google + Import projects from Google Code +%hr + += form_tag create_user_map_import_google_code_path, class: 'form-horizontal' do + %p + Customize how Google Code email addresses and usernames are imported into GitLab. + In the next step, you'll be able to select the projects you want to import. + %p + The user map is a JSON document mapping the Google Code users that participated on your projects to the way their email addresses and usernames will be imported into GitLab. You can change this by changing the value on the right hand side of :. Be sure to preserve the surrounding double quotes, other punctuation and the email address or username on the left hand side. + %ul + %li + %strong Default: Directly import the Google Code email address or username + %p + "johnsmith@example.com": "johnsm...@example.com" + will add "By johnsm...@example.com" to all issues and comments originally created by johnsmith@example.com. + The email address or username is masked to ensure the user's privacy. + %li + %strong Map a Google Code user to a GitLab user + %p + "johnsmith@example.com": "@johnsmith" + will add "By @johnsmith" to all issues and comments originally created by johnsmith@example.com, + and will set @johnsmith as the assignee on all issues originally assigned to johnsmith@example.com. + %li + %strong Map a Google Code user to a full name + %p + "johnsmith@example.com": "John Smith" + will add "By John Smith" to all issues and comments originally created by johnsmith@example.com. + %li + %strong Map a Google Code user to a full email address + %p + "johnsmith@example.com": "johnsmith@example.com" + will add "By johnsmith@example.com" to all issues and comments originally created by johnsmith@example.com. + By default, the email address or username is masked to ensure the user's privacy. Use this option if you want to show the full email address. + + .form-group + .col-sm-12 + = text_area_tag :user_map, JSON.pretty_generate(@user_map), class: 'form-control', rows: 15 + + .form-actions + = submit_tag 'Continue to the next step', class: "btn btn-create" diff --git a/app/views/import/google_code/status.html.haml b/app/views/import/google_code/status.html.haml new file mode 100644 index 0000000000..2013b8c03c --- /dev/null +++ b/app/views/import/google_code/status.html.haml @@ -0,0 +1,49 @@ +%h3.page-title + %i.fa.fa-google + Import projects from Google Code + +%p.light + Select projects you want to import. +%p.light + Optionally, you can + = link_to "customize", new_user_map_import_google_code_path + how Google Code email addresses and usernames are imported into GitLab. +%hr +%p + = button_tag 'Import all projects', class: "btn btn-success js-import-all" + +%table.table.import-jobs + %thead + %tr + %th From Google Code + %th To GitLab + %th Status + %tbody + - @already_added_projects.each do |project| + %tr{id: "project_#{project.id}", class: "#{project_status_css_class(project.import_status)}"} + %td + = link_to project.import_source, "https://code.google.com/p/#{project.import_source}", target: "_blank" + %td + %strong= link_to project.path_with_namespace, [project.namespace.becomes(Namespace), project] + %td.job-status + - if project.import_status == 'finished' + %span + %i.fa.fa-check + done + - elsif project.import_status == 'started' + %i.fa.fa-spinner.fa-spin + started + - else + = project.human_import_status_name + + - @repos.each do |repo| + %tr{id: "repo_#{repo.id}"} + %td + = link_to repo.name, "https://code.google.com/p/#{repo.name}", target: "_blank" + %td.import-target + = "#{current_user.username}/#{repo.name}" + %td.import-actions.job-status + = button_tag "Import", class: "btn js-add-to-import" + +:coffeescript + new ImporterStatus("#{jobs_import_google_code_path}", "#{import_google_code_path}") diff --git a/app/views/invites/show.html.haml b/app/views/invites/show.html.haml new file mode 100644 index 0000000000..ab0ecffe4d --- /dev/null +++ b/app/views/invites/show.html.haml @@ -0,0 +1,29 @@ +%h3.page-title Invitation + +%p + You have been invited + - if inviter = @member.created_by + by + = link_to inviter.name, user_url(inviter) + to join + - case @member.source + - when Project + - project = @member.source + project + %strong + = link_to project.name_with_namespace, namespace_project_url(project.namespace, project) + - when Group + - group = @member.source + group + %strong + = link_to group.name, group_url(group) + as #{@member.human_access}. + +- if @member.source.users.include?(current_user) + %p + However, you are already a member of this #{@member.source.is_a?(Group) ? "group" : "project"}. + Sign in using a different account to accept the invitation. +- else + .actions + = link_to "Accept invitation", accept_invite_url(@token), method: :post, class: "btn btn-success" + = link_to "Decline", decline_invite_url(@token), method: :post, class: "btn btn-danger prepend-left-10" diff --git a/app/views/layouts/_broadcast.html.haml b/app/views/layouts/_broadcast.html.haml index 5794e3de33..e7d477c225 100644 --- a/app/views/layouts/_broadcast.html.haml +++ b/app/views/layouts/_broadcast.html.haml @@ -1,4 +1,4 @@ - if broadcast_message.present? .broadcast-message{ style: broadcast_styling(broadcast_message) } - %i.icon-bullhorn + %i.fa.fa-bullhorn = broadcast_message.message diff --git a/app/views/layouts/_collapse_button.html.haml b/app/views/layouts/_collapse_button.html.haml new file mode 100644 index 0000000000..2ed51d87ca --- /dev/null +++ b/app/views/layouts/_collapse_button.html.haml @@ -0,0 +1,4 @@ +- if nav_menu_collapsed? + = link_to icon('angle-right'), '#', class: 'toggle-nav-collapse', title: "Open/Close" +- else + = link_to icon('angle-left'), '#', class: 'toggle-nav-collapse', title: "Open/Close" diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml index 0c27f679de..d12145651a 100644 --- a/app/views/layouts/_head.html.haml +++ b/app/views/layouts/_head.html.haml @@ -1,12 +1,5 @@ %head %meta{charset: "utf-8"} - - -# Go repository retrieval support - -# Need to be the fist thing in the head - -# Since Go is using an XML parser to process HTML5 - -# https://github.com/gitlabhq/gitlabhq/pull/5958#issuecomment-45397555 - - if controller_name == 'projects' && action_name == 'show' - %meta{name: "go-import", content: "#{@project.web_url_without_protocol} git #{@project.web_url}.git"} %meta{content: "GitLab Community Edition", name: "description"} %title @@ -18,8 +11,8 @@ = javascript_include_tag "application" = csrf_meta_tags = include_gon - :erb - + %meta{name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1'} + %meta{name: 'theme-color', content: '#474D57'} = render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id') = render 'layouts/piwik' if extra_config.has_key?('piwik_url') && extra_config.has_key?('piwik_site_id') @@ -30,6 +23,6 @@ = auto_discovery_link_tag :atom, projects_url(:atom, private_token: current_user.private_token), title: "Dashboard feed" - if @project && !@project.new_record? - if current_controller?(:tree, :commits) - = auto_discovery_link_tag(:atom, project_commits_url(@project, @ref, format: :atom, private_token: current_user.private_token), title: "Recent commits to #{@project.name}:#{@ref}") + = auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "Recent commits to #{@project.name}:#{@ref}") - if current_controller?(:issues) - = auto_discovery_link_tag(:atom, project_issues_url(@project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues") + = auto_discovery_link_tag(:atom, namespace_project_issues_url(@project.namespace, @project, :atom, private_token: current_user.private_token), title: "#{@project.name} issues") diff --git a/app/views/layouts/_head_panel.html.haml b/app/views/layouts/_head_panel.html.haml index 7c727aca78..d58582c107 100644 --- a/app/views/layouts/_head_panel.html.haml +++ b/app/views/layouts/_head_panel.html.haml @@ -1,16 +1,14 @@ -%header.navbar.navbar-static-top.navbar-gitlab +%header.navbar.navbar-fixed-top.navbar-gitlab .navbar-inner .container %div.app_logo - %span.separator = link_to root_path, class: "home has_bottom_tooltip", title: "Dashboard" do - %h1 GITLAB - %span.separator + = brand_header_logo %h1.title= title %button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"} %span.sr-only Toggle navigation - %i.icon-reorder + %i.fa.fa-bars .navbar-collapse.collapse %ul.nav.navbar-nav @@ -18,31 +16,33 @@ = render "layouts/search" %li.visible-sm.visible-xs = link_to search_path, title: "Search", class: 'has_bottom_tooltip', 'data-original-title' => 'Search area' do - %i.icon-search + %i.fa.fa-search %li = link_to help_path, title: 'Help', class: 'has_bottom_tooltip', 'data-original-title' => 'Help' do - %i.icon-question-sign + %i.fa.fa-question-circle %li = link_to explore_root_path, title: "Explore", class: 'has_bottom_tooltip', 'data-original-title' => 'Public area' do - %i.icon-globe + %i.fa.fa-globe %li - = link_to user_snippets_path(current_user), title: "My snippets", class: 'has_bottom_tooltip', 'data-original-title' => 'My snippets' do - %i.icon-paste + = link_to user_snippets_path(current_user), title: "Your snippets", class: 'has_bottom_tooltip', 'data-original-title' => 'Your snippets' do + %i.fa.fa-clipboard - if current_user.is_admin? %li = link_to admin_root_path, title: "Admin area", class: 'has_bottom_tooltip', 'data-original-title' => 'Admin area' do - %i.icon-cogs + %i.fa.fa-cogs - if current_user.can_create_project? %li = link_to new_project_path, title: "New project", class: 'has_bottom_tooltip', 'data-original-title' => 'New project' do - %i.icon-plus + %i.fa.fa-plus %li = link_to profile_path, title: "Profile settings", class: 'has_bottom_tooltip', 'data-original-title' => 'Profile settings"' do - %i.icon-user + %i.fa.fa-user %li - = link_to destroy_user_session_path, class: "logout", method: :delete, title: "Logout", class: 'has_bottom_tooltip', 'data-original-title' => 'Logout' do - %i.icon-signout + = link_to destroy_user_session_path, class: "logout", method: :delete, title: "Sign out", class: 'has_bottom_tooltip', 'data-original-title' => 'Sign out' do + %i.fa.fa-sign-out %li.hidden-xs - = link_to current_user, class: "profile-pic", id: 'profile-pic' do - = image_tag avatar_icon(current_user.email, 26), alt: 'User activity' + = link_to current_user, class: "profile-pic has_bottom_tooltip", id: 'profile-pic', 'data-original-title' => 'Your profile' do + = image_tag avatar_icon(current_user.email, 60), alt: 'User activity' + += render 'shared/outdated_browser' diff --git a/app/views/layouts/_init_auto_complete.html.haml b/app/views/layouts/_init_auto_complete.html.haml index 353f7ce34f..3c58f10e75 100644 --- a/app/views/layouts/_init_auto_complete.html.haml +++ b/app/views/layouts/_init_auto_complete.html.haml @@ -1,3 +1,3 @@ :javascript - GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_project_path(@project, type: @noteable.class, type_id: params[:id])}" + GitLab.GfmAutoComplete.dataSource = "#{autocomplete_sources_namespace_project_path(@project.namespace, @project, type: @noteable.class, type_id: params[:id])}" GitLab.GfmAutoComplete.setup(); diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml new file mode 100644 index 0000000000..422966cdc5 --- /dev/null +++ b/app/views/layouts/_page.html.haml @@ -0,0 +1,23 @@ +- if defined?(sidebar) + .page-with-sidebar{ class: nav_sidebar_class } + = render "layouts/broadcast" + .sidebar-wrapper + = render(sidebar) + .collapse-nav + = render partial: 'layouts/collapse_button' + .content-wrapper + .container-fluid + .content + = render "layouts/flash" + .clearfix + = yield +- else + .container.navless-container + .content + = yield + += yield :embedded_scripts + +:coffeescript + $('.page-sidebar-collapsed .nav-sidebar a').tooltip placement: "right" + diff --git a/app/views/layouts/_public_head_panel.html.haml b/app/views/layouts/_public_head_panel.html.haml index 945c66e2ad..3d6d2bfc00 100644 --- a/app/views/layouts/_public_head_panel.html.haml +++ b/app/views/layouts/_public_head_panel.html.haml @@ -1,22 +1,22 @@ -%header.navbar.navbar-static-top.navbar-gitlab +%header.navbar.navbar-fixed-top.navbar-gitlab .navbar-inner .container %div.app_logo - %span.separator = link_to explore_root_path, class: "home" do - %h1 GITLAB - %span.separator + = brand_header_logo %h1.title= title %button.navbar-toggle{"data-target" => ".navbar-collapse", "data-toggle" => "collapse", type: "button"} %span.sr-only Toggle navigation - %i.icon-reorder + %i.fa.fa-bars - .pull-right.hidden-xs - = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-new' + - unless current_controller?('sessions') + .pull-right.hidden-xs + = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes'), class: 'btn btn-sign-in btn-new append-right-10' - .navbar-collapse.collapse - %ul.nav.navbar-nav - %li.visible-xs - = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes') + .navbar-collapse.collapse + %ul.nav.navbar-nav + %li.visible-xs + = link_to "Sign in", new_session_path(:user, redirect_to_referer: 'yes') += render 'shared/outdated_browser' diff --git a/app/views/layouts/_search.html.haml b/app/views/layouts/_search.html.haml index caf0e39234..04f7984685 100644 --- a/app/views/layouts/_search.html.haml +++ b/app/views/layouts/_search.html.haml @@ -4,7 +4,25 @@ = hidden_field_tag :group_id, @group.try(:id) - if @project && @project.persisted? = hidden_field_tag :project_id, @project.id - = hidden_field_tag :search_code, true + + - if current_controller?(:issues) + = hidden_field_tag :scope, 'issues' + - elsif current_controller?(:merge_requests) + = hidden_field_tag :scope, 'merge_requests' + - elsif current_controller?(:wikis) + = hidden_field_tag :scope, 'wiki_blobs' + - else + = hidden_field_tag :search_code, true + + - if @snippet || @snippets + = hidden_field_tag :snippets, true = hidden_field_tag :repository_ref, @ref - = submit_tag 'Go' if ENV['RAILS_ENV'] == 'test' + = button_tag 'Go' if ENV['RAILS_ENV'] == 'test' .search-autocomplete-opts.hide{:'data-autocomplete-path' => search_autocomplete_path, :'data-autocomplete-project-id' => @project.try(:id), :'data-autocomplete-project-ref' => @ref } + +:javascript + $('.search-input').on('keyup', function(e) { + if (e.keyCode == 27) { + $('.search-input').blur() + } + }) diff --git a/app/views/layouts/admin.html.haml b/app/views/layouts/admin.html.haml index 1ea91a1914..ab84e87c30 100644 --- a/app/views/layouts/admin.html.haml +++ b/app/views/layouts/admin.html.haml @@ -1,13 +1,6 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: "Admin area" - %body{class: "#{app_theme} admin", :'data-page' => body_data_page} - = render "layouts/broadcast" - = render "layouts/head_panel", title: "Admin area" - = render "layouts/flash" - %nav.main-nav.navbar-collapse.collapse - .container= render 'layouts/nav/admin' - - .container - .content= yield - = yield :embedded_scripts + %body{class: "#{app_theme} admin", :'data-page' => body_data_page} + = render "layouts/head_panel", title: link_to("Admin area", admin_root_path) + = render 'layouts/page', sidebar: 'layouts/nav/admin' diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index 511db389e0..6bd8ac4adb 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -1,12 +1,6 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: "Dashboard" - %body{class: "#{app_theme} application", :'data-page' => body_data_page } - = render "layouts/broadcast" - = render "layouts/head_panel", title: "Dashboard" - = render "layouts/flash" - %nav.main-nav.navbar-collapse.collapse - .container= render 'layouts/nav/dashboard' - - .container - .content= yield + %body{class: "#{app_theme} application", :'data-page' => body_data_page } + = render "layouts/head_panel", title: link_to("Dashboard", root_path) + = render 'layouts/page', sidebar: 'layouts/nav/dashboard' diff --git a/app/views/layouts/devise.html.haml b/app/views/layouts/devise.html.haml index dd70836cdc..6f805f1c9d 100644 --- a/app/views/layouts/devise.html.haml +++ b/app/views/layouts/devise.html.haml @@ -1,35 +1,32 @@ !!! 5 %html{ lang: "en"} = render "layouts/head" - %body.ui_basic.login-page - = render "layouts/flash" - .container + %body.ui_mars.login-page.application + = render "layouts/broadcast" + = render "layouts/public_head_panel", title: '' + .container.navless-container .content - .login-title - %h1= brand_title - %hr - .container - .content - .row - .col-md-7 - - if brand_item - .brand-image - = brand_image - .brand_text - = brand_text - - else - .brand-image.default-brand-image.hidden-sm.hidden-xs - = image_tag 'brand_logo.png' - .brand_text.hidden-xs - %h2 Open source software to collaborate on code - - %p.lead - Manage git repositories with fine grained access controls that keep your code secure. - Perform code reviews and enhance collaboration with merge requests. - Each project can also have an issue tracker and a wiki. - - .col-md-5 + = render "layouts/flash" + .row.prepend-top-20 + .col-sm-5.pull-right = yield + .col-sm-7.brand-holder.pull-left + %h1 + = brand_title + - if brand_item + = brand_image + = brand_text + - else + %h3 Open source software to collaborate on code + + %p + Manage git repositories with fine grained access controls that keep your code secure. + Perform code reviews and enhance collaboration with merge requests. + Each project can also have an issue tracker and a wiki. + + - if extra_sign_in_text.present? + = markdown(extra_sign_in_text) + %hr .container .footer-links diff --git a/app/views/layouts/errors.html.haml b/app/views/layouts/errors.html.haml index d0e276d751..e51fd4cb82 100644 --- a/app/views/layouts/errors.html.haml +++ b/app/views/layouts/errors.html.haml @@ -1,9 +1,9 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: "Error" - %body{class: "#{app_theme} application"} + %body{class: "#{app_theme} application"} = render "layouts/head_panel", title: "" if current_user - = render "layouts/flash" .container.navless-container + = render "layouts/flash" .error-page = yield diff --git a/app/views/layouts/explore.html.haml b/app/views/layouts/explore.html.haml index d023846c5e..2bd0b8d85c 100644 --- a/app/views/layouts/explore.html.haml +++ b/app/views/layouts/explore.html.haml @@ -2,12 +2,12 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: page_title - %body{class: "#{app_theme} application", :'data-page' => body_data_page} + %body{class: "#{app_theme} application", :'data-page' => body_data_page} = render "layouts/broadcast" - if current_user - = render "layouts/head_panel", title: page_title + = render "layouts/head_panel", title: link_to(page_title, explore_root_path) - else - = render "layouts/public_head_panel", title: page_title + = render "layouts/public_head_panel", title: link_to(page_title, explore_root_path) .container.navless-container .content .explore-title diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml index fb4a3a3ba9..f4a6bee15f 100644 --- a/app/views/layouts/group.html.haml +++ b/app/views/layouts/group.html.haml @@ -1,12 +1,6 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: group_head_title - %body{class: "#{app_theme} application", :'data-page' => body_data_page} - = render "layouts/broadcast" - = render "layouts/head_panel", title: "group: #{@group.name}" - = render "layouts/flash" - %nav.main-nav.navbar-collapse.collapse - .container= render 'layouts/nav/group' - - .container - .content= yield + %body{class: "#{app_theme} application", :'data-page' => body_data_page} + = render "layouts/head_panel", title: link_to(@group.name, group_path(@group)) + = render 'layouts/page', sidebar: 'layouts/nav/group' diff --git a/app/views/layouts/nav/_admin.html.haml b/app/views/layouts/nav/_admin.html.haml index c57216f01c..34efceb37d 100644 --- a/app/views/layouts/nav/_admin.html.haml +++ b/app/views/layouts/nav/_admin.html.haml @@ -1,19 +1,65 @@ -%ul +%ul.nav.nav-sidebar = nav_link(controller: :dashboard, html_options: {class: 'home'}) do = link_to admin_root_path, title: "Stats" do - Overview + %i.fa.fa-dashboard + %span + Overview = nav_link(controller: :projects) do - = link_to "Projects", admin_projects_path + = link_to admin_namespaces_projects_path, title: 'Projects' do + %i.fa.fa-cube + %span + Projects = nav_link(controller: :users) do - = link_to "Users", admin_users_path + = link_to admin_users_path, title: 'Users' do + %i.fa.fa-user + %span + Users = nav_link(controller: :groups) do - = link_to "Groups", admin_groups_path + = link_to admin_groups_path, title: 'Groups' do + %i.fa.fa-group + %span + Groups + = nav_link(controller: :deploy_keys) do + = link_to admin_deploy_keys_path, title: 'Deploy Keys' do + %i.fa.fa-key + %span + Deploy Keys = nav_link(controller: :logs) do - = link_to "Logs", admin_logs_path + = link_to admin_logs_path, title: 'Logs' do + %i.fa.fa-file-text + %span + Logs = nav_link(controller: :broadcast_messages) do - = link_to "Messages", admin_broadcast_messages_path + = link_to admin_broadcast_messages_path, title: 'Broadcast Messages' do + %i.fa.fa-bullhorn + %span + Messages = nav_link(controller: :hooks) do - = link_to "Hooks", admin_hooks_path + = link_to admin_hooks_path, title: 'Hooks' do + %i.fa.fa-external-link + %span + Hooks = nav_link(controller: :background_jobs) do - = link_to "Background Jobs", admin_background_jobs_path + = link_to admin_background_jobs_path, title: 'Background Jobs' do + %i.fa.fa-cog + %span + Background Jobs + + = nav_link(controller: :applications) do + = link_to admin_applications_path, title: 'Applications' do + %i.fa.fa-cloud + %span + Applications + + = nav_link(controller: :services) do + = link_to admin_application_settings_services_path, title: 'Service Templates' do + %i.fa.fa-copy + %span + Service Templates + + = nav_link(controller: :application_settings, html_options: { class: 'separate-item'}) do + = link_to admin_application_settings_path, title: 'Settings' do + %i.fa.fa-cogs + %span + Settings diff --git a/app/views/layouts/nav/_dashboard.html.haml b/app/views/layouts/nav/_dashboard.html.haml index a300bbc190..e4f630c6a1 100644 --- a/app/views/layouts/nav/_dashboard.html.haml +++ b/app/views/layouts/nav/_dashboard.html.haml @@ -1,18 +1,38 @@ -%ul +%ul.nav.nav-sidebar = nav_link(path: 'dashboard#show', html_options: {class: 'home'}) do - = link_to root_path, title: "Home" do - Activity - = nav_link(path: 'dashboard#projects') do - = link_to projects_dashboard_path do - Projects + = link_to root_path, title: 'Home', class: 'shortcuts-activity' do + %i.fa.fa-dashboard + %span + Your Projects + = nav_link(path: 'projects#starred') do + = link_to starred_dashboard_projects_path, title: 'Starred Projects' do + %i.fa.fa-star + %span + Starred Projects + = nav_link(controller: :groups) do + = link_to dashboard_groups_path, title: 'Groups' do + %i.fa.fa-group + %span + Groups + = nav_link(controller: :milestones) do + = link_to dashboard_milestones_path, title: 'Milestones' do + %i.fa.fa-clock-o + %span + Milestones = nav_link(path: 'dashboard#issues') do - = link_to issues_dashboard_path do - Issues - %span.count= current_user.assigned_issues.opened.count + = link_to assigned_issues_dashboard_path, title: 'Issues', class: 'shortcuts-issues' do + %i.fa.fa-exclamation-circle + %span + Issues + %span.count= current_user.assigned_issues.opened.count = nav_link(path: 'dashboard#merge_requests') do - = link_to merge_requests_dashboard_path do - Merge Requests - %span.count= current_user.assigned_merge_requests.opened.count + = link_to assigned_mrs_dashboard_path, title: 'Merge Requests', class: 'shortcuts-merge_requests' do + %i.fa.fa-tasks + %span + Merge Requests + %span.count= current_user.assigned_merge_requests.opened.count = nav_link(controller: :help) do - = link_to "Help", help_path - + = link_to help_path, title: 'Help' do + %i.fa.fa-question-circle + %span + Help diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml index 9095a843c9..f0d92b7a12 100644 --- a/app/views/layouts/nav/_group.html.haml +++ b/app/views/layouts/nav/_group.html.haml @@ -1,25 +1,42 @@ -%ul +%ul.nav.nav-sidebar = nav_link(path: 'groups#show', html_options: {class: 'home'}) do = link_to group_path(@group), title: "Home" do - Activity - = nav_link(controller: [:group, :milestones]) do - = link_to group_milestones_path(@group) do - Milestones + %i.fa.fa-dashboard + %span + Activity + - if current_user + = nav_link(controller: [:group, :milestones]) do + = link_to group_milestones_path(@group), title: 'Milestones' do + %i.fa.fa-clock-o + %span + Milestones = nav_link(path: 'groups#issues') do - = link_to issues_group_path(@group) do - Issues - - if current_user - %span.count= current_user.assigned_issues.opened.of_group(@group).count + = link_to issues_group_path(@group), title: 'Issues' do + %i.fa.fa-exclamation-circle + %span + Issues + - if current_user + %span.count= Issue.opened.of_group(@group).count = nav_link(path: 'groups#merge_requests') do - = link_to merge_requests_group_path(@group) do - Merge Requests - - if current_user - %span.count= current_user.cared_merge_requests.opened.of_group(@group).count - = nav_link(path: 'groups#members') do - = link_to "Members", members_group_path(@group) + = link_to merge_requests_group_path(@group), title: 'Merge Requests' do + %i.fa.fa-tasks + %span + Merge Requests + - if current_user + %span.count= MergeRequest.opened.of_group(@group).count + = nav_link(controller: [:group_members]) do + = link_to group_group_members_path(@group), title: 'Members' do + %i.fa.fa-users + %span + Members - - if can?(current_user, :manage_group, @group) - = nav_link(path: 'groups#edit') do - = link_to edit_group_path(@group), class: "tab " do - Settings + - if can?(current_user, :admin_group, @group) + = nav_link(html_options: { class: "#{"active" if group_settings_page?} separate-item" }) do + = link_to edit_group_path(@group), title: 'Settings', class: "tab no-highlight" do + %i.fa.fa-cogs + %span + Settings + %i.fa.fa-angle-down + - if group_settings_page? + = render 'groups/settings_nav' diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml index 1de5ee99cf..d88e862829 100644 --- a/app/views/layouts/nav/_profile.html.haml +++ b/app/views/layouts/nav/_profile.html.haml @@ -1,26 +1,50 @@ -%ul +%ul.nav.nav-sidebar = nav_link(path: 'profiles#show', html_options: {class: 'home'}) do = link_to profile_path, title: "Profile" do - Profile + %i.fa.fa-user + %span + Profile = nav_link(controller: :accounts) do - = link_to "Account", profile_account_path + = link_to profile_account_path, title: 'Account' do + %i.fa.fa-gear + %span + Account + = nav_link(path: ['profiles#applications', 'applications#edit', 'applications#show', 'applications#new']) do + = link_to applications_profile_path, title: 'Applications' do + %i.fa.fa-cloud + %span + Applications = nav_link(controller: :emails) do - = link_to profile_emails_path do - Emails - %span.count= current_user.emails.count + 1 + = link_to profile_emails_path, title: 'Emails' do + %i.fa.fa-envelope-o + %span + Emails + %span.count= current_user.emails.count + 1 - unless current_user.ldap_user? = nav_link(controller: :passwords) do - = link_to "Password", edit_profile_password_path + = link_to edit_profile_password_path, title: 'Password' do + %i.fa.fa-lock + %span + Password = nav_link(controller: :notifications) do - = link_to "Notifications", profile_notifications_path - = nav_link(controller: :keys) do - = link_to profile_keys_path do - SSH Keys - %span.count= current_user.keys.count - = nav_link(path: 'profiles#design') do - = link_to "Design", design_profile_path - = nav_link(controller: :groups) do - = link_to "Groups", profile_groups_path - = nav_link(path: 'profiles#history') do - = link_to "History", history_profile_path + = link_to profile_notifications_path, title: 'Notifications' do + %i.fa.fa-inbox + %span + Notifications + = nav_link(controller: :keys) do + = link_to profile_keys_path, title: 'SSH Keys' do + %i.fa.fa-key + %span + SSH Keys + %span.count= current_user.keys.count + = nav_link(path: 'profiles#design') do + = link_to design_profile_path, title: 'Design' do + %i.fa.fa-image + %span + Design + = nav_link(path: 'profiles#history') do + = link_to history_profile_path, title: 'History' do + %i.fa.fa-history + %span + History diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml index 69491c2529..6c13f30f62 100644 --- a/app/views/layouts/nav/_project.html.haml +++ b/app/views/layouts/nav/_project.html.haml @@ -1,46 +1,97 @@ -%ul - = nav_link(path: 'projects#show', html_options: {class: "home"}) do - = link_to project_path(@project), title: "Project" do - Activity +%ul.project-navigation.nav.nav-sidebar + - if @project_settings_nav + = nav_link do + = link_to project_path(@project), title: 'Back to project', class: "" do + %i.fa.fa-caret-square-o-left + %span + Back to project - - if project_nav_tab? :files - = nav_link(controller: %w(tree blob blame edit_tree new_tree)) do - = link_to 'Files', project_tree_path(@project, @ref || @repository.root_ref) + %li.separate-item - - if project_nav_tab? :commits - = nav_link(controller: %w(commit commits compare repositories tags branches)) do - = link_to "Commits", project_commits_path(@project, @ref || @repository.root_ref) + = render 'projects/settings_nav' - - if project_nav_tab? :network - = nav_link(controller: %w(network)) do - = link_to "Network", project_network_path(@project, @ref || @repository.root_ref) + - else + = nav_link(path: 'projects#show', html_options: {class: "home"}) do + = link_to project_path(@project), title: 'Project', class: 'shortcuts-project' do + %i.fa.fa-dashboard + %span + Project + - if project_nav_tab? :files + = nav_link(controller: %w(tree blob blame edit_tree new_tree)) do + = link_to namespace_project_tree_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Files', class: 'shortcuts-tree' do + %i.fa.fa-files-o + %span + Files - - if project_nav_tab? :graphs - = nav_link(controller: %w(graphs)) do - = link_to "Graphs", project_graph_path(@project, @ref || @repository.root_ref) + - if project_nav_tab? :commits + = nav_link(controller: %w(commit commits compare repositories tags branches)) do + = link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Commits', class: 'shortcuts-commits' do + %i.fa.fa-history + %span + Commits - - if project_nav_tab? :issues - = nav_link(controller: %w(issues milestones labels)) do - = link_to url_for_project_issues do - Issues - - if @project.used_default_issues_tracker? - %span.count.issue_counter= @project.issues.opened.count + - if project_nav_tab? :network + = nav_link(controller: %w(network)) do + = link_to namespace_project_network_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Network', class: 'shortcuts-network' do + %i.fa.fa-code-fork + %span + Network - - if project_nav_tab? :merge_requests - = nav_link(controller: :merge_requests) do - = link_to project_merge_requests_path(@project) do - Merge Requests - %span.count.merge_counter= @project.merge_requests.opened.count + - if project_nav_tab? :graphs + = nav_link(controller: %w(graphs)) do + = link_to namespace_project_graph_path(@project.namespace, @project, @ref || @repository.root_ref), title: 'Graphs', class: 'shortcuts-graphs' do + %i.fa.fa-area-chart + %span + Graphs - - if project_nav_tab? :wiki - = nav_link(controller: :wikis) do - = link_to 'Wiki', project_wiki_path(@project, :home) + - if project_nav_tab? :milestones + = nav_link(controller: :milestones) do + = link_to namespace_project_milestones_path(@project.namespace, @project), title: 'Milestones' do + %i.fa.fa-clock-o + %span + Milestones - - if project_nav_tab? :snippets - = nav_link(controller: :snippets) do - = link_to 'Snippets', project_snippets_path(@project) + - 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 + %i.fa.fa-exclamation-circle + %span + Issues + - if @project.default_issues_tracker? + %span.count.issue_counter= @project.issues.opened.count - - if project_nav_tab? :settings - = nav_link(html_options: {class: "#{project_tab_class}"}) do - = link_to edit_project_path(@project), class: "stat-tab tab " do - Settings + - 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 + %i.fa.fa-tasks + %span + Merge Requests + %span.count.merge_counter= @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 + %i.fa.fa-tags + %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 + %i.fa.fa-book + %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 + %i.fa.fa-file-text-o + %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', class: "stat-tab tab no-highlight" do + %i.fa.fa-cogs + %span + Settings diff --git a/app/views/layouts/navless.html.haml b/app/views/layouts/navless.html.haml index c43d688a2c..4d0278251a 100644 --- a/app/views/layouts/navless.html.haml +++ b/app/views/layouts/navless.html.haml @@ -1,11 +1,10 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: @title - %body{class: "#{app_theme} application", :'data-page' => body_data_page} + %body{class: "#{app_theme} application", :'data-page' => body_data_page} = render "layouts/broadcast" - = render "layouts/head_panel", title: @title - = render "layouts/flash" - + = render "layouts/head_panel", title: defined?(@title_url) ? link_to(@title, @title_url) : @title .container.navless-container .content + = render "layouts/flash" = yield diff --git a/app/views/layouts/notify.html.haml b/app/views/layouts/notify.html.haml index ab421d63f1..00c7cedce4 100644 --- a/app/views/layouts/notify.html.haml +++ b/app/views/layouts/notify.html.haml @@ -16,7 +16,18 @@ font-size:small; color:#777 } - + pre.commit-message { + white-space: pre-wrap; + } + .file-stats a { + text-decoration: none; + } + .file-stats .new-file { + color: #090; + } + .file-stats .deleted-file { + color: #B00; + }} %body %div.content = yield @@ -24,7 +35,8 @@ %p \— %br - - if @project - You're receiving this notification because you are a member of the #{link_to_unless @target_url, @project.name_with_namespace, project_url(@project)} project team. - if @target_url #{link_to "View it on GitLab", @target_url} + = email_action @target_url + - if @project && !@disable_footer + You're receiving this notification because you are a member of the #{link_to_unless @target_url, @project.name_with_namespace, namespace_project_url(@project.namespace, @project)} project team. diff --git a/app/views/layouts/profile.html.haml b/app/views/layouts/profile.html.haml index 2d869a6cdc..2b5be7fc37 100644 --- a/app/views/layouts/profile.html.haml +++ b/app/views/layouts/profile.html.haml @@ -1,12 +1,6 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: "Profile" - %body{class: "#{app_theme} profile", :'data-page' => body_data_page} - = render "layouts/broadcast" - = render "layouts/head_panel", title: "Profile" - = render "layouts/flash" - %nav.main-nav.navbar-collapse.collapse - .container= render 'layouts/nav/profile' - - .container - .content= yield + %body{class: "#{app_theme} profile", :'data-page' => body_data_page} + = render "layouts/head_panel", title: link_to("Profile", profile_path) + = render 'layouts/page', sidebar: 'layouts/nav/profile' diff --git a/app/views/layouts/project_settings.html.haml b/app/views/layouts/project_settings.html.haml index 5659cfab31..0a0039dec1 100644 --- a/app/views/layouts/project_settings.html.haml +++ b/app/views/layouts/project_settings.html.haml @@ -1,21 +1,8 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: @project.name_with_namespace - %body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id } - = render "layouts/broadcast" + %body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id } = render "layouts/head_panel", title: project_title(@project) = render "layouts/init_auto_complete" - = render "layouts/flash" - - if can?(current_user, :download_code, @project) - = render 'shared/no_ssh' - - %nav.main-nav.navbar-collapse.collapse - .container= render 'layouts/nav/project' - - .container - .content - .row - .col-md-2 - = render "projects/settings_nav" - .col-md-10 - = yield + - @project_settings_nav = true + = render 'layouts/page', sidebar: 'layouts/nav/project' diff --git a/app/views/layouts/projects.html.haml b/app/views/layouts/projects.html.haml index f02eca6bd7..dde0964f47 100644 --- a/app/views/layouts/projects.html.haml +++ b/app/views/layouts/projects.html.haml @@ -1,17 +1,7 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: project_head_title - %body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id } - = render "layouts/broadcast" + %body{class: "#{app_theme} project", :'data-page' => body_data_page, :'data-project-id' => @project.id } = render "layouts/head_panel", title: project_title(@project) = render "layouts/init_auto_complete" - = render "layouts/flash" - - if can?(current_user, :download_code, @project) - = render 'shared/no_ssh' - - %nav.main-nav.navbar-collapse.collapse - .container= render 'layouts/nav/project' - - .container - .content= yield - = yield :embedded_scripts + = render 'layouts/page', sidebar: 'layouts/nav/project' diff --git a/app/views/layouts/public_group.html.haml b/app/views/layouts/public_group.html.haml index a289b78472..b9b1d03e08 100644 --- a/app/views/layouts/public_group.html.haml +++ b/app/views/layouts/public_group.html.haml @@ -1,10 +1,6 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: group_head_title - %body{class: "#{app_theme} application", :'data-page' => body_data_page} - = render "layouts/broadcast" - = render "layouts/public_head_panel", title: "group: #{@group.name}" - %nav.main-nav.navbar-collapse.collapse - .container= render 'layouts/nav/group' - .container - .content= yield + %body{class: "#{app_theme} application", :'data-page' => body_data_page} + = render "layouts/public_head_panel", title: link_to(@group.name, group_path(@group)) + = render 'layouts/page', sidebar: 'layouts/nav/group' diff --git a/app/views/layouts/public_projects.html.haml b/app/views/layouts/public_projects.html.haml index 2a9230244f..04fa7c84e7 100644 --- a/app/views/layouts/public_projects.html.haml +++ b/app/views/layouts/public_projects.html.haml @@ -1,10 +1,6 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: @project.name_with_namespace - %body{class: "#{app_theme} application", :'data-page' => body_data_page} - = render "layouts/broadcast" + %body{class: "#{app_theme} application", :'data-page' => body_data_page} = render "layouts/public_head_panel", title: project_title(@project) - %nav.main-nav.navbar-collapse.collapse - .container= render 'layouts/nav/project' - .container - .content= yield + = render 'layouts/page', sidebar: 'layouts/nav/project' diff --git a/app/views/layouts/public_users.html.haml b/app/views/layouts/public_users.html.haml index 4aa258fea0..71c16bd168 100644 --- a/app/views/layouts/public_users.html.haml +++ b/app/views/layouts/public_users.html.haml @@ -1,8 +1,6 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: @title - %body{class: "#{app_theme} application", :'data-page' => body_data_page} - = render "layouts/broadcast" - = render "layouts/public_head_panel", title: @title - .container.navless-container - .content= yield + %body{class: "#{app_theme} application", :'data-page' => body_data_page} + = render "layouts/public_head_panel", title: defined?(@title_url) ? link_to(@title, @title_url) : @title + = render 'layouts/page' diff --git a/app/views/layouts/search.html.haml b/app/views/layouts/search.html.haml index 97ed8ba12d..f9d8db06e1 100644 --- a/app/views/layouts/search.html.haml +++ b/app/views/layouts/search.html.haml @@ -1,11 +1,10 @@ !!! 5 %html{ lang: "en"} = render "layouts/head", title: "Search" - %body{class: "#{app_theme} application", :'data-page' => body_data_page} + %body{class: "#{app_theme} application", :'data-page' => body_data_page} = render "layouts/broadcast" - = render "layouts/head_panel", title: "Search" - = render "layouts/flash" - + = render "layouts/head_panel", title: link_to("Search", search_path) .container.navless-container .content + = render "layouts/flash" = yield diff --git a/app/views/layouts/user_team.html.haml b/app/views/layouts/user_team.html.haml deleted file mode 100644 index ce13853ed7..0000000000 --- a/app/views/layouts/user_team.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -!!! 5 -%html{ lang: "en"} - = render "layouts/head", title: "#{@team.name}" - %body{class: "#{app_theme} application", :'data-page' => body_data_page} - = render "layouts/broadcast" - = render "layouts/head_panel", title: "team: #{@team.name}" - = render "layouts/flash" - %nav.main-nav.navbar-collapse.collapse - .container= render 'layouts/nav/team' - - .container - .content= yield diff --git a/app/views/notify/_note_message.html.haml b/app/views/notify/_note_message.html.haml index 5272dfa0ed..3fd4b04ac8 100644 --- a/app/views/notify/_note_message.html.haml +++ b/app/views/notify/_note_message.html.haml @@ -1,2 +1,2 @@ %div - = markdown(@note.note) + = markdown(@note.note, reference_only_path: false) diff --git a/app/views/notify/_reassigned_issuable_email.html.haml b/app/views/notify/_reassigned_issuable_email.html.haml new file mode 100644 index 0000000000..56d81b2ed2 --- /dev/null +++ b/app/views/notify/_reassigned_issuable_email.html.haml @@ -0,0 +1,10 @@ +%p + Assignee changed + - if @previous_assignee + from + %strong #{@previous_assignee.name} + to + - if issuable.assignee_id + %strong #{issuable.assignee_name} + - else + %strong Unassigned diff --git a/app/views/notify/_reassigned_issuable_email.text.erb b/app/views/notify/_reassigned_issuable_email.text.erb new file mode 100644 index 0000000000..855d37429d --- /dev/null +++ b/app/views/notify/_reassigned_issuable_email.text.erb @@ -0,0 +1,6 @@ +Reassigned <%= issuable.class.model_name.human.titleize %> <%= issuable.iid %> + +<%= url_for([issuable.project.namespace.becomes(Namespace), issuable.project, issuable, {only_path: false}]) %> + +Assignee changed <%= "from #{@previous_assignee.name}" if @previous_assignee -%> + to <%= "#{issuable.assignee_id ? issuable.assignee_name : 'Unassigned'}" %> diff --git a/app/views/notify/closed_issue_email.text.haml b/app/views/notify/closed_issue_email.text.haml index 49f160a0d5..ac703b31ed 100644 --- a/app/views/notify/closed_issue_email.text.haml +++ b/app/views/notify/closed_issue_email.text.haml @@ -1,3 +1,3 @@ = "Issue was closed by #{@updated_by.name}" -Issue ##{@issue.iid}: #{project_issue_url(@issue.project, @issue)} +Issue ##{@issue.iid}: #{namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)} diff --git a/app/views/notify/closed_merge_request_email.text.haml b/app/views/notify/closed_merge_request_email.text.haml index d6b76e906c..59db86b08b 100644 --- a/app/views/notify/closed_merge_request_email.text.haml +++ b/app/views/notify/closed_merge_request_email.text.haml @@ -1,6 +1,6 @@ = "Merge Request ##{@merge_request.iid} was closed by #{@updated_by.name}" -Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)} +Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)} = merge_path_description(@merge_request, 'to') diff --git a/app/views/notify/group_access_granted_email.html.haml b/app/views/notify/group_access_granted_email.html.haml index 823ebf7734..f1916d624b 100644 --- a/app/views/notify/group_access_granted_email.html.haml +++ b/app/views/notify/group_access_granted_email.html.haml @@ -1,4 +1,4 @@ %p - = "You have been granted #{@membership.human_access} access to group" + = "You have been granted #{@group_member.human_access} access to group" = link_to group_url(@group) do = @group.name diff --git a/app/views/notify/group_access_granted_email.text.erb b/app/views/notify/group_access_granted_email.text.erb index 331bb98d5c..ef9617bfc1 100644 --- a/app/views/notify/group_access_granted_email.text.erb +++ b/app/views/notify/group_access_granted_email.text.erb @@ -1,4 +1,4 @@ -You have been granted <%= @membership.human_access %> access to group <%= @group.name %> +You have been granted <%= @group_member.human_access %> access to group <%= @group.name %> <%= url_for(group_url(@group)) %> diff --git a/app/views/notify/group_invite_accepted_email.html.haml b/app/views/notify/group_invite_accepted_email.html.haml new file mode 100644 index 0000000000..55efad384a --- /dev/null +++ b/app/views/notify/group_invite_accepted_email.html.haml @@ -0,0 +1,6 @@ +%p + #{@group_member.invite_email}, now known as + #{link_to @group_member.user.name, user_url(@group_member.user)}, + has accepted your invitation to join group + #{link_to @group.name, group_url(@group)}. + diff --git a/app/views/notify/group_invite_accepted_email.text.erb b/app/views/notify/group_invite_accepted_email.text.erb new file mode 100644 index 0000000000..f8b70f7a5a --- /dev/null +++ b/app/views/notify/group_invite_accepted_email.text.erb @@ -0,0 +1,3 @@ +<%= @group_member.invite_email %>, now known as <%= @group_member.user.name %>, has accepted your invitation to join group <%= @group.name %>. + +<%= group_url(@group) %> diff --git a/app/views/notify/group_invite_declined_email.html.haml b/app/views/notify/group_invite_declined_email.html.haml new file mode 100644 index 0000000000..f9525d84fa --- /dev/null +++ b/app/views/notify/group_invite_declined_email.html.haml @@ -0,0 +1,5 @@ +%p + #{@invite_email} + has declined your invitation to join group + #{link_to @group.name, group_url(@group)}. + diff --git a/app/views/notify/group_invite_declined_email.text.erb b/app/views/notify/group_invite_declined_email.text.erb new file mode 100644 index 0000000000..6c19a288d1 --- /dev/null +++ b/app/views/notify/group_invite_declined_email.text.erb @@ -0,0 +1,3 @@ +<%= @invite_email %> has declined your invitation to join group <%= @group.name %>. + +<%= group_url(@group) %> diff --git a/app/views/notify/group_member_invited_email.html.haml b/app/views/notify/group_member_invited_email.html.haml new file mode 100644 index 0000000000..163e88bfea --- /dev/null +++ b/app/views/notify/group_member_invited_email.html.haml @@ -0,0 +1,14 @@ +%p + You have been invited + - if inviter = @group_member.created_by + by + = link_to inviter.name, user_url(inviter) + to join group + = link_to @group.name, group_url(@group) + as #{@group_member.human_access}. + +%p + = link_to 'Accept invitation', invite_url(@token) + or + = link_to 'decline', decline_invite_url(@token) + diff --git a/app/views/notify/group_member_invited_email.text.erb b/app/views/notify/group_member_invited_email.text.erb new file mode 100644 index 0000000000..28ce4819b1 --- /dev/null +++ b/app/views/notify/group_member_invited_email.text.erb @@ -0,0 +1,4 @@ +You have been invited <%= "by #{@group_member.created_by.name} " if @group_member.created_by %>to join group <%= @group.name %> as <%= @group_member.human_access %>. + +Accept invitation: <%= invite_url(@token) %> +Decline invitation: <%= decline_invite_url(@token) %> diff --git a/app/views/notify/issue_status_changed_email.text.erb b/app/views/notify/issue_status_changed_email.text.erb index 4200881f7e..e6ab3fcde7 100644 --- a/app/views/notify/issue_status_changed_email.text.erb +++ b/app/views/notify/issue_status_changed_email.text.erb @@ -1,4 +1,4 @@ Issue was <%= @issue_status %> by <%= @updated_by.name %> -Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %> +Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %> diff --git a/app/views/notify/merge_request_status_email.text.haml b/app/views/notify/merge_request_status_email.text.haml index 8750bf86e2..b96dd0fd8a 100644 --- a/app/views/notify/merge_request_status_email.text.haml +++ b/app/views/notify/merge_request_status_email.text.haml @@ -1,6 +1,6 @@ = "Merge Request ##{@merge_request.iid} was #{@mr_status} by #{@updated_by.name}" -Merge Request url: #{project_merge_request_url(@merge_request.target_project, @merge_request)} +Merge Request url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)} = merge_path_description(@merge_request, 'to') diff --git a/app/views/notify/merged_merge_request_email.text.haml b/app/views/notify/merged_merge_request_email.text.haml index 360da60bc3..9db75bdb19 100644 --- a/app/views/notify/merged_merge_request_email.text.haml +++ b/app/views/notify/merged_merge_request_email.text.haml @@ -1,6 +1,6 @@ = "Merge Request ##{@merge_request.iid} was merged" -Merge Request Url: #{project_merge_request_url(@merge_request.target_project, @merge_request)} +Merge Request Url: #{namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)} = merge_path_description(@merge_request, 'to') diff --git a/app/views/notify/new_issue_email.html.haml b/app/views/notify/new_issue_email.html.haml index f2f8eee18c..53a068be52 100644 --- a/app/views/notify/new_issue_email.html.haml +++ b/app/views/notify/new_issue_email.html.haml @@ -1,5 +1,5 @@ -if @issue.description - = markdown(@issue.description) + = markdown(@issue.description, reference_only_path: false) - if @issue.assignee_id.present? %p diff --git a/app/views/notify/new_issue_email.text.erb b/app/views/notify/new_issue_email.text.erb index d36f54eb1c..0cc6293549 100644 --- a/app/views/notify/new_issue_email.text.erb +++ b/app/views/notify/new_issue_email.text.erb @@ -1,5 +1,5 @@ New Issue was created. -Issue <%= @issue.iid %>: <%= url_for(project_issue_url(@issue.project, @issue)) %> +Issue <%= @issue.iid %>: <%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue)) %> Author: <%= @issue.author_name %> Asignee: <%= @issue.assignee_name %> diff --git a/app/views/notify/new_merge_request_email.html.haml b/app/views/notify/new_merge_request_email.html.haml index f02d5111b2..5b7dd117c1 100644 --- a/app/views/notify/new_merge_request_email.html.haml +++ b/app/views/notify/new_merge_request_email.html.haml @@ -6,4 +6,4 @@ Assignee: #{@merge_request.author_name} → #{@merge_request.assignee_name} -if @merge_request.description - = markdown(@merge_request.description) + = markdown(@merge_request.description, reference_only_path: false) diff --git a/app/views/notify/new_merge_request_email.text.erb b/app/views/notify/new_merge_request_email.text.erb index 16be4bb619..f08039ad04 100644 --- a/app/views/notify/new_merge_request_email.text.erb +++ b/app/views/notify/new_merge_request_email.text.erb @@ -1,6 +1,6 @@ New Merge Request #<%= @merge_request.iid %> -<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %> +<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request)) %> <%= merge_path_description(@merge_request, 'to') %> Author: <%= @merge_request.author_name %> diff --git a/app/views/notify/new_ssh_key_email.html.haml b/app/views/notify/new_ssh_key_email.html.haml index deb0822d8f..63b0cbbd20 100644 --- a/app/views/notify/new_ssh_key_email.html.haml +++ b/app/views/notify/new_ssh_key_email.html.haml @@ -6,5 +6,5 @@ title: %code= @key.title %p - If this key was added in error, you can remove it here: + If this key was added in error, you can remove it under = link_to "SSH Keys", profile_keys_url diff --git a/app/views/notify/new_ssh_key_email.text.erb b/app/views/notify/new_ssh_key_email.text.erb index 5f0080c2b7..05b551c89a 100644 --- a/app/views/notify/new_ssh_key_email.text.erb +++ b/app/views/notify/new_ssh_key_email.text.erb @@ -2,6 +2,6 @@ Hi <%= @user.name %>! A new public key was added to your account: -title.................. <%= @key.title %> +Title: <%= @key.title %> -If this key was added in error, you can remove it here: <%= profile_keys_url %> +If this key was added in error, you can remove it at <%= profile_keys_url %> diff --git a/app/views/notify/note_commit_email.text.erb b/app/views/notify/note_commit_email.text.erb index aab8e5cfb6..aaeaf5fdf7 100644 --- a/app/views/notify/note_commit_email.text.erb +++ b/app/views/notify/note_commit_email.text.erb @@ -1,6 +1,6 @@ New comment for Commit <%= @commit.short_id %> -<%= url_for(project_commit_url(@note.project, id: @commit.id, anchor: "note_#{@note.id}")) %> +<%= url_for(namespace_project_commit_url(@note.project.namespace, @note.project, id: @commit.id, anchor: "note_#{@note.id}")) %> Author: <%= @note.author_name %> diff --git a/app/views/notify/note_issue_email.text.erb b/app/views/notify/note_issue_email.text.erb index 8a61f54a33..e33cbcd70f 100644 --- a/app/views/notify/note_issue_email.text.erb +++ b/app/views/notify/note_issue_email.text.erb @@ -1,6 +1,6 @@ New comment for Issue <%= @issue.iid %> -<%= url_for(project_issue_url(@issue.project, @issue, anchor: "note_#{@note.id}")) %> +<%= url_for(namespace_project_issue_url(@issue.project.namespace, @issue.project, @issue, anchor: "note_#{@note.id}")) %> Author: <%= @note.author_name %> diff --git a/app/views/notify/note_merge_request_email.text.erb b/app/views/notify/note_merge_request_email.text.erb index 79e72ca16c..1d1411992a 100644 --- a/app/views/notify/note_merge_request_email.text.erb +++ b/app/views/notify/note_merge_request_email.text.erb @@ -1,6 +1,6 @@ New comment for Merge Request <%= @merge_request.iid %> -<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")) %> +<%= url_for(namespace_project_merge_request_url(@merge_request.target_project.namespace, @merge_request.target_project, @merge_request, anchor: "note_#{@note.id}")) %> <%= @note.author_name %> diff --git a/app/views/notify/project_access_granted_email.html.haml b/app/views/notify/project_access_granted_email.html.haml index ce34f82535..dfc30a2d36 100644 --- a/app/views/notify/project_access_granted_email.html.haml +++ b/app/views/notify/project_access_granted_email.html.haml @@ -1,5 +1,5 @@ %p - = "You have been granted #{@users_project.human_access} access to project" + = "You have been granted #{@project_member.human_access} access to project" %p - = link_to project_url(@project) do + = link_to namespace_project_url(@project.namespace, @project) do = @project.name_with_namespace diff --git a/app/views/notify/project_access_granted_email.text.erb b/app/views/notify/project_access_granted_email.text.erb index 66c57def37..68eb1611ba 100644 --- a/app/views/notify/project_access_granted_email.text.erb +++ b/app/views/notify/project_access_granted_email.text.erb @@ -1,4 +1,4 @@ -You have been granted <%= @users_project.human_access %> access to project <%= @project.name_with_namespace %> +You have been granted <%= @project_member.human_access %> access to project <%= @project.name_with_namespace %> -<%= url_for(project_url(@project)) %> +<%= url_for(namespace_project_url(@project.namespace, @project)) %> diff --git a/app/views/notify/project_invite_accepted_email.html.haml b/app/views/notify/project_invite_accepted_email.html.haml new file mode 100644 index 0000000000..7e58d30b10 --- /dev/null +++ b/app/views/notify/project_invite_accepted_email.html.haml @@ -0,0 +1,6 @@ +%p + #{@project_member.invite_email}, now known as + #{link_to @project_member.user.name, user_url(@project_member.user)}, + has accepted your invitation to join project + #{link_to @project.name_with_namespace, namespace_project_url(@project.namespace, @project)}. + diff --git a/app/views/notify/project_invite_accepted_email.text.erb b/app/views/notify/project_invite_accepted_email.text.erb new file mode 100644 index 0000000000..fcbe752114 --- /dev/null +++ b/app/views/notify/project_invite_accepted_email.text.erb @@ -0,0 +1,3 @@ +<%= @project_member.invite_email %>, now known as <%= @project_member.user.name %>, has accepted your invitation to join project <%= @project.name_with_namespace %>. + +<%= namespace_project_url(@project.namespace, @project) %> diff --git a/app/views/notify/project_invite_declined_email.html.haml b/app/views/notify/project_invite_declined_email.html.haml new file mode 100644 index 0000000000..c2d7e6f6e3 --- /dev/null +++ b/app/views/notify/project_invite_declined_email.html.haml @@ -0,0 +1,5 @@ +%p + #{@invite_email} + has declined your invitation to join project + #{link_to @project.name_with_namespace, namespace_project_url(@project.namespace, @project)}. + diff --git a/app/views/notify/project_invite_declined_email.text.erb b/app/views/notify/project_invite_declined_email.text.erb new file mode 100644 index 0000000000..484687fa51 --- /dev/null +++ b/app/views/notify/project_invite_declined_email.text.erb @@ -0,0 +1,3 @@ +<%= @invite_email %> has declined your invitation to join project <%= @project.name_with_namespace %>. + +<%= namespace_project_url(@project.namespace, @project) %> diff --git a/app/views/notify/project_member_invited_email.html.haml b/app/views/notify/project_member_invited_email.html.haml new file mode 100644 index 0000000000..79eb89616d --- /dev/null +++ b/app/views/notify/project_member_invited_email.html.haml @@ -0,0 +1,13 @@ +%p + You have been invited + - if inviter = @project_member.created_by + by + = link_to inviter.name, user_url(inviter) + to join project + = link_to @project.name_with_namespace, namespace_project_url(@project.namespace, @project) + as #{@project_member.human_access}. + +%p + = link_to 'Accept invitation', invite_url(@token) + or + = link_to 'decline', decline_invite_url(@token) diff --git a/app/views/notify/project_member_invited_email.text.erb b/app/views/notify/project_member_invited_email.text.erb new file mode 100644 index 0000000000..e070627211 --- /dev/null +++ b/app/views/notify/project_member_invited_email.text.erb @@ -0,0 +1,4 @@ +You have been invited <%= "by #{@project_member.created_by.name} " if @project_member.created_by %>to join project <%= @project.name_with_namespace %> as <%= @project_member.human_access %>. + +Accept invitation: <%= invite_url(@token) %> +Decline invitation: <%= decline_invite_url(@token) %> diff --git a/app/views/notify/project_was_moved_email.html.haml b/app/views/notify/project_was_moved_email.html.haml index 1667c59bc0..3cd759f1f5 100644 --- a/app/views/notify/project_was_moved_email.html.haml +++ b/app/views/notify/project_was_moved_email.html.haml @@ -1,15 +1,15 @@ %p - = "Project was moved to another location" + Project was moved to another location %p The project is now located under - = link_to project_url(@project) do + = link_to namespace_project_url(@project.namespace, @project) do = @project.name_with_namespace %p To update the remote url in your local repository run (for ssh): -%p{ style: "background:#f5f5f5; padding:10px; border:1px solid #ddd" } +%p{ style: "background: #f5f5f5; padding:10px; border:1px solid #ddd" } git remote set-url origin #{@project.ssh_url_to_repo} %p or for http(s): -%p{ style: "background:#f5f5f5; padding:10px; border:1px solid #ddd" } +%p{ style: "background: #f5f5f5; padding:10px; border:1px solid #ddd" } git remote set-url origin #{@project.http_url_to_repo} %br diff --git a/app/views/notify/project_was_moved_email.text.erb b/app/views/notify/project_was_moved_email.text.erb index 664148fb3b..b3f18b35a4 100644 --- a/app/views/notify/project_was_moved_email.text.erb +++ b/app/views/notify/project_was_moved_email.text.erb @@ -1,7 +1,7 @@ Project was moved to another location The project is now located under -<%= project_url(@project) %> +<%= namespace_project_url(@project.namespace, @project) %> To update the remote url in your local repository run (for ssh): diff --git a/app/views/notify/reassigned_issue_email.html.haml b/app/views/notify/reassigned_issue_email.html.haml index f1458df5c7..498ba8b836 100644 --- a/app/views/notify/reassigned_issue_email.html.haml +++ b/app/views/notify/reassigned_issue_email.html.haml @@ -1,11 +1 @@ -%p - Assignee changed - - if @previous_assignee - from - %strong #{@previous_assignee.name} - to - - if @issue.assignee_id - %strong #{@issue.assignee_name} - - else - %strong Unassigned - += render 'reassigned_issuable_email', issuable: @issue diff --git a/app/views/notify/reassigned_issue_email.text.erb b/app/views/notify/reassigned_issue_email.text.erb index 4becac2749..710253be98 100644 --- a/app/views/notify/reassigned_issue_email.text.erb +++ b/app/views/notify/reassigned_issue_email.text.erb @@ -1,5 +1 @@ -Reassigned Issue <%= @issue.iid %> - -<%= url_for(project_issue_url(@issue.project, @issue)) %> - -Assignee changed <%= "from #{@previous_assignee.name}" if @previous_assignee %> to <%= "#{@issue.assignee_id ? @issue.assignee_name : 'Unassigned'}" %> +<%= render 'reassigned_issuable_email', issuable: @issue %> diff --git a/app/views/notify/reassigned_merge_request_email.html.haml b/app/views/notify/reassigned_merge_request_email.html.haml index 00aee6bc95..2a650130f5 100644 --- a/app/views/notify/reassigned_merge_request_email.html.haml +++ b/app/views/notify/reassigned_merge_request_email.html.haml @@ -1,7 +1 @@ -%p - Assignee changed - - if @previous_assignee - from - %strong #{@previous_assignee.name} - to - %strong #{@merge_request.assignee_name} += render 'reassigned_issuable_email', issuable: @merge_request diff --git a/app/views/notify/reassigned_merge_request_email.text.erb b/app/views/notify/reassigned_merge_request_email.text.erb index 87a7847e06..b5b4f1ff99 100644 --- a/app/views/notify/reassigned_merge_request_email.text.erb +++ b/app/views/notify/reassigned_merge_request_email.text.erb @@ -1,7 +1 @@ -Reassigned Merge Request #<%= @merge_request.iid %> - -<%= url_for(project_merge_request_url(@merge_request.target_project, @merge_request)) %> - - -Assignee changed <%= "from #{@previous_assignee.name}" if @previous_assignee %> to <%= @merge_request.assignee_name %> - +<%= render 'reassigned_issuable_email', issuable: @merge_request %> diff --git a/app/views/notify/repository_push_email.html.haml b/app/views/notify/repository_push_email.html.haml index 0358810afd..a374a66233 100644 --- a/app/views/notify/repository_push_email.html.haml +++ b/app/views/notify/repository_push_email.html.haml @@ -1,28 +1,66 @@ -%h3 #{@author.name} pushed to #{@branch} at #{@project.name_with_namespace} +%h3 #{@author.name} #{@action_name} #{@ref_type} #{@ref_name} at #{link_to @project.name_with_namespace, namespace_project_url(@project.namespace, @project)} -%h4 Commits: +- if @compare + - if @reverse_compare + %p + %strong WARNING: + The push did not contain any new commits, but force pushed to delete the commits and changes below. -%ul - - @commits.each do |commit| - %li - %strong #{commit.short_id} - %span by #{commit.author_name} - %pre #{commit.safe_message} + %h4 + = @reverse_compare ? "Deleted commits:" : "Commits:" -%h4 Changes: -- @diffs.each do |diff| - %li - %strong - - if diff.old_path == diff.new_path - = diff.new_path - - elsif diff.new_path && diff.old_path - #{diff.old_path} → #{diff.new_path} - - else - = diff.new_path || diff.old_path - %hr - %pre - = diff.diff - %br + %ul + - @commits.each do |commit| + %li + %strong #{link_to commit.short_id, namespace_project_commit_url(@project.namespace, @project, commit)} + %div + %span by #{commit.author_name} + %i at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")} + %pre.commit-message + = commit.safe_message -- if @compare.timeout - %h5 Huge diff. To prevent performance issues changes are hidden + %h4 #{pluralize @diffs.count, "changed file"}: + + %ul + - @diffs.each_with_index do |diff, i| + %li.file-stats + %a{href: "#{@target_url if @disable_diffs}#diff-#{i}" } + - if diff.deleted_file + %span.deleted-file + − + = diff.old_path + - elsif diff.renamed_file + = diff.old_path + → + = diff.new_path + - elsif diff.new_file + %span.new-file + + + = diff.new_path + - else + = diff.new_path + + - unless @disable_diffs + %h4 Changes: + - @diffs.each_with_index do |diff, i| + %li{id: "diff-#{i}"} + %a{href: @target_url + "#diff-#{i}"} + - if diff.deleted_file + %strong + = diff.old_path + deleted + - elsif diff.renamed_file + %strong + = diff.old_path + → + %strong + = diff.new_path + - else + %strong + = diff.new_path + %hr + = color_email_diff(diff.diff) + %br + + - if @compare.timeout + %h5 Huge diff. To prevent performance issues changes are hidden diff --git a/app/views/notify/repository_push_email.text.haml b/app/views/notify/repository_push_email.text.haml index 4d7c972a16..97a176ed2a 100644 --- a/app/views/notify/repository_push_email.text.haml +++ b/app/views/notify/repository_push_email.text.haml @@ -1,25 +1,49 @@ -#{@author.name} pushed to #{@branch} at #{@project.name_with_namespace} - -\ -Commits: -- @commits.each do |commit| - #{commit.short_id} by #{commit.author_name} - #{commit.safe_message} - \- - - - - -\ -\ -Changes: -- @diffs.each do |diff| +#{@author.name} #{@action_name} #{@ref_type} #{@ref_name} at #{@project.name_with_namespace} +- if @compare \ - \===================================== - - if diff.old_path == diff.new_path - = diff.new_path - - elsif diff.new_path && diff.old_path - #{diff.old_path} → #{diff.new_path} - - else - = diff.new_path || diff.old_path - \===================================== - != diff.diff -\ -- if @compare.timeout - Huge diff. To prevent performance issues it was hidden + \ + - if @reverse_compare + WARNING: The push did not contain any new commits, but force pushed to delete the commits and changes below. + \ + \ + = @reverse_compare ? "Deleted commits:" : "Commits:" + - @commits.each do |commit| + #{commit.short_id} by #{commit.author_name} at #{commit.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ")} + #{commit.safe_message} + \- - - - - + \ + \ + #{pluralize @diffs.count, "changed file"}: + \ + - @diffs.each do |diff| + - if diff.deleted_file + \- − #{diff.old_path} + - elsif diff.renamed_file + \- #{diff.old_path} → #{diff.new_path} + - elsif diff.new_file + \- + #{diff.new_path} + - else + \- #{diff.new_path} + - unless @disable_diffs + \ + \ + Changes: + - @diffs.each do |diff| + \ + \===================================== + - 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 @compare.timeout + \ + \ + Huge diff. To prevent performance issues it was hidden + - if @target_url + \ + \ + View it on GitLab: #{@target_url} diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml index c60e1ca929..5bffb4acc1 100644 --- a/app/views/profiles/accounts/show.html.haml +++ b/app/views/profiles/accounts/show.html.haml @@ -1,16 +1,11 @@ -%h3.page-title - Account settings -%p.light - You can change your username and private token here. - - if current_user.ldap_user? +- if current_user.ldap_user? + .alert.alert-info Some options are unavailable for LDAP accounts -%hr - .account-page %fieldset.update-token %legend - Private token + Reset Private token %div = form_for @user, url: reset_private_token_profile_path, method: :put do |f| .data @@ -25,7 +20,7 @@ - if current_user.private_token = text_field_tag "token", current_user.private_token, class: "form-control" %div - = f.submit 'Reset', data: { confirm: "Are you sure?" }, class: "btn btn-primary btn-build-token" + = f.submit 'Reset private token', data: { confirm: "Are you sure?" }, class: "btn btn-primary btn-build-token" - else %span You don`t have one yet. Click generate to fix it. = f.submit 'Generate', class: "btn success btn-build-token" @@ -33,17 +28,21 @@ - if show_profile_social_tab? %fieldset - %legend Social Accounts - .oauth_select_holder.append-bottom-10 + %legend Connected Accounts + .oauth-buttons.append-bottom-10 %p Click on icon to activate signin with one of the following services - enabled_social_providers.each do |provider| - %span{class: oauth_active_class(provider) } - = link_to authbutton(provider, 32), omniauth_authorize_path(User, provider) + .btn-group + = link_to oauth_image_tag(provider), omniauth_authorize_path(User, provider), + class: "btn btn-lg #{'active' if oauth_active?(provider)}" + - if oauth_active?(provider) + = link_to unlink_profile_account_path(provider: provider), method: :delete, class: 'btn btn-lg' do + %i.fa.fa-close - if show_profile_username_tab? %fieldset.update-username %legend - Username + Change Username = form_for @user, url: update_username_profile_path, method: :put, remote: true do |f| %p Changing your username will change path to all personal projects! @@ -52,12 +51,12 @@   .loading-gif.hide %p - %i.icon-spinner.icon-spin + %i.fa.fa-spinner.fa-spin Saving new username %p.light = user_url(@user) %div - = f.submit 'Save username', class: "btn btn-save" + = f.submit 'Save username', class: "btn btn-warning" - if show_profile_remove_tab? %fieldset.remove-account @@ -75,3 +74,4 @@ The following groups will be abandoned. You should transfer or remove them: %strong #{current_user.solo_owned_groups.map(&:name).join(', ')} = link_to 'Delete account', user_registration_path, data: { confirm: "REMOVE #{current_user.name}? Are you sure?" }, method: :delete, class: "btn btn-remove" + diff --git a/app/views/profiles/applications.html.haml b/app/views/profiles/applications.html.haml new file mode 100644 index 0000000000..97e98948f3 --- /dev/null +++ b/app/views/profiles/applications.html.haml @@ -0,0 +1,49 @@ +%h3.page-title + Application Settings +%p.light + OAuth2 protocol settings below. + +%fieldset.oauth-applications + %legend Your applications + %p= link_to 'New Application', new_oauth_application_path, class: 'btn btn-success' + - if @applications.any? + %table.table.table-striped + %thead + %tr + %th Name + %th Callback URL + %th Clients + %th + %th + %tbody + - @applications.each do |application| + %tr{:id => "application_#{application.id}"} + %td= link_to application.name, oauth_application_path(application) + %td + - application.redirect_uri.split.each do |uri| + %div= uri + %td= application.access_tokens.count + %td= link_to 'Edit', edit_oauth_application_path(application), class: 'btn btn-link btn-sm' + %td= render 'doorkeeper/applications/delete_form', application: application + +%fieldset.oauth-authorized-applications.prepend-top-20 + %legend Authorized applications + + - if @authorized_tokens.any? + %table.table.table-striped + %thead + %tr + %th Name + %th Authorized At + %th Scope + %th + %tbody + - @authorized_apps.each do |app| + - token = app.authorized_tokens.order('created_at desc').first + %tr{:id => "application_#{app.id}"} + %td= app.name + %td= token.created_at + %td= token.scopes + %td= render 'doorkeeper/authorized_applications/delete_form', application: app + - else + %p.light You dont have any authorized applications diff --git a/app/views/profiles/design.html.haml b/app/views/profiles/design.html.haml index 0d8075b7d4..cc00d08d03 100644 --- a/app/views/profiles/design.html.haml +++ b/app/views/profiles/design.html.haml @@ -1,7 +1,7 @@ %h3.page-title - My appearance settings + Design Settings %p.light - Appearance settings saved to your profile and available across all devices + Appearance settings will be saved to your profile and made available across all devices. %hr = form_for @user, url: profile_path, remote: true, method: :put do |f| @@ -33,6 +33,11 @@ .prev.violet = f.radio_button :theme_id, 5 Violet + + = label_tag do + .prev.blue + = f.radio_button :theme_id, 6 + Blue %br .clearfix diff --git a/app/views/profiles/emails/index.html.haml b/app/views/profiles/emails/index.html.haml index ca980db2f3..09f290429e 100644 --- a/app/views/profiles/emails/index.html.haml +++ b/app/views/profiles/emails/index.html.haml @@ -1,9 +1,13 @@ %h3.page-title - My email addresses + Email Settings %p.light Your %b Primary Email - will be used for account notifications, avatar detection and web based operations, such as edits and merges. + will be used for avatar detection and web based operations, such as edits and merges. + %br + Your + %b Notification Email + will be used for account notifications. %br All email addresses will be used to identify your commits. @@ -16,12 +20,16 @@ %li %strong= @primary %span.label.label-success Primary Email + - if @primary === @public_email + %span.label.label-info Public Email - @emails.each do |email| %li %strong= email.email + - if email.email === @public_email + %span.label.label-info Public Email %span.cgray added #{time_ago_with_tooltip(email.created_at)} - = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-small btn-remove pull-right' + = link_to 'Remove', profile_email_path(email), data: { confirm: 'Are you sure?'}, method: :delete, class: 'btn btn-sm btn-remove pull-right' %h4 Add email address = form_for 'email', url: profile_emails_path, html: { class: 'form-horizontal' } do |f| @@ -30,4 +38,4 @@ .col-sm-10 = f.text_field :email, class: 'form-control' .form-actions - = f.submit 'Add', class: 'btn btn-create' + = f.submit 'Add email address', class: 'btn btn-create' diff --git a/app/views/profiles/groups/index.html.haml b/app/views/profiles/groups/index.html.haml deleted file mode 100644 index 2efe72b1bb..0000000000 --- a/app/views/profiles/groups/index.html.haml +++ /dev/null @@ -1,39 +0,0 @@ -%h3.page-title - Group membership - - if current_user.can_create_group? - %span.pull-right - = link_to new_group_path, class: "btn btn-new" do - %i.icon-plus - New Group -%p.light - Group members have access to all a group's projects -%hr -.panel.panel-default - .panel-heading - %strong Groups - (#{@user_groups.count}) - %ul.well-list - - @user_groups.each do |user_group| - - group = user_group.group - %li - .pull-right - - if can?(current_user, :manage_group, group) - = link_to edit_group_path(group), class: "btn-small btn btn-grouped" do - %i.icon-cogs - Settings - - - if can?(current_user, :destroy, user_group) - = link_to leave_profile_group_path(group), data: { confirm: leave_group_message(group.name) }, method: :delete, class: "btn-small btn btn-grouped", title: 'Remove user from group' do - %i.icon-signout - Leave - - = link_to group, class: 'group-name' do - %strong= group.name - - as - %strong #{user_group.human_access} - - %div.light - #{pluralize(group.projects.count, "project")}, #{pluralize(group.users.count, "user")} - -= paginate @user_groups diff --git a/app/views/profiles/history.html.haml b/app/views/profiles/history.html.haml index 3951c47b5f..b1ab433f48 100644 --- a/app/views/profiles/history.html.haml +++ b/app/views/profiles/history.html.haml @@ -1,7 +1,7 @@ %h3.page-title - Account history + Your Account History %p.light - All events created by your account are listed here + All events created by your account are listed below. %hr .profile_history = render @events diff --git a/app/views/profiles/keys/_key.html.haml b/app/views/profiles/keys/_key.html.haml index 81411a7565..fe5770f45c 100644 --- a/app/views/profiles/keys/_key.html.haml +++ b/app/views/profiles/keys/_key.html.haml @@ -1,9 +1,12 @@ -%li - = link_to profile_key_path(key) do - %strong= key.title - %span - (#{key.fingerprint}) - %span.cgray - added #{time_ago_with_tooltip(key.created_at)} - - = link_to 'Remove', profile_key_path(key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-small btn-remove delete-key pull-right" +%tr + %td + = link_to path_to_key(key, is_admin) do + %strong= key.title + %td + %span + (#{key.fingerprint}) + %td + %span.cgray + added #{time_ago_with_tooltip(key.created_at)} + %td + = link_to 'Remove', path_to_key(key, is_admin), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-sm btn-remove delete-key pull-right" diff --git a/app/views/profiles/keys/_key_details.html.haml b/app/views/profiles/keys/_key_details.html.haml new file mode 100644 index 0000000000..8bac22a2e1 --- /dev/null +++ b/app/views/profiles/keys/_key_details.html.haml @@ -0,0 +1,22 @@ +- is_admin = defined?(admin) ? true : false +.row + .col-md-4 + .panel.panel-default + .panel-heading + SSH Key + %ul.well-list + %li + %span.light Title: + %strong= @key.title + %li + %span.light Created on: + %strong= @key.created_at.stamp("Aug 21, 2011") + + .col-md-8 + %p + %span.light Fingerprint: + %strong= @key.fingerprint + %pre.well-pre + = @key.key + .pull-right + = link_to 'Remove', path_to_key(@key, is_admin), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key" diff --git a/app/views/profiles/keys/_key_table.html.haml b/app/views/profiles/keys/_key_table.html.haml new file mode 100644 index 0000000000..ef0075aad3 --- /dev/null +++ b/app/views/profiles/keys/_key_table.html.haml @@ -0,0 +1,19 @@ +- is_admin = defined?(admin) ? true : false +.panel.panel-default + - if @keys.any? + %table.table + %thead.panel-heading + %tr + %th Title + %th Fingerprint + %th Added at + %th + %tbody + - @keys.each do |key| + = render 'profiles/keys/key', key: key, is_admin: is_admin + - else + .nothing-here-block + - if is_admin + User has no ssh keys + - else + There are no SSH keys with access to your account. diff --git a/app/views/profiles/keys/index.html.haml b/app/views/profiles/keys/index.html.haml index aabfd57c28..0904c50c88 100644 --- a/app/views/profiles/keys/index.html.haml +++ b/app/views/profiles/keys/index.html.haml @@ -1,22 +1,10 @@ %h3.page-title - My SSH keys + SSH Keys Settings .pull-right = link_to "Add SSH Key", new_profile_key_path, class: "btn btn-new" %p.light - SSH keys allow you to establish a secure connection between your computer and GitLab - %br Before you can add an SSH key you need to - = link_to "generate it", help_page_path("ssh", "README") + = link_to "generate it.", help_page_path("ssh", "README") %hr - -.panel.panel-default - .panel-heading - SSH Keys (#{@keys.count}) - %ul.well-list#keys-table - = render @keys - - if @keys.blank? - %li - .nothing-here-block There are no SSH keys with access to your account. - - += render 'key_table' diff --git a/app/views/profiles/keys/new.html.haml b/app/views/profiles/keys/new.html.haml index 46b7fc7286..ccec716d0c 100644 --- a/app/views/profiles/keys/new.html.haml +++ b/app/views/profiles/keys/new.html.haml @@ -8,9 +8,9 @@ $('#key_key').on('focusout', function(){ var title = $('#key_title'), val = $('#key_key').val(), - key_mail = val.match(/([a-zA-Z0-9._-]+@[a-zA-Z0-9._-]+|\.[a-zA-Z0-9._-]+)/gi); + comment = val.match(/^\S+ \S+ (.+)$/); - if( key_mail && key_mail.length > 0 && title.val() == '' ){ - $('#key_title').val( key_mail ); + if( comment && comment.length > 1 && title.val() == '' ){ + $('#key_title').val( comment[1] ); } - }); \ No newline at end of file + }); diff --git a/app/views/profiles/keys/show.html.haml b/app/views/profiles/keys/show.html.haml index c4fc1bb269..cfd5329896 100644 --- a/app/views/profiles/keys/show.html.haml +++ b/app/views/profiles/keys/show.html.haml @@ -1,22 +1 @@ -.row - .col-md-4 - .panel.panel-default - .panel-heading - SSH Key - %ul.well-list - %li - %span.light Title: - %strong= @key.title - %li - %span.light Created on: - %strong= @key.created_at.stamp("Aug 21, 2011") - - .col-md-8 - %p - %span.light Fingerprint: - %strong= @key.fingerprint - %pre.well-pre - = @key.key - -.pull-right - = link_to 'Remove', profile_key_path(@key), data: {confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove delete-key" += render "key_details" diff --git a/app/views/profiles/notifications/_settings.html.haml b/app/views/profiles/notifications/_settings.html.haml index 218d51d31a..2c85d2a9b2 100644 --- a/app/views/profiles/notifications/_settings.html.haml +++ b/app/views/profiles/notifications/_settings.html.haml @@ -1,12 +1,12 @@ %li - %span.notification-icon-holder + %span.notification.fa.fa-holder - if notification.global? = notification_icon(@notification) - else = notification_icon(notification) %span.str-truncated - - if membership.kind_of? UsersGroup + - if membership.kind_of? GroupMember = link_to membership.group.name, membership.group - else = link_to_project(membership.project) diff --git a/app/views/profiles/notifications/show.html.haml b/app/views/profiles/notifications/show.html.haml index efe9c03219..273e72f8a4 100644 --- a/app/views/profiles/notifications/show.html.haml +++ b/app/views/profiles/notifications/show.html.haml @@ -1,51 +1,78 @@ %h3.page-title - Notifications settings + Notifications Settings %p.light - GitLab uses the email specified in your profile for notifications + These are your global notification settings. %hr -= form_tag profile_notifications_path, method: :put, remote: true, class: 'update-notifications form-horizontal global-notifications-form' do + += form_for @user, url: profile_notifications_path, method: :put, html: { class: 'update-notifications form-horizontal global-notifications-form' } do |f| + -if @user.errors.any? + %div.alert.alert-danger + %ul + - @user.errors.full_messages.each do |msg| + %li= msg + = hidden_field_tag :notification_type, 'global' - = label_tag :notification_level, 'Notification level', class: 'control-label' - .col-sm-10 - .radio - = label_tag nil, class: '' do - = radio_button_tag :notification_level, Notification::N_DISABLED, @notification.disabled?, class: 'trigger-submit' - .level-title - Disabled - %p You will not get any notifications via email + .form-group + = f.label :notification_email, class: "control-label" + .col-sm-10 + = f.select :notification_email, @user.all_emails, { include_blank: false }, class: "form-control" - .radio - = label_tag nil, class: '' do - = radio_button_tag :notification_level, Notification::N_PARTICIPATING, @notification.participating?, class: 'trigger-submit' - .level-title - Participating - %p You will only receive notifications from related resources (e.g. from your commits or assigned issues) + .form-group + = f.label :notification_level, class: 'control-label' + .col-sm-10 + .radio + = f.label :notification_level, value: Notification::N_DISABLED do + = f.radio_button :notification_level, Notification::N_DISABLED + .level-title + Disabled + %p You will not get any notifications via email - .radio - = label_tag nil, class: '' do - = radio_button_tag :notification_level, Notification::N_WATCH, @notification.watch?, class: 'trigger-submit' - .level-title - Watch - %p You will receive all notifications from projects in which you participate + .radio + = f.label :notification_level, value: Notification::N_MENTION do + = f.radio_button :notification_level, Notification::N_MENTION + .level-title + Mention + %p You will receive notifications only for comments in which you were @mentioned + + .radio + = f.label :notification_level, value: Notification::N_PARTICIPATING do + = f.radio_button :notification_level, Notification::N_PARTICIPATING + .level-title + Participating + %p You will only receive notifications from related resources (e.g. from your commits or assigned issues) + + .radio + = f.label :notification_level, value: Notification::N_WATCH do + = f.radio_button :notification_level, Notification::N_WATCH + .level-title + Watch + %p You will receive all notifications from projects in which you participate + + .form-actions + = f.submit 'Save changes', class: "btn btn-create" .clearfix %hr - %p - You can also specify notification level per group or per project - %br - By default all projects and groups uses notification level set above .row.all-notifications .col-md-6 + %p + You can also specify notification level per group or per project. + %br + By default, all projects and groups will use the notification level set above. %h4 Groups: %ul.bordered-list - - @users_groups.each do |users_group| - - notification = Notification.new(users_group) - = render 'settings', type: 'group', membership: users_group, notification: notification + - @group_members.each do |group_member| + - notification = Notification.new(group_member) + = render 'settings', type: 'group', membership: group_member, notification: notification .col-md-6 + %p + To specify the notification level per project of a group you belong to, + %br + you need to be a member of the project itself, not only its group. %h4 Projects: %ul.bordered-list - - @users_projects.each do |users_project| - - notification = Notification.new(users_project) - = render 'settings', type: 'project', membership: users_project, notification: notification + - @project_members.each do |project_member| + - notification = Notification.new(project_member) + = render 'settings', type: 'project', membership: project_member, notification: notification diff --git a/app/views/profiles/passwords/edit.html.haml b/app/views/profiles/passwords/edit.html.haml index 2a7d317aa3..4b04b113e8 100644 --- a/app/views/profiles/passwords/edit.html.haml +++ b/app/views/profiles/passwords/edit.html.haml @@ -1,25 +1,30 @@ -%h3.page-title Password +%h3.page-title Password Settings %p.light - Change your password or recover your current one. + - if @user.password_automatically_set? + Set your password. + - else + Change your password or recover your current one. %hr .update-password = form_for @user, url: profile_password_path, method: :put, html: { class: 'form-horizontal' } do |f| %div %p.slead - You must provide current password in order to change it. - %br - After a successful password update you will be redirected to login page where you should login with your new password + - unless @user.password_automatically_set? + You must provide current password in order to change it. + %br + After a successful password update, you will be redirected to the login page where you can log in with your new password. -if @user.errors.any? .alert.alert-danger %ul - @user.errors.full_messages.each do |msg| %li= msg - .form-group - = f.label :current_password, class: 'control-label' - .col-sm-10 - = f.password_field :current_password, required: true, class: 'form-control' - %div - = link_to "Forgot your password?", reset_profile_password_path, method: :put + - unless @user.password_automatically_set? + .form-group + = f.label :current_password, class: 'control-label' + .col-sm-10 + = f.password_field :current_password, required: true, class: 'form-control' + %div + = link_to "Forgot your password?", reset_profile_password_path, method: :put .form-group = f.label :password, 'New password', class: 'control-label' @@ -30,4 +35,4 @@ .col-sm-10 = f.password_field :password_confirmation, required: true, class: 'form-control' .form-actions - = f.submit 'Save password', class: "btn btn-save" + = f.submit 'Save password', class: "btn btn-create" diff --git a/app/views/profiles/passwords/new.html.haml b/app/views/profiles/passwords/new.html.haml index aef7348fd2..8bed6e0dbe 100644 --- a/app/views/profiles/passwords/new.html.haml +++ b/app/views/profiles/passwords/new.html.haml @@ -10,10 +10,11 @@ %ul - @user.errors.full_messages.each do |msg| %li= msg - - .form-group - = f.label :current_password, class: 'control-label' - .col-sm-10= f.password_field :current_password, required: true, class: 'form-control' + + - unless @user.password_automatically_set? + .form-group + = f.label :current_password, class: 'control-label' + .col-sm-10= f.password_field :current_password, required: true, class: 'form-control' .form-group = f.label :password, class: 'control-label' .col-sm-10= f.password_field :password, required: true, class: 'form-control' diff --git a/app/views/profiles/show.html.haml b/app/views/profiles/show.html.haml index e03a43cbf1..6c745e69e4 100644 --- a/app/views/profiles/show.html.haml +++ b/app/views/profiles/show.html.haml @@ -1,7 +1,7 @@ %h3.page-title - Profile settings + Profile Settings %p.light - This information appears on your profile. + This information will appear on your profile. - if current_user.ldap_user? Some options are unavailable for LDAP accounts %hr @@ -36,11 +36,16 @@ = f.text_field :email, class: "form-control", required: true - if @user.unconfirmed_email.present? %span.help-block - Please click the link in the confirmation email before continuing, it was send to + Please click the link in the confirmation email before continuing, it was sent to %strong #{@user.unconfirmed_email} - else %span.help-block We also use email for avatar detection if no avatar is uploaded. + .form-group + = f.label :public_email, class: "control-label" + .col-sm-10 + = f.select :public_email, options_for_select(@user.all_emails, selected: @user.public_email), {include_blank: 'Do not show in profile'}, class: "form-control" + %span.help-block This email will be displayed on your public profile. .form-group = f.label :skype, class: "control-label" .col-sm-10= f.text_field :skype, class: "form-control" @@ -53,10 +58,13 @@ .form-group = f.label :website_url, 'Website', class: "control-label" .col-sm-10= f.text_field :website_url, class: "form-control" + .form-group + = f.label :location, 'Location', class: "control-label" + .col-sm-10= f.text_field :location, class: "form-control" .form-group = f.label :bio, class: "control-label" .col-sm-10 - = f.text_area :bio, rows: 6, class: "form-control", maxlength: 250 + = f.text_area :bio, rows: 4, class: "form-control", maxlength: 250 %span.help-block Tell us about yourself in fewer than 250 characters. .col-md-5 @@ -77,22 +85,26 @@ %br or change it at #{link_to "gravatar.com", "http://gravatar.com"} %hr - %a.choose-btn.btn.btn-small.js-choose-user-avatar-button - %i.icon-paper-clip + %a.choose-btn.btn.btn-sm.js-choose-user-avatar-button + %i.fa.fa-paperclip %span Choose File ...   %span.file_name.js-avatar-filename File name... = f.file_field :avatar, class: "js-user-avatar-input hidden" - .light The maximum file size allowed is 100KB. + .light The maximum file size allowed is 200KB. - if @user.avatar? %hr - = link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-small remove-avatar" + = link_to 'Remove avatar', profile_avatar_path, data: { confirm: "Avatar will be removed. Are you sure?"}, method: :delete, class: "btn btn-remove btn-sm remove-avatar" - if @user.public_profile? - .bs-callout.bs-callout-info + .alert.alert-info %h4 Public profile %p Your profile is publicly visible because you joined public project(s) - .form-actions - = f.submit 'Save changes', class: "btn btn-save" + .row + .col-md-7 + .form-group + .col-sm-2   + .col-sm-10 + = f.submit 'Save changes', class: "btn btn-success" diff --git a/app/views/profiles/update.js.erb b/app/views/profiles/update.js.erb index 04b5cf4827..e664ac2a52 100644 --- a/app/views/profiles/update.js.erb +++ b/app/views/profiles/update.js.erb @@ -1,6 +1,6 @@ // Remove body class for any previous theme, re-add current one -$('body').removeClass('ui_basic ui_mars ui_modern ui_gray ui_color') -$('body').addClass('<%= app_theme %>') +$('body').removeClass('ui_basic ui_mars ui_modern ui_gray ui_color light_theme dark_theme') +$('body').addClass('<%= app_theme %> <%= theme_type %>') // Re-render the header to reflect the new theme $('header').html('<%= escape_javascript(render("layouts/head_panel", title: "Profile")) %>') diff --git a/app/views/projects/_bitbucket_import_modal.html.haml b/app/views/projects/_bitbucket_import_modal.html.haml new file mode 100644 index 0000000000..07d4d60276 --- /dev/null +++ b/app/views/projects/_bitbucket_import_modal.html.haml @@ -0,0 +1,13 @@ +%div#bitbucket_import_modal.modal.hide + .modal-dialog + .modal-content + .modal-header + %a.close{href: "#", "data-dismiss" => "modal"} × + %h3 Import projects from Bitbucket + .modal-body + To enable importing projects from Bitbucket, + - if current_user.admin? + you need to + - else + your GitLab administrator needs to + == #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/bitbucket.md'}. diff --git a/app/views/projects/_commit_button.html.haml b/app/views/projects/_commit_button.html.haml new file mode 100644 index 0000000000..35f7e7bb34 --- /dev/null +++ b/app/views/projects/_commit_button.html.haml @@ -0,0 +1,6 @@ +.form-actions + .commit-button-annotation + = button_tag 'Commit Changes', + class: 'btn commit-btn js-commit-button btn-create' + = link_to 'Cancel', cancel_path, + class: 'btn btn-cancel', data: {confirm: leave_edit_message} diff --git a/app/views/projects/_dropdown.html.haml b/app/views/projects/_dropdown.html.haml index e283bd2bf1..3036f11bb2 100644 --- a/app/views/projects/_dropdown.html.haml +++ b/app/views/projects/_dropdown.html.haml @@ -1,33 +1,37 @@ - if current_user .dropdown.pull-right %a.dropdown-toggle.btn.btn-new{href: '#', "data-toggle" => "dropdown"} - %i.icon-reorder + %i.fa.fa-bars %ul.dropdown-menu - if @project.issues_enabled && can?(current_user, :write_issue, @project) %li - = link_to url_for_new_issue, title: "New Issue" do + = link_to url_for_new_issue(@project, only_path: true), title: "New Issue" do + %i.fa.fa-fw.fa-exclamation-circle New issue - if @project.merge_requests_enabled && can?(current_user, :write_merge_request, @project) %li - = link_to new_project_merge_request_path(@project), title: "New Merge Request" do + = link_to new_namespace_project_merge_request_path(@project.namespace, @project), title: "New Merge Request" do + %i.fa.fa-fw.fa-tasks New merge request - if @project.snippets_enabled && can?(current_user, :write_snippet, @project) %li - = link_to new_project_snippet_path(@project), title: "New Snippet" do + = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do + %i.fa.fa-fw.fa-file-text-o New snippet - - if can?(current_user, :admin_team_member, @project) + - if can?(current_user, :admin_project_member, @project) %li - = link_to new_project_team_member_path(@project), title: "New project member" do + = link_to namespace_project_project_members_path(@project.namespace, @project), title: "New project member" do + %i.fa.fa-fw.fa-users New project member - if can? current_user, :push_code, @project %li.divider %li - = link_to new_project_branch_path(@project) do - %i.icon-code-fork - Git branch + = link_to new_namespace_project_branch_path(@project.namespace, @project) do + %i.fa.fa-fw.fa-code-fork + New branch %li - = link_to new_project_tag_path(@project) do - %i.icon-tag - Git tag + = link_to new_namespace_project_tag_path(@project.namespace, @project) do + %i.fa.fa-fw.fa-tag + New tag diff --git a/app/views/projects/_github_import_modal.html.haml b/app/views/projects/_github_import_modal.html.haml new file mode 100644 index 0000000000..e88a0f7d68 --- /dev/null +++ b/app/views/projects/_github_import_modal.html.haml @@ -0,0 +1,13 @@ +%div#github_import_modal.modal.hide + .modal-dialog + .modal-content + .modal-header + %a.close{href: "#", "data-dismiss" => "modal"} × + %h3 Import projects from GitHub + .modal-body + To enable importing projects from GitHub, + - if current_user.admin? + you need to + - else + your GitLab administrator needs to + == #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/github.md'}. \ No newline at end of file diff --git a/app/views/projects/_gitlab_import_modal.html.haml b/app/views/projects/_gitlab_import_modal.html.haml new file mode 100644 index 0000000000..52212b6ae0 --- /dev/null +++ b/app/views/projects/_gitlab_import_modal.html.haml @@ -0,0 +1,13 @@ +%div#gitlab_import_modal.modal.hide + .modal-dialog + .modal-content + .modal-header + %a.close{href: "#", "data-dismiss" => "modal"} × + %h3 Import projects from GitLab.com + .modal-body + To enable importing projects from GitLab.com, + - if current_user.admin? + you need to + - else + your GitLab administrator needs to + == #{link_to 'setup OAuth integration', 'https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/gitlab.md'}. \ No newline at end of file diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml index 62348f26f0..5689bdee1c 100644 --- a/app/views/projects/_home_panel.html.haml +++ b/app/views/projects/_home_panel.html.haml @@ -1,36 +1,43 @@ - empty_repo = @project.empty_repo? .project-home-panel{:class => ("empty-project" if empty_repo)} - .visibility-level-label.has_tooltip{'data-title' => "#{visibility_level_label(@project.visibility_level)} project" } - = visibility_level_icon(@project.visibility_level) - .row - .col-sm-6 - %h4.project-home-title - = @project.name_with_namespace + .project-identicon-holder + = project_icon(@project, alt: '', class: 'avatar project-avatar') + .project-home-row.project-home-row-top + .project-home-desc + - if @project.description.present? + = escaped_autolink(@project.description) + - if can?(current_user, :admin_project, @project) + – + = link_to 'Edit', edit_namespace_project_path + - elsif !@project.empty_repo? && @repository.readme + - readme = @repository.readme + – + = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)) do + = readme.name + .project-repo-buttons + .inline.star.js-toggler-container{class: @show_star ? 'on' : ''} + - if current_user + = link_to_toggle_star('Star this project.', false) + = link_to_toggle_star('Unstar this project.', true) + - else + = link_to new_user_session_path, class: 'btn star-btn has_tooltip', title: 'You must sign in to star a project' do + %span + = icon('star') + Star + %span.count + = @project.star_count + - unless @project.empty_repo? + - if current_user && can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace + .inline.fork-buttons.prepend-left-10 + - if current_user.already_forked?(@project) && current_user.manageable_namespaces.size < 2 + = link_to namespace_project_path(current_user, current_user.fork_of(@project)), title: 'Go to your fork', class: 'btn btn-sm btn-default' do + = link_to_toggle_fork + - else + = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn btn-sm btn-default' do + = link_to_toggle_fork - .col-sm-6 - - if current_user && !empty_repo - .project-home-dropdown - = render "dropdown" - = render "shared/clone_panel" - - .project-home-extra.row - .col-md-7 - .project-home-desc - - if @project.description.present? - = auto_link ERB::Util.html_escape(@project.description), link: :urls - - if can?(current_user, :admin_project, @project) - – - %strong= link_to 'Edit', edit_project_path - - elsif !@project.empty_repo? && @repository.readme - - readme = @repository.readme - – - = link_to project_blob_path(@project, tree_join(@repository.root_ref, readme.name)) do - = readme.name - - .col-md-5 - .project-home-links - - unless empty_repo - = link_to pluralize(number_with_delimiter(@repository.commit_count), 'commit'), project_commits_path(@project, @ref || @repository.root_ref) - = link_to pluralize(number_with_delimiter(@repository.branch_names.count), 'branch'), project_branches_path(@project) - = link_to pluralize(number_with_delimiter(@repository.tag_names.count), 'tag'), project_tags_path(@project) - %span.light.prepend-left-20= repository_size + .project-home-row.hidden-xs + - if current_user && !empty_repo + .project-home-dropdown + = render "dropdown" + = render "shared/clone_panel" diff --git a/app/views/projects/_issuable_form.html.haml b/app/views/projects/_issuable_form.html.haml new file mode 100644 index 0000000000..e321a84974 --- /dev/null +++ b/app/views/projects/_issuable_form.html.haml @@ -0,0 +1,87 @@ +- if issuable.errors.any? + .row + .col-sm-10.col-sm-offset-2 + .alert.alert-danger + - issuable.errors.full_messages.each do |msg| + %span= msg + %br +.form-group + = f.label :title, class: 'control-label' do + %strong= 'Title *' + .col-sm-10 + = f.text_field :title, maxlength: 255, autofocus: true, + class: 'form-control pad js-gfm-input', required: true +.form-group.issuable-description + = f.label :description, 'Description', class: 'control-label' + .col-sm-10 + + = render layout: 'projects/md_preview', locals: { preview_class: "wiki" } do + = render 'projects/zen', f: f, attr: :description, + classes: 'description form-control' + .col-sm-12.hint + .pull-left + Parsed with + #{link_to 'GitLab Flavored Markdown', help_page_path('markdown', 'markdown'), target: '_blank'}. + .pull-right + Attach files by dragging & dropping + or #{link_to 'selecting them', '#', class: 'markdown-selector' }. + + .clearfix + .error-alert +%hr +.form-group + .issue-assignee + = f.label :assignee_id, class: 'control-label' do + %i.fa.fa-user + Assign to + .col-sm-10 + = users_select_tag("#{issuable.class.model_name.param_key}[assignee_id]", + placeholder: 'Select a user', class: 'custom-form-control', null_user: true, + selected: issuable.assignee_id) +   + = link_to 'Assign to me', '#', class: 'btn assign-to-me-link' +.form-group + .issue-milestone + = f.label :milestone_id, class: 'control-label' do + %i.fa.fa-clock-o + Milestone + .col-sm-10 + - if milestone_options(issuable).present? + = f.select(:milestone_id, milestone_options(issuable), + { include_blank: 'Select milestone' }, { class: 'select2' }) + - else + .prepend-top-10 + %span.light No open milestones available. +   + - 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 + = f.label :label_ids, class: 'control-label' do + %i.fa.fa-tag + Labels + .col-sm-10 + - if issuable.project.labels.any? + = f.collection_select :label_ids, issuable.project.labels.all, :id, :name, + { selected: issuable.label_ids }, multiple: true, class: 'select2' + - else + .prepend-top-10 + %span.light No labels yet. +   + - 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 + +.form-actions + - if !issuable.project.empty_repo? && (guide_url = contribution_guide_url(issuable.project)) && !issuable.persisted? + %p + Please review the + %strong #{link_to 'guidelines for contribution', guide_url} + to this repository. + - if issuable.new_record? + = f.submit "Submit new #{issuable.class.model_name.human.downcase}", class: 'btn btn-create' + - else + = f.submit 'Save changes', class: 'btn btn-save' + - if issuable.new_record? + - cancel_project = issuable.source_project + - else + - cancel_project = issuable.project + = link_to 'Cancel', [cancel_project.namespace.becomes(Namespace), cancel_project, issuable], class: 'btn btn-cancel' diff --git a/app/views/projects/_md_preview.html.haml b/app/views/projects/_md_preview.html.haml new file mode 100644 index 0000000000..f356a25dbf --- /dev/null +++ b/app/views/projects/_md_preview.html.haml @@ -0,0 +1,13 @@ +%ul.nav.nav-tabs + %li.active + = link_to '#md-write-holder', class: 'js-md-write-button' do + Write + %li + = link_to '#md-preview-holder', class: 'js-md-preview-button', + data: { url: markdown_preview_namespace_project_path(@project.namespace, @project) } do + Preview +%div + .md-write-holder + = yield + .md-preview-holder.hide + .js-md-preview{class: (preview_class if defined?(preview_class))} diff --git a/app/views/projects/_settings_nav.html.haml b/app/views/projects/_settings_nav.html.haml index 9b73f06a5b..281a84a3d3 100644 --- a/app/views/projects/_settings_nav.html.haml +++ b/app/views/projects/_settings_nav.html.haml @@ -1,25 +1,31 @@ -%ul.nav.nav-pills.nav-stacked.nav-stacked-menu.append-bottom-20.project-settings-nav +%ul.project-settings-nav.sidebar-subnav = nav_link(path: 'projects#edit') do - = link_to edit_project_path(@project), class: "stat-tab tab " do - %i.icon-edit - Project - = nav_link(controller: [:team_members, :teams]) do - = link_to project_team_index_path(@project), class: "team-tab tab" do - %i.icon-group - Members + = link_to edit_project_path(@project), title: 'Project', class: "stat-tab tab " do + %i.fa.fa-pencil-square-o + %span + Project + = nav_link(controller: [:project_members, :teams]) do + = link_to namespace_project_project_members_path(@project.namespace, @project), title: 'Members', class: "team-tab tab" do + %i.fa.fa-users + %span + Members = nav_link(controller: :deploy_keys) do - = link_to project_deploy_keys_path(@project) do - %i.icon-key - Deploy Keys + = link_to namespace_project_deploy_keys_path(@project.namespace, @project), title: 'Deploy Keys' do + %i.fa.fa-key + %span + Deploy Keys = nav_link(controller: :hooks) do - = link_to project_hooks_path(@project) do - %i.icon-link - Web Hooks + = link_to namespace_project_hooks_path(@project.namespace, @project), title: 'Web Hooks' do + %i.fa.fa-link + %span + Web Hooks = nav_link(controller: :services) do - = link_to project_services_path(@project) do - %i.icon-cogs - Services + = link_to namespace_project_services_path(@project.namespace, @project), title: 'Services' do + %i.fa.fa-cogs + %span + Services = nav_link(controller: :protected_branches) do - = link_to project_protected_branches_path(@project) do - %i.icon-lock - Protected branches + = link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches' do + %i.fa.fa-lock + %span + Protected branches diff --git a/app/views/projects/_visibility_level.html.haml b/app/views/projects/_visibility_level.html.haml index 5f34e66b3e..42c8e68522 100644 --- a/app/views/projects/_visibility_level.html.haml +++ b/app/views/projects/_visibility_level.html.haml @@ -7,8 +7,8 @@ - Gitlab::VisibilityLevel.values.each do |level| .radio - restricted = restricted_visibility_levels.include?(level) - = f.radio_button :visibility_level, level, checked: (visibility_level == level), disabled: restricted = label :project_visibility_level, level do + = f.radio_button :visibility_level, level, checked: (visibility_level == level), disabled: restricted = visibility_level_icon(level) .option-title = visibility_level_label(level) diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml new file mode 100644 index 0000000000..cf1c55ecca --- /dev/null +++ b/app/views/projects/_zen.html.haml @@ -0,0 +1,10 @@ +.zennable + %input#zen-toggle-comment.zen-toggle-comment{ tabindex: '-1', type: 'checkbox' } + .zen-backdrop + - classes << ' js-gfm-input markdown-area' + = f.text_area attr, class: classes, placeholder: 'Leave a comment' + = link_to nil, class: 'zen-enter-link', tabindex: '-1' do + %i.fa.fa-expand + Edit in fullscreen + = link_to nil, class: 'zen-leave-link' do + %i.fa.fa-compress diff --git a/app/views/projects/blame/show.html.haml b/app/views/projects/blame/show.html.haml index cdca8b2e63..e6a859fea8 100644 --- a/app/views/projects/blame/show.html.haml +++ b/app/views/projects/blame/show.html.haml @@ -3,37 +3,33 @@ #tree-holder.tree-holder .file-holder .file-title - %i.icon-file - %span.file_name + %i.fa.fa-file + %strong = @path - %small= number_to_human_size @blob.size - %span.options= render "projects/blob/actions" - .file-content.blame + %small= number_to_human_size @blob.size + .file-actions + = render "projects/blob/actions" + .file-content.blame.highlight %table - - current_line = 1 - - @blame.each do |commit, lines| + - @blame.each do |commit, lines, since| - commit = Commit.new(commit) %tr %td.blame-commit %span.commit - = link_to commit.short_id(8), project_commit_path(@project, commit), class: "commit_short_id" + = link_to commit.short_id, namespace_project_commit_path(@project.namespace, @project, commit), class: "commit_short_id"   = commit_author_link(commit, avatar: true, size: 16)   - = link_to_gfm truncate(commit.title, length: 20), project_commit_path(@project, commit.id), class: "row_title" + = link_to_gfm truncate(commit.title, length: 20), namespace_project_commit_path(@project.namespace, @project, commit.id), class: "row_title" %td.lines.blame-numbers %pre - - if lines.empty? - = current_line - - current_line += 1 - - else - - lines.each do |line| - = current_line - \ - - current_line += 1 + - (since...(since + lines.count)).each do |i| + = i + \ %td.lines - %pre - :erb - <% lines.each do |line| %> - <%= line %> - <% end %> + %pre{class: 'code highlight white'} + %code + :erb + <% lines.each do |line| %> + <%= highlight(@blob.name, line, true).html_safe %> + <% end %> diff --git a/app/views/projects/blob/_actions.html.haml b/app/views/projects/blob/_actions.html.haml index cabef3c19f..13f8271b97 100644 --- a/app/views/projects/blob/_actions.html.haml +++ b/app/views/projects/blob/_actions.html.haml @@ -1,19 +1,22 @@ .btn-group.tree-btn-group - -# only show edit link for text files - - if @blob.text? - - if allowed_tree_edit? - = link_to "edit", project_edit_tree_path(@project, @id), class: "btn btn-small" - - else - %span.btn.btn-small.disabled edit - = link_to "raw", project_raw_path(@project, @id), class: "btn btn-small", target: "_blank" + = edit_blob_link(@project, @ref, @path) + = link_to 'Raw', namespace_project_raw_path(@project.namespace, @project, @id), + class: 'btn btn-sm', target: '_blank' -# only show normal/blame view links for text files - if @blob.text? - - if current_page? project_blame_path(@project, @id) - = link_to "normal view", project_blob_path(@project, @id), class: "btn btn-small" + - if current_page? namespace_project_blame_path(@project.namespace, @project, @id) + = link_to 'Normal View', namespace_project_blob_path(@project.namespace, @project, @id), + class: 'btn btn-sm' - else - = link_to "blame", project_blame_path(@project, @id), class: "btn btn-small" unless @blob.empty? - = link_to "history", project_commits_path(@project, @id), class: "btn btn-small" + = link_to 'Blame', namespace_project_blame_path(@project.namespace, @project, @id), + class: 'btn btn-sm' unless @blob.empty? + = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), + class: 'btn btn-sm' + - if @ref != @commit.sha + = link_to 'Permalink', namespace_project_blob_path(@project.namespace, @project, + tree_join(@commit.sha, @path)), class: 'btn btn-sm' - if allowed_tree_edit? - = link_to '#modal-remove-blob', class: "remove-blob btn btn-small btn-remove", "data-toggle" => "modal" do - remove + = button_tag class: 'remove-blob btn btn-sm btn-remove', + 'data-toggle' => 'modal', 'data-target' => '#modal-remove-blob' do + Remove diff --git a/app/views/projects/blob/_blob.html.haml b/app/views/projects/blob/_blob.html.haml index be785daced..65c3ab10e0 100644 --- a/app/views/projects/blob/_blob.html.haml +++ b/app/views/projects/blob/_blob.html.haml @@ -1,32 +1,34 @@ %ul.breadcrumb.repo-breadcrumb %li - %i.icon-angle-right - = link_to project_tree_path(@project, @ref) do + %i.fa.fa-angle-right + = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do = @project.path - tree_breadcrumbs(@tree, 6) do |title, path| %li - if path - if path.end_with?(@path) - = link_to project_blob_path(@project, path) do + = link_to namespace_project_blob_path(@project.namespace, @project, path) do %strong = truncate(title, length: 40) - else - = link_to truncate(title, length: 40), project_tree_path(@project, path) + = link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path) - else = link_to title, '#' -%ul.blob-commit-info.bs-callout.bs-callout-info.hidden-xs - - blob_commit = @repository.last_commit_for_path(@commit.id, @blob.path) +%ul.blob-commit-info.well.hidden-xs + - blob_commit = @repository.last_commit_for_path(@commit.id, blob.path) = render blob_commit, project: @project %div#tree-content-holder.tree-content-holder %article.file-holder - .file-title.clearfix - %i.icon-file - %span.file_name + .file-title + = blob_icon blob.mode, blob.name + %strong = blob.name - %small= number_to_human_size blob.size - %span.options.hidden-xs= render "actions" + %small + = number_to_human_size(blob.size) + .file-actions.hidden-xs + = render "actions" - if blob.text? = render "text", blob: blob - elsif blob.image? diff --git a/app/views/projects/blob/_download.html.haml b/app/views/projects/blob/_download.html.haml index cdbfee7cc6..f2c5e95ecf 100644 --- a/app/views/projects/blob/_download.html.haml +++ b/app/views/projects/blob/_download.html.haml @@ -1,7 +1,7 @@ .file-content.blob_file.blob-no-preview .center - = link_to project_raw_path(@project, @id) do + = link_to namespace_project_raw_path(@project.namespace, @project, @id) do %h1.light - %i.icon-download-alt + %i.fa.fa-download %h4 Download (#{number_to_human_size blob.size}) diff --git a/app/views/projects/blob/_editor.html.haml b/app/views/projects/blob/_editor.html.haml new file mode 100644 index 0000000000..96f188e4aa --- /dev/null +++ b/app/views/projects/blob/_editor.html.haml @@ -0,0 +1,25 @@ +.file-holder.file + .file-title + .editor-ref + %i.fa.fa-code-fork + = ref + %span.editor-file-name + - if @path + %span.monospace + = @path + + - if current_action?(:new) || current_action?(:create) + \/ + = text_field_tag 'file_name', params[:file_name], placeholder: "File name", + required: true, class: 'form-control new-file-name' + .pull-right + = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control' + + .file-content.code + %pre.js-edit-mode-pane#editor + = params[:content] || local_assigns[:blob_data] + - if local_assigns[:path] + .js-edit-mode-pane#preview.hide + .center + %h2 + %i.icon-spinner.icon-spin diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml index 692248dd23..09559a4967 100644 --- a/app/views/projects/blob/_remove.html.haml +++ b/app/views/projects/blob/_remove.html.haml @@ -9,15 +9,14 @@ %strong= @ref .modal-body - = form_tag project_blob_path(@project, @id), method: :delete, class: 'form-horizontal' do - .form-group.commit_message-group - = label_tag 'commit_message', class: "control-label" do - Commit message - .col-sm-10 - = render 'shared/commit_message_container', {textarea: text_area_tag('commit_message', - params[:commit_message], placeholder: "Removed this file because...", required: true, rows: 3, class: 'form-control')} + = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal' do + = render 'shared/commit_message_container', params: params, + placeholder: 'Removed this file because...' .form-group .col-sm-2 .col-sm-10 - = submit_tag 'Remove file', class: 'btn btn-remove' + = button_tag 'Remove file', class: 'btn btn-remove btn-remove-file' = link_to "Cancel", '#', class: "btn btn-cancel", "data-dismiss" => "modal" + +:javascript + disableButtonIfEmptyField('#commit_message', '.btn-remove-file') diff --git a/app/views/projects/blob/_text.html.haml b/app/views/projects/blob/_text.html.haml index 7cbea7c3eb..f6bd62f239 100644 --- a/app/views/projects/blob/_text.html.haml +++ b/app/views/projects/blob/_text.html.haml @@ -8,6 +8,6 @@ - else .file-content.code - unless blob.empty? - = render 'shared/file_hljs', blob: blob + = render 'shared/file_highlight', blob: blob - else .nothing-here-block Empty file diff --git a/app/views/projects/blob/diff.html.haml b/app/views/projects/blob/diff.html.haml index cfb91d6568..5c79d0ef11 100644 --- a/app/views/projects/blob/diff.html.haml +++ b/app/views/projects/blob/diff.html.haml @@ -1,7 +1,7 @@ - if @lines.present? - if @form.unfold? && @form.since != 1 && !@form.bottom? %tr.line_holder{ id: @form.since } - = render "projects/commits/diffs/match_line", {line: @match_line, + = render "projects/diffs/match_line", {line: @match_line, line_old: @form.since, line_new: @form.since, bottom: false} - @lines.each_with_index do |line, index| @@ -15,5 +15,5 @@ - if @form.unfold? && @form.bottom? && @form.to < @blob.loc %tr.line_holder{ id: @form.to } - = render "projects/commits/diffs/match_line", {line: @match_line, + = render "projects/diffs/match_line", {line: @match_line, line_old: @form.to, line_new: @form.to, bottom: true} diff --git a/app/views/projects/blob/edit.html.haml b/app/views/projects/blob/edit.html.haml new file mode 100644 index 0000000000..1f61a0b940 --- /dev/null +++ b/app/views/projects/blob/edit.html.haml @@ -0,0 +1,31 @@ +.file-editor + %ul.nav.nav-tabs.js-edit-mode + %li.active + = link_to '#editor' do + %i.fa.fa-edit + Edit file + + %li + = link_to '#preview', 'data-preview-url' => namespace_project_preview_blob_path(@project.namespace, @project, @id) do + %i.fa.fa-eye + = editing_preview_title(@blob.name) + + = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: "form-horizontal") do + = render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data + = render 'shared/commit_message_container', params: params, + placeholder: "Update #{@blob.name}" + + .form-group.branch + = label_tag 'branch', class: 'control-label' do + Branch + .col-sm-10 + = text_field_tag 'new_branch', @ref, class: "form-control" + + = hidden_field_tag 'last_commit', @last_commit + = hidden_field_tag 'content', '', id: "file-content" + = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] + = render 'projects/commit_button', ref: @ref, + cancel_path: @after_edit_path + +:javascript + blob = new EditBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", "#{@blob.language.try(:ace_mode)}") diff --git a/app/views/projects/blob/new.html.haml b/app/views/projects/blob/new.html.haml new file mode 100644 index 0000000000..d78a01f642 --- /dev/null +++ b/app/views/projects/blob/new.html.haml @@ -0,0 +1,19 @@ +%h3.page-title New file +.file-editor + = form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal form-new-file') do + = render 'projects/blob/editor', ref: @ref + = render 'shared/commit_message_container', params: params, + placeholder: 'Add new file' + + .form-group.branch + = label_tag 'branch', class: 'control-label' do + Branch + .col-sm-10 + = text_field_tag 'new_branch', @ref, class: "form-control" + + = hidden_field_tag 'content', '', id: 'file-content' + = render 'projects/commit_button', ref: @ref, + cancel_path: namespace_project_tree_path(@project.namespace, @project, @id) + +:javascript + blob = new NewBlob(gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}", null) diff --git a/app/views/projects/blob/preview.html.haml b/app/views/projects/blob/preview.html.haml new file mode 100644 index 0000000000..e7c3460ad7 --- /dev/null +++ b/app/views/projects/blob/preview.html.haml @@ -0,0 +1,25 @@ +.diff-file + .diff-content + - if gitlab_markdown?(@blob.name) + .file-content.wiki + = preserve do + = markdown(@content) + - elsif markup?(@blob.name) + .file-content.wiki + = raw render_markup(@blob.name, @content) + - else + .file-content.code + - unless @diff_lines.empty? + %table.text-file + - @diff_lines.each do |line| + %tr.line_holder{ class: "#{line.type}" } + - if line.type == "match" + %td.old_line= "..." + %td.new_line= "..." + %td.line_content.matched= line.text + - else + %td.old_line + %td.new_line + %td.line_content{class: "#{line.type}"}= raw diff_line_content(line.text) + - else + .nothing-here-block No changes. diff --git a/app/views/projects/branches/_branch.html.haml b/app/views/projects/branches/_branch.html.haml index 54a7b934dd..4e7415be4a 100644 --- a/app/views/projects/branches/_branch.html.haml +++ b/app/views/projects/branches/_branch.html.haml @@ -1,25 +1,25 @@ - commit = @repository.commit(branch.target) %li(class="js-branch-#{branch.name}") %h4 - = link_to project_tree_path(@project, branch.name) do - %strong= truncate(branch.name, length: 60) + = link_to namespace_project_tree_path(@project.namespace, @project, branch.name) do + %strong.str-truncated= branch.name - if branch.name == @repository.root_ref %span.label.label-info default - if @project.protected_branch? branch.name %span.label.label-success - %i.icon-lock + %i.fa.fa-lock protected .pull-right - if can?(current_user, :download_code, @project) - = render 'projects/repositories/download_archive', ref: branch.name, btn_class: 'btn-grouped btn-group-small' + = render 'projects/repositories/download_archive', ref: branch.name, btn_class: 'btn-grouped btn-group-xs' - if branch.name != @repository.root_ref - = link_to project_compare_index_path(@project, from: @repository.root_ref, to: branch.name), class: 'btn btn-grouped btn-small', method: :post, title: "Compare" do - %i.icon-copy + = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: branch.name), class: 'btn btn-grouped btn-xs', method: :post, title: "Compare" do + %i.fa.fa-files-o Compare - if can_remove_branch?(@project, branch.name) - = link_to project_branch_path(@project, branch.name), class: 'btn btn-grouped btn-small btn-remove remove-row', method: :delete, data: { confirm: 'Removed branch cannot be restored. Are you sure?'}, remote: true do - %i.icon-trash + = link_to namespace_project_branch_path(@project.namespace, @project, branch.name), class: 'btn btn-grouped btn-xs btn-remove remove-row', method: :delete, data: { confirm: 'Removed branch cannot be restored. Are you sure?'}, remote: true do + %i.fa.fa-trash-o - if commit %ul.list-unstyled diff --git a/app/views/projects/branches/destroy.js.haml b/app/views/projects/branches/destroy.js.haml index ec1661c0aa..882a4d0c5e 100644 --- a/app/views/projects/branches/destroy.js.haml +++ b/app/views/projects/branches/destroy.js.haml @@ -1,3 +1 @@ -:plain - $(".js-branch-#{@branch_name}").remove(); - $('.js-totalbranch-count').html("#{@repository.branches.size}") +$('.js-totalbranch-count').html("#{@repository.branches.size}") diff --git a/app/views/projects/branches/index.html.haml b/app/views/projects/branches/index.html.haml index 8bc4a8f7e9..a313ffcf27 100644 --- a/app/views/projects/branches/index.html.haml +++ b/app/views/projects/branches/index.html.haml @@ -3,12 +3,12 @@ Branches .pull-right - if can? current_user, :push_code, @project - = link_to new_project_branch_path(@project), class: 'btn btn-create' do - %i.icon-add-sign + = link_to new_namespace_project_branch_path(@project.namespace, @project), class: 'btn btn-create' do + %i.fa.fa-add-sign New branch   .dropdown.inline - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} %span.light sort: - if @sort.present? = @sort.humanize @@ -17,12 +17,12 @@ %b.caret %ul.dropdown-menu %li - = link_to project_branches_path(sort: nil) do + = link_to namespace_project_branches_path(sort: nil) do Name - = link_to project_branches_path(sort: 'recently_updated') do - Recently updated - = link_to project_branches_path(sort: 'last_updated') do - Last updated + = link_to namespace_project_branches_path(sort: 'recently_updated') do + = sort_title_recently_updated + = link_to namespace_project_branches_path(sort: 'last_updated') do + = sort_title_oldest_updated %hr - unless @branches.empty? %ul.bordered-list.top-list.all-branches diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml index 5da2ede293..e5fcb98c68 100644 --- a/app/views/projects/branches/new.html.haml +++ b/app/views/projects/branches/new.html.haml @@ -1,20 +1,25 @@ +- if @error + .alert.alert-danger + %button{ type: "button", class: "close", "data-dismiss" => "alert"} × + = @error %h3.page-title - %i.icon-code-fork + %i.fa.fa-code-fork New branch -= form_tag project_branches_path, method: :post, class: "form-horizontal" do += form_tag namespace_project_branches_path, method: :post, id: "new-branch-form", class: "form-horizontal" do .form-group = label_tag :branch_name, 'Name for new branch', class: 'control-label' .col-sm-10 - = text_field_tag :branch_name, nil, placeholder: 'enter new branch name', required: true, tabindex: 1, class: 'form-control' + = text_field_tag :branch_name, params[:branch_name], placeholder: 'enter new branch name', required: true, tabindex: 1, class: 'form-control' .form-group = label_tag :ref, 'Create from', class: 'control-label' .col-sm-10 - = text_field_tag :ref, nil, placeholder: 'existing branch name, tag or commit SHA', required: true, tabindex: 2, class: 'form-control' + = text_field_tag :ref, params[:ref], placeholder: 'existing branch name, tag or commit SHA', required: true, tabindex: 2, class: 'form-control' .form-actions - = submit_tag 'Create branch', class: 'btn btn-create', tabindex: 3 - = link_to 'Cancel', project_branches_path(@project), class: 'btn btn-cancel' + = button_tag 'Create branch', class: 'btn btn-create', tabindex: 3 + = link_to 'Cancel', namespace_project_branches_path(@project.namespace, @project), class: 'btn btn-cancel' :javascript + disableButtonIfAnyEmptyField($("#new-branch-form"), ".form-control", ".btn-create"); var availableTags = #{@project.repository.ref_names.to_json}; $("#ref").autocomplete({ diff --git a/app/views/projects/commit/_commit_box.html.haml b/app/views/projects/commit/_commit_box.html.haml index 2bc9048b2a..3f645b8139 100644 --- a/app/views/projects/commit/_commit_box.html.haml +++ b/app/views/projects/commit/_commit_box.html.haml @@ -2,23 +2,24 @@ %div - if @notes_count > 0 %span.btn.disabled.btn-grouped - %i.icon-comment + %i.fa.fa-comment = @notes_count .pull-left.btn-group %a.btn.btn-grouped.dropdown-toggle{ data: {toggle: :dropdown} } - %i.icon-download-alt + %i.fa.fa-download Download as %span.caret %ul.dropdown-menu - %li= link_to "Email Patches", project_commit_path(@project, @commit, format: :patch) - %li= link_to "Plain Diff", project_commit_path(@project, @commit, format: :diff) - = link_to project_tree_path(@project, @commit), class: "btn btn-primary btn-grouped" do + - 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-primary btn-grouped" do %span Browse Code » %div %p %span.light Commit - = link_to @commit.id, project_commit_path(@project, @commit) + = link_to @commit.id, namespace_project_commit_path(@project.namespace, @project, @commit) .commit-info-row %span.light Authored by %strong @@ -35,20 +36,10 @@ .commit-info-row %span.cgray= pluralize(@commit.parents.count, "parent") - @commit.parents.each do |parent| - = link_to parent.id[0...10], project_commit_path(@project, parent) + = link_to parent.short_id, namespace_project_commit_path(@project.namespace, @project, parent) -- if @branches.any? - .commit-info-row - %span.cgray - Exists in - %span - - branch = commit_default_branch(@project, @branches) - = link_to(branch, project_tree_path(@project, branch)) - - if @branches.any? - and in - = link_to("#{pluralize(@branches.count, "other branch")}", "#", class: "js-details-expand") - %span.js-details-content.hide - = commit_branches_links(@project, @branches) +.commit-info-row.branches + %i.fa.fa-spinner.fa-spin .commit-box %h3.commit-title @@ -56,3 +47,6 @@ - if @commit.description.present? %pre.commit-description = preserve(gfm(escape_once(@commit.description))) + +:coffeescript + $(".commit-info-row.branches").load("#{branches_namespace_project_commit_path(@project.namespace, @project, @commit.id)}") diff --git a/app/views/projects/commit/branches.html.haml b/app/views/projects/commit/branches.html.haml new file mode 100644 index 0000000000..82aac1fbd1 --- /dev/null +++ b/app/views/projects/commit/branches.html.haml @@ -0,0 +1,16 @@ +- if @branches.any? + %span + - branch = commit_default_branch(@project, @branches) + = link_to(namespace_project_tree_path(@project.namespace, @project, branch)) do + %span.label.label-gray + %i.fa.fa-code-fork + = branch + - if @branches.any? || @tags.any? + = link_to("#", class: "js-details-expand") do + %span.label.label-gray + \... + %span.js-details-content.hide + - if @branches.any? + = commit_branches_links(@project, @branches) + - if @tags.any? + = commit_tags_links(@project, @tags) diff --git a/app/views/projects/commit/show.html.haml b/app/views/projects/commit/show.html.haml index 0a15aef6cb..fc721067ed 100644 --- a/app/views/projects/commit/show.html.haml +++ b/app/views/projects/commit/show.html.haml @@ -1,3 +1,3 @@ = render "commit_box" -= render "projects/commits/diffs", diffs: @diffs, project: @project += render "projects/diffs/diffs", diffs: @diffs, project: @project = render "projects/notes/notes_with_form" diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml index 8e73663939..c6026f9680 100644 --- a/app/views/projects/commits/_commit.html.haml +++ b/app/views/projects/commits/_commit.html.haml @@ -1,24 +1,24 @@ %li.commit.js-toggle-container .commit-row-title - = link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id" -   - %span.str-truncated - = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message" + %strong.str-truncated + = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" - if commit.description? %a.text-expander.js-toggle-button ... - = link_to_browse_code(project, commit) + .pull-right + = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id" .notes_count - if @note_counts - note_count = @note_counts.fetch(commit.id, 0) - else - notes = project.notes.for_commit_id(commit.id) - - note_count = notes.count + - note_count = notes.user.count - if note_count > 0 - %span.label.label-gray - %i.icon-comment= note_count + %span.light + %i.fa.fa-comments + = note_count - if commit.description? .commit-row-description.js-toggle-content @@ -26,6 +26,8 @@ = preserve(gfm(escape_once(commit.description))) .commit-row-info - = commit_author_link(commit, avatar: true, size: 16) + = commit_author_link(commit, avatar: true, size: 24) + authored .committed_ago #{time_ago_with_tooltip(commit.committed_date)}   + = link_to_browse_code(project, commit) diff --git a/app/views/projects/commits/_commit_list.html.haml b/app/views/projects/commits/_commit_list.html.haml new file mode 100644 index 0000000000..2ee7d73bd2 --- /dev/null +++ b/app/views/projects/commits/_commit_list.html.haml @@ -0,0 +1,11 @@ +%div.panel.panel-default + .panel-heading + Commits (#{@commits.count}) + - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE + %ul.well-list + - Commit.decorate(@commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE)).each do |commit| + = render "projects/commits/inline_commit", commit: commit, project: @project + %li.warning-row.unstyled + other #{@commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE} commits hidden to prevent performance issues. + - else + %ul.well-list= render Commit.decorate(@commits), project: @project diff --git a/app/views/projects/commits/_commits.html.haml b/app/views/projects/commits/_commits.html.haml index 77f795f23f..0cd9ce1f37 100644 --- a/app/views/projects/commits/_commits.html.haml +++ b/app/views/projects/commits/_commits.html.haml @@ -1,11 +1,15 @@ +- unless defined?(project) + - project = @project + - @commits.group_by { |c| c.committed_date.to_date }.sort.reverse.each do |day, commits| .row.commits-row - .col-md-2 - %h4 - %i.icon-calendar + .col-md-2.hidden-xs.hidden-sm + %h5.commits-row-date + %i.fa.fa-calendar %span= day.stamp("28 Aug, 2010") - %p= pluralize(commits.count, 'commit') - .col-md-10 + .light + = pluralize(commits.count, 'commit') + .col-md-10.col-sm-12 %ul.bordered-list - = render commits, project: @project + = render commits, project: project %hr.lists-separator diff --git a/app/views/projects/commits/_diff_file.html.haml b/app/views/projects/commits/_diff_file.html.haml deleted file mode 100644 index 6e6107c884..0000000000 --- a/app/views/projects/commits/_diff_file.html.haml +++ /dev/null @@ -1,44 +0,0 @@ -- file = project.repository.blob_for_diff(@commit, diff) -- return unless file -- blob_diff_path = diff_project_blob_path(project, - tree_join(@commit.id, diff.new_path)) -.diff-file{id: "diff-#{i}", data: {blob_diff_path: blob_diff_path }} - .diff-header{id: "file-path-#{hexdigest(diff.new_path || diff.old_path)}"} - - if diff.deleted_file - %span= diff.old_path - - .diff-btn-group - - if @commit.parent_ids.present? - = view_file_btn(@commit.parent_id, diff, project) - - else - %span= diff.new_path - - if diff_file_mode_changed?(diff) - %span.file-mode= "#{diff.a_mode} → #{diff.b_mode}" - - .diff-btn-group - = link_to "#", class: "js-toggle-diff-comments btn btn-small" do - %i.icon-chevron-down - Diff comments -   - - - if @merge_request && @merge_request.source_project - = link_to project_edit_tree_path(@merge_request.source_project, tree_join(@merge_request.source_branch, diff.new_path), from_merge_request_id: @merge_request.id), { class: 'btn btn-small' } do - Edit -   - - = view_file_btn(@commit.id, diff, project) - - .diff-content - -# Skipp all non non-supported blobs - - return unless file.respond_to?('text?') - - if file.text? - - if params[:view] == 'parallel' - = render "projects/commits/parallel_view", diff: diff, project: project, file: file, index: i - - else - = render "projects/commits/text_file", diff: diff, index: i - - elsif file.image? - - old_file = project.repository.prev_blob_for_diff(@commit, diff) - = render "projects/commits/image", diff: diff, old_file: old_file, file: file, index: i - - else - .nothing-here-block No preview for this file type - diff --git a/app/views/projects/commits/_diff_warning.html.haml b/app/views/projects/commits/_diff_warning.html.haml deleted file mode 100644 index 05d516efa1..0000000000 --- a/app/views/projects/commits/_diff_warning.html.haml +++ /dev/null @@ -1,19 +0,0 @@ -.bs-callout.bs-callout-warning - %h4 - Too many changes. - .pull-right - - unless diff_hard_limit_enabled? - = link_to "Reload with full diff", url_for(params.merge(force_show_diff: true)), class: "btn btn-small btn-warning" - - - if current_controller?(:commit) or current_controller?(:merge_requests) - - if current_controller?(:commit) - = link_to "Plain diff", project_commit_path(@project, @commit, format: :diff), class: "btn btn-warning btn-small" - = link_to "Email patch", project_commit_path(@project, @commit, format: :patch), class: "btn btn-warning btn-small" - - elsif @merge_request && @merge_request.persisted? - = link_to "Plain diff", project_merge_request_path(@project, @merge_request, format: :diff), class: "btn btn-warning btn-small" - = link_to "Email patch", project_merge_request_path(@project, @merge_request, format: :patch), class: "btn btn-warning btn-small" - %p - To preserve performance only - %strong #{safe_diff_files(diffs).size} of #{diffs.size} - files displayed. - diff --git a/app/views/projects/commits/_diffs.html.haml b/app/views/projects/commits/_diffs.html.haml deleted file mode 100644 index 64d6a2f09c..0000000000 --- a/app/views/projects/commits/_diffs.html.haml +++ /dev/null @@ -1,23 +0,0 @@ -.row - .col-md-8 - = render 'projects/commits/diff_stats', diffs: diffs - .col-md-4 - %ul.nav.nav-tabs - %li.pull-right{class: params[:view] == 'parallel' ? 'active' : ''} - = link_to "Side-by-side Diff", url_for(view: 'parallel'), {id: "commit-diff-viewtype"} - %li.pull-right{class: params[:view] != 'parallel' ? 'active' : ''} - = link_to "Inline Diff", url_for(view: 'inline'), {id: "commit-diff-viewtype"} - -- if show_diff_size_warninig?(diffs) - = render 'projects/commits/diff_warning', diffs: diffs - -.files - - safe_diff_files(diffs).each_with_index do |diff, i| - = render 'projects/commits/diff_file', diff: diff, i: i, project: project - -- if @diff_timeout - .alert.alert-danger - %h4 - Failed to collect changes - %p - Maybe diff is really big and operation failed with timeout. Try to get diff localy diff --git a/app/views/projects/commits/_head.html.haml b/app/views/projects/commits/_head.html.haml index b636e8ffe1..a714f5f79e 100644 --- a/app/views/projects/commits/_head.html.haml +++ b/app/views/projects/commits/_head.html.haml @@ -1,19 +1,17 @@ %ul.nav.nav-tabs = nav_link(controller: [:commit, :commits]) do - = link_to 'Commits', project_commits_path(@project, @repository.root_ref) + = link_to namespace_project_commits_path(@project.namespace, @project, @repository.root_ref) do + Commits + %span.badge= number_with_precision(@repository.commit_count, precision: 0, delimiter: ',') = nav_link(controller: :compare) do - = link_to 'Compare', project_compare_index_path(@project, from: @repository.root_ref, to: @ref || @repository.root_ref) + = link_to 'Compare', namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: @ref || @repository.root_ref) = nav_link(html_options: {class: branches_tab_class}) do - = link_to project_branches_path(@project) do + = link_to namespace_project_branches_path(@project.namespace, @project) do Branches %span.badge.js-totalbranch-count= @repository.branches.size = nav_link(controller: :tags) do - = link_to project_tags_path(@project) do + = link_to namespace_project_tags_path(@project.namespace, @project) do Tags - %span.badge= @repository.tags.length - - = nav_link(controller: :repositories, action: :stats) do - = link_to stats_project_repository_path(@project) do - Stats + %span.badge.js-totaltags-count= @repository.tags.length diff --git a/app/views/projects/commits/_inline_commit.html.haml b/app/views/projects/commits/_inline_commit.html.haml index b36369b428..c03bc3f9df 100644 --- a/app/views/projects/commits/_inline_commit.html.haml +++ b/app/views/projects/commits/_inline_commit.html.haml @@ -1,8 +1,8 @@ %li.commit.inline-commit .commit-row-title - = link_to commit.short_id(8), project_commit_path(project, commit), class: "commit_short_id" + = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"   %span.str-truncated - = link_to_gfm commit.title, project_commit_path(project, commit.id), class: "commit-row-message" + = link_to_gfm commit.title, namespace_project_commit_path(project.namespace, project, commit.id), class: "commit-row-message" .pull-right #{time_ago_with_tooltip(commit.committed_date)} diff --git a/app/views/projects/commits/_parallel_view.html.haml b/app/views/projects/commits/_parallel_view.html.haml deleted file mode 100644 index 80f5be98f2..0000000000 --- a/app/views/projects/commits/_parallel_view.html.haml +++ /dev/null @@ -1,38 +0,0 @@ -/ Side-by-side diff view -- old_lines, new_lines = parallel_diff_lines(project, @commit, diff, file) -- num_lines = old_lines.length - -%div.text-file - %table - - num_lines.times do |index| - - new_line = new_lines[index] - - old_line = old_lines[index] - %tr.line_holder.parallel - -# For old line - - if old_line.type == :file_created - %td.old_line= old_line.num - %td.line_content.parallel= "File was created" - - elsif old_line.type == :deleted - %td.old_line.old= old_line.num - %td.line_content{class: "parallel noteable_line old #{old_line.code}", "line_code" => old_line.code}= old_line.content - - else old_line.type == :no_change - %td.old_line= old_line.num - %td.line_content.parallel= old_line.content - - -# For new line - - if new_line.type == :file_deleted - %td.new_line= new_line.num - %td.line_content.parallel= "File was deleted" - - elsif new_line.type == :added - %td.new_line.new= new_line.num - %td.line_content{class: "parallel noteable_line new #{new_line.code}", "line_code" => new_line.code}= new_line.content - - else new_line.type == :no_change - %td.new_line= new_line.num - %td.line_content.parallel= new_line.content - - - if @reply_allowed - - comments1 = @line_notes.select { |n| n.line_code == old_line.code }.sort_by(&:created_at) - - comments2 = @line_notes.select { |n| n.line_code == new_line.code }.sort_by(&:created_at) - - unless comments1.empty? and comments2.empty? - = render "projects/notes/diff_notes_with_reply_parallel", notes1: comments1, notes2: comments2 - diff --git a/app/views/projects/commits/_text_file.html.haml b/app/views/projects/commits/_text_file.html.haml deleted file mode 100644 index 756481c1b2..0000000000 --- a/app/views/projects/commits/_text_file.html.haml +++ /dev/null @@ -1,33 +0,0 @@ -- too_big = diff.diff.lines.count > Commit::DIFF_SAFE_LINES -- if too_big - %a.supp_diff_link Changes suppressed. Click to show - -%table.text-file{class: "#{'hide' if too_big}"} - - last_line = 0 - - each_diff_line(diff, index) do |line, type, line_code, line_new, line_old, raw_line| - - last_line = line_new - %tr.line_holder{ id: line_code, class: "#{type}" } - - if type == "match" - = render "projects/commits/diffs/match_line", {line: line, - line_old: line_old, line_new: line_new, bottom: false} - - else - %td.old_line - = link_to raw(type == "new" ? " " : line_old), "##{line_code}", id: line_code - - if @comments_allowed - = link_to_new_diff_note(line_code) - %td.new_line{data: {linenumber: line_new}} - = link_to raw(type == "old" ? " " : line_new) , "##{line_code}", id: line_code - %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line) - - - if @reply_allowed - - comments = @line_notes.select { |n| n.line_code == line_code }.sort_by(&:created_at) - - unless comments.empty? - = render "projects/notes/diff_notes_with_reply", notes: comments, line: line - - - if last_line > 0 - = render "projects/commits/diffs/match_line", {line: "", - line_old: last_line, line_new: last_line, bottom: true} - -- if diff.diff.blank? && diff_file_mode_changed?(diff) - .file-mode-changed - File mode changed diff --git a/app/views/projects/commits/show.atom.builder b/app/views/projects/commits/show.atom.builder index 32c82edb24..9211de72b1 100644 --- a/app/views/projects/commits/show.atom.builder +++ b/app/views/projects/commits/show.atom.builder @@ -1,15 +1,15 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.title "Recent commits to #{@project.name}:#{@ref}" - xml.link :href => project_commits_url(@project, @ref, format: :atom), :rel => "self", :type => "application/atom+xml" - xml.link :href => project_commits_url(@project, @ref), :rel => "alternate", :type => "text/html" - xml.id project_commits_url(@project, @ref) + xml.link :href => namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom), :rel => "self", :type => "application/atom+xml" + xml.link :href => namespace_project_commits_url(@project.namespace, @project, @ref), :rel => "alternate", :type => "text/html" + xml.id namespace_project_commits_url(@project.namespace, @project, @ref) xml.updated @commits.first.committed_date.strftime("%Y-%m-%dT%H:%M:%SZ") if @commits.any? @commits.each do |commit| xml.entry do - xml.id project_commit_url(@project, :id => commit.id) - xml.link :href => project_commit_url(@project, :id => commit.id) + 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.strftime("%Y-%m-%dT%H:%M:%SZ") xml.media :thumbnail, :width => "40", :height => "40", :url => avatar_icon(commit.author_email) diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml index 482a845558..7ea855e1a4 100644 --- a/app/views/projects/commits/show.html.haml +++ b/app/views/projects/commits/show.html.haml @@ -5,17 +5,15 @@ - if current_user && current_user.private_token .commits-feed-holder.hidden-xs.hidden-sm - = link_to project_commits_path(@project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Feed", class: 'btn' do - %i.icon-rss + = link_to namespace_project_commits_path(@project.namespace, @project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Feed", class: 'btn' do + %i.fa.fa-rss Commits feed %ul.breadcrumb.repo-breadcrumb = commits_breadcrumbs - %li.active - commits %div{id: dom_id(@project)} - #commits-list= render "commits" + #commits-list= render "commits", project: @project .clear = spinner diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml index da6157cf1b..dfb1dded9e 100644 --- a/app/views/projects/compare/_form.html.haml +++ b/app/views/projects/compare/_form.html.haml @@ -1,4 +1,4 @@ -= form_tag project_compare_index_path(@project), method: :post, class: 'form-inline' do += form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline' do .clearfix.append-bottom-20 - if params[:to] && params[:from] = link_to 'switch', {from: params[:to], to: params[:from]}, {class: 'commits-compare-switch has_tooltip', title: 'Switch base of comparison'} @@ -12,7 +12,7 @@ %span.input-group-addon to = text_field_tag :to, params[:to], class: "form-control"   - = submit_tag "Compare", class: "btn btn-create commits-compare-btn" + = button_tag "Compare", class: "btn btn-create commits-compare-btn" - if compare_to_mr_button? = link_to compare_mr_path, class: 'prepend-left-10 btn' do %strong Make a merge request diff --git a/app/views/projects/compare/show.html.haml b/app/views/projects/compare/show.html.haml index aa79d08509..214b5bd337 100644 --- a/app/views/projects/compare/show.html.haml +++ b/app/views/projects/compare/show.html.haml @@ -6,20 +6,8 @@ = render "form" - if @commits.present? - %div.panel.panel-default - .panel-heading - Commits (#{@commits.count}) - - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE - %ul.well-list - - Commit.decorate(@commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE)).each do |commit| - = render "projects/commits/inline_commit", commit: commit, project: @project - %li.warning-row.unstyled - other #{@commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE} commits hidden to prevent performance issues. - - else - %ul.well-list= render Commit.decorate(@commits), project: @project - - = render "projects/commits/diffs", diffs: @diffs, project: @project - + = render "projects/commits/commit_list" + = render "projects/diffs/diffs", diffs: @diffs, project: @project - else .light-well .center diff --git a/app/views/projects/create.js.haml b/app/views/projects/create.js.haml deleted file mode 100644 index 89710d3a09..0000000000 --- a/app/views/projects/create.js.haml +++ /dev/null @@ -1,13 +0,0 @@ -- if @project.saved? - - if @project.import? - :plain - location.href = "#{import_project_path(@project)}"; - - else - :plain - location.href = "#{project_path(@project)}"; -- else - :plain - $(".project-edit-errors").html("#{escape_javascript(render('errors'))}"); - $('.project-submit').enable(); - $('.save-project-loader').hide(); - $('.project-edit-container').show(); diff --git a/app/views/projects/deploy_keys/_deploy_key.html.haml b/app/views/projects/deploy_keys/_deploy_key.html.haml index 2b4f36fb4b..c577dfa8d5 100644 --- a/app/views/projects/deploy_keys/_deploy_key.html.haml +++ b/app/views/projects/deploy_keys/_deploy_key.html.haml @@ -1,24 +1,36 @@ %li .pull-right - if @available_keys.include?(deploy_key) - = link_to enable_project_deploy_key_path(@project, deploy_key), class: 'btn btn-small', method: :put do - %i.icon-plus + = link_to enable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: 'btn btn-sm', method: :put do + %i.fa.fa-plus Enable - else - - if deploy_key.projects.count > 1 - = link_to disable_project_deploy_key_path(@project, deploy_key), class: 'btn btn-small', method: :put do - %i.icon-off - Disable + - if deploy_key.destroyed_when_orphaned? && deploy_key.almost_orphaned? + = link_to 'Remove', disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), data: { confirm: 'You are going to remove deploy key. Are you sure?'}, method: :put, class: "btn btn-remove delete-key btn-sm pull-right" - else - = link_to 'Remove', project_deploy_key_path(@project, deploy_key), data: { confirm: 'You are going to remove deploy key. Are you sure?'}, method: :delete, class: "btn btn-remove delete-key btn-small pull-right" + = link_to disable_namespace_project_deploy_key_path(@project.namespace, @project, deploy_key), class: 'btn btn-sm', method: :put do + %i.fa.fa-power-off + Disable - - = link_to project_deploy_key_path(deploy_key.projects.include?(@project) ? @project : deploy_key.projects.first, deploy_key) do - %i.icon-key + - if project = project_for_deploy_key(deploy_key) + = link_to namespace_project_deploy_key_path(project.namespace, project, deploy_key) do + %i.fa.fa-key + %strong= deploy_key.title + - else + %i.fa.fa-key %strong= deploy_key.title + %p.light.prepend-top-10 - - deploy_key.projects.map(&:name_with_namespace).each do |project_name| - %span.label.label-gray.deploy-project-label= project_name + - if deploy_key.public? + %span.label.label-info.deploy-project-label + Public deploy key + + - deploy_key.projects.each do |project| + - if can?(current_user, :read_project, project) + %span.label.label-gray.deploy-project-label + = link_to namespace_project_path(project.namespace, project) do + = project.name_with_namespace + %small.pull-right Created #{time_ago_with_tooltip(deploy_key.created_at)} diff --git a/app/views/projects/deploy_keys/_form.html.haml b/app/views/projects/deploy_keys/_form.html.haml index 162ef05b36..91675b3738 100644 --- a/app/views/projects/deploy_keys/_form.html.haml +++ b/app/views/projects/deploy_keys/_form.html.haml @@ -1,5 +1,5 @@ %div - = form_for [@project, @key], url: project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal' } do |f| + = form_for [@project.namespace.becomes(Namespace), @project, @key], url: namespace_project_deploy_keys_path, html: { class: 'deploy-key-form form-horizontal' } do |f| -if @key.errors.any? .alert.alert-danger %ul @@ -19,5 +19,5 @@ .form-actions = f.submit 'Create', class: "btn-create btn" - = link_to "Cancel", project_deploy_keys_path(@project), class: "btn btn-cancel" + = link_to "Cancel", namespace_project_deploy_keys_path(@project.namespace, @project), class: "btn btn-cancel" diff --git a/app/views/projects/deploy_keys/index.html.haml b/app/views/projects/deploy_keys/index.html.haml index f50aeba337..472a13a852 100644 --- a/app/views/projects/deploy_keys/index.html.haml +++ b/app/views/projects/deploy_keys/index.html.haml @@ -1,8 +1,8 @@ %h3.page-title Deploy keys allow read-only access to the repository - = link_to new_project_deploy_key_path(@project), class: "btn btn-new pull-right", title: "New Deploy Key" do - %i.icon-plus + = link_to new_namespace_project_deploy_key_path(@project.namespace, @project), class: "btn btn-new pull-right", title: "New Deploy Key" do + %i.fa.fa-plus New Deploy Key %p.light @@ -20,13 +20,22 @@ = render @enabled_keys - if @enabled_keys.blank? .light-well - .nothing-here-block Create a #{link_to 'new deploy key', new_project_deploy_key_path(@project)} or add an existing one + .nothing-here-block Create a #{link_to 'new deploy key', new_namespace_project_deploy_key_path(@project.namespace, @project)} or add an existing one .col-md-6.available-keys - %h5 - %strong Deploy keys - from projects available to you - %ul.bordered-list - = render @available_keys - - if @available_keys.blank? - .light-well - .nothing-here-block Deploy keys from projects you have access to will be displayed here + - # If there are available public deploy keys but no available project deploy keys, only public deploy keys are shown. + - if @available_project_keys.any? || @available_public_keys.blank? + %h5 + %strong Deploy keys + from projects you have access to + %ul.bordered-list + = render @available_project_keys + - if @available_project_keys.blank? + .light-well + .nothing-here-block Deploy keys from projects you have access to will be displayed here + + - if @available_public_keys.any? + %h5 + %strong Public deploy keys + available to any project + %ul.bordered-list + = render @available_public_keys diff --git a/app/views/projects/deploy_keys/show.html.haml b/app/views/projects/deploy_keys/show.html.haml index c66e6bc69c..405b5bcd0d 100644 --- a/app/views/projects/deploy_keys/show.html.haml +++ b/app/views/projects/deploy_keys/show.html.haml @@ -5,9 +5,9 @@ created on = @key.created_at.stamp("Aug 21, 2011") .back-link - = link_to project_deploy_keys_path(@project) do + = link_to namespace_project_deploy_keys_path(@project.namespace, @project) do ← To keys list %hr %pre= @key.key .pull-right - = link_to 'Remove', project_deploy_key_path(@project, @key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn-remove btn delete-key" + = link_to 'Remove', namespace_project_deploy_key_path(@project.namespace, @project, @key), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn-remove btn delete-key" diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml new file mode 100644 index 0000000000..b49aee504f --- /dev/null +++ b/app/views/projects/diffs/_diffs.html.haml @@ -0,0 +1,23 @@ +.prepend-top-20.append-bottom-20 + .pull-right + .btn-group + = inline_diff_btn + = parallel_diff_btn + = render 'projects/diffs/stats', diffs: diffs + +- if show_diff_size_warning?(diffs) + = render 'projects/diffs/warning', diffs: diffs + +.files + - safe_diff_files(diffs).each_with_index do |diff_file, index| + = render 'projects/diffs/file', diff_file: diff_file, i: index, project: project + +- if @diff_timeout + .alert.alert-danger + %h4 + Failed to collect changes + %p + Maybe diff is really big and operation failed with timeout. Try to get diff locally + +:coffeescript + $('.files .diff-header').stick_in_parent(offset_top: $('.navbar').height()) diff --git a/app/views/projects/diffs/_file.html.haml b/app/views/projects/diffs/_file.html.haml new file mode 100644 index 0000000000..672a663532 --- /dev/null +++ b/app/views/projects/diffs/_file.html.haml @@ -0,0 +1,50 @@ +- blob = project.repository.blob_for_diff(@commit, diff_file.diff) +- return unless blob +- blob_diff_path = namespace_project_blob_diff_path(project.namespace, project, tree_join(@commit.id, diff_file.file_path)) +.diff-file{id: "diff-#{i}", data: {blob_diff_path: blob_diff_path }} + .diff-header{id: "file-path-#{hexdigest(diff_file.new_path || diff_file.old_path)}"} + - if diff_file.deleted_file + %span="#{diff_file.old_path} deleted" + + .diff-btn-group + - if @commit.parent_ids.present? + = view_file_btn(@commit.parent_id, diff_file, project) + - elsif diff_file.diff.submodule? + - submodule_item = project.repository.blob_at(@commit.id, diff_file.file_path) + = submodule_link(submodule_item, @commit.id) + - else + %span + - if diff_file.renamed_file + = "#{diff_file.old_path} renamed to #{diff_file.new_path}" + - else + = diff_file.new_path + - if diff_file.mode_changed? + %span.file-mode= "#{diff_file.diff.a_mode} → #{diff_file.diff.b_mode}" + + .diff-btn-group + - if blob.text? + = link_to '#', class: 'js-toggle-diff-comments btn btn-sm active has_tooltip', title: "Toggle comments for this file" do + %i.fa.fa-comments +   + + - if @merge_request && @merge_request.source_project + = edit_blob_link(@merge_request.source_project, + @merge_request.source_branch, diff_file.new_path, + after: ' ', from_merge_request_id: @merge_request.id) + + = view_file_btn(@commit.id, diff_file, project) + + .diff-content.diff-wrap-lines + -# Skipp all non non-supported blobs + - return unless blob.respond_to?('text?') + - if blob.text? + - if params[:view] == 'parallel' + = render "projects/diffs/parallel_view", diff_file: diff_file, project: project, blob: blob, index: i + - else + = render "projects/diffs/text_file", diff_file: diff_file, index: i + - elsif blob.image? + - old_file = project.repository.prev_blob_for_diff(@commit, diff_file) + = render "projects/diffs/image", diff_file: diff_file, old_file: old_file, file: blob, index: i + - else + .nothing-here-block No preview for this file type + diff --git a/app/views/projects/commits/_image.html.haml b/app/views/projects/diffs/_image.html.haml similarity index 88% rename from app/views/projects/commits/_image.html.haml rename to app/views/projects/diffs/_image.html.haml index 6d9ef5964d..058b71b21f 100644 --- a/app/views/projects/commits/_image.html.haml +++ b/app/views/projects/diffs/_image.html.haml @@ -1,3 +1,4 @@ +- diff = diff_file.diff - if diff.renamed_file || diff.new_file || diff.deleted_file .image %span.wrap @@ -9,7 +10,7 @@ %div.two-up.view %span.wrap .frame.deleted - %a{href: project_blob_path(@project, tree_join(@commit.parent_id, diff.old_path))} + %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(@commit.parent_id, diff.old_path))} %img{src: "data:#{old_file.mime_type};base64,#{Base64.encode64(old_file.data)}"} %p.image-info.hide %span.meta-filesize= "#{number_to_human_size old_file.size}" @@ -21,7 +22,7 @@ %span.meta-height %span.wrap .frame.added - %a{href: project_blob_path(@project, tree_join(@commit.id, diff.new_path))} + %a{href: namespace_project_blob_path(@project.namespace, @project, tree_join(@commit.id, diff.new_path))} %img{src: "data:#{file.mime_type};base64,#{Base64.encode64(file.data)}"} %p.image-info.hide %span.meta-filesize= "#{number_to_human_size file.size}" diff --git a/app/views/projects/commits/diffs/_match_line.html.haml b/app/views/projects/diffs/_match_line.html.haml similarity index 100% rename from app/views/projects/commits/diffs/_match_line.html.haml rename to app/views/projects/diffs/_match_line.html.haml diff --git a/app/views/projects/diffs/_match_line_parallel.html.haml b/app/views/projects/diffs/_match_line_parallel.html.haml new file mode 100644 index 0000000000..815df16aa4 --- /dev/null +++ b/app/views/projects/diffs/_match_line_parallel.html.haml @@ -0,0 +1,4 @@ +%td.old_line + %td.line_content.parallel.matched= line +%td.new_line + %td.line_content.parallel.matched= line diff --git a/app/views/projects/diffs/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml new file mode 100644 index 0000000000..75f3a80f0d --- /dev/null +++ b/app/views/projects/diffs/_parallel_view.html.haml @@ -0,0 +1,41 @@ +/ Side-by-side diff view +%div.text-file.diff-wrap-lines + %table + - parallel_diff(diff_file, index).each do |line| + - type_left = line[0] + - line_number_left = line[1] + - line_content_left = line[2] + - line_code_left = line[3] + - type_right = line[4] + - line_number_right = line[5] + - line_content_right = line[6] + - line_code_right = line[7] + + %tr.line_holder.parallel + - if type_left == 'match' + = render "projects/diffs/match_line_parallel", { line: line_content_left, + line_old: line_number_left, line_new: line_number_right } + - elsif type_left == 'old' || type_left.nil? + %td.old_line{id: line_code_left, class: "#{type_left}"} + = link_to raw(line_number_left), "##{line_code_left}", id: line_code_left + %td.line_content{class: "parallel noteable_line #{type_left} #{line_code_left}", "line_code" => line_code_left }= raw line_content_left + + - if type_right == 'new' + - new_line_class = 'new' + - new_line_code = line_code_right + - else + - new_line_class = nil + - new_line_code = line_code_left + + %td.new_line{id: new_line_code, class: "#{new_line_class}", data: { linenumber: line_number_right }} + = link_to raw(line_number_right), "##{new_line_code}", id: new_line_code + %td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code}", "line_code" => new_line_code}= raw line_content_right + + - if @reply_allowed + - comments_left, comments_right = organize_comments(type_left, type_right, line_code_left, line_code_right) + - if comments_left.present? || comments_right.present? + = render "projects/notes/diff_notes_with_reply_parallel", notes1: comments_left, notes2: comments_right + +- if diff_file.diff.diff.blank? && diff_file.mode_changed? + .file-mode-changed + File mode changed diff --git a/app/views/projects/commits/_diff_stats.html.haml b/app/views/projects/diffs/_stats.html.haml similarity index 74% rename from app/views/projects/commits/_diff_stats.html.haml rename to app/views/projects/diffs/_stats.html.haml index 846a1ee10e..1625930615 100644 --- a/app/views/projects/commits/_diff_stats.html.haml +++ b/app/views/projects/diffs/_stats.html.haml @@ -1,17 +1,14 @@ .js-toggle-container .commit-stat-summary Showing - %strong.cdark #{pluralize(diffs.count, "changed file")} + = link_to '#', class: 'js-toggle-button' do + %strong #{pluralize(diffs.count, "changed file")} - if current_controller?(:commit) - unless @commit.has_zero_stats? with %strong.cgreen #{@commit.stats.additions} additions and %strong.cred #{@commit.stats.deletions} deletions -   - = link_to '#', class: 'btn btn-small js-toggle-button' do - Show diff stats - %i.icon-chevron-down .file-stats.js-toggle-content.hide %ul.bordered-list - diffs.each_with_index do |diff, i| @@ -19,23 +16,23 @@ - if diff.deleted_file %span.deleted-file %a{href: "#diff-#{i}"} - %i.icon-minus + %i.fa.fa-minus = diff.old_path - elsif diff.renamed_file %span.renamed-file %a{href: "#diff-#{i}"} - %i.icon-minus + %i.fa.fa-minus = diff.old_path - = "->" + → = diff.new_path - elsif diff.new_file %span.new-file %a{href: "#diff-#{i}"} - %i.icon-plus + %i.fa.fa-plus = diff.new_path - else %span.edit-file %a{href: "#diff-#{i}"} - %i.icon-adjust + %i.fa.fa-adjust = diff.new_path diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml new file mode 100644 index 0000000000..e6dfbfd651 --- /dev/null +++ b/app/views/projects/diffs/_text_file.html.haml @@ -0,0 +1,36 @@ +- too_big = diff_file.diff_lines.count > Commit::DIFF_SAFE_LINES +- if too_big + %a.supp_diff_link Changes suppressed. Click to show + +%table.text-file{class: "#{'hide' if too_big}"} + - last_line = 0 + - diff_file.diff_lines.each_with_index do |line, index| + - type = line.type + - last_line = line.new_pos + - line_code = generate_line_code(diff_file.file_path, line) + - line_old = line.old_pos + %tr.line_holder{ id: line_code, class: "#{type}" } + - if type == "match" + = render "projects/diffs/match_line", {line: line.text, + line_old: line_old, line_new: line.new_pos, bottom: false} + - else + %td.old_line + = link_to raw(type == "new" ? " " : line_old), "##{line_code}", id: line_code + - if @comments_allowed && can?(current_user, :write_note, @project) + = link_to_new_diff_note(line_code) + %td.new_line{data: {linenumber: line.new_pos}} + = link_to raw(type == "old" ? " " : line.new_pos) , "##{line_code}", id: line_code + %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line.text) + + - 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: line.text + + - if last_line > 0 + = render "projects/diffs/match_line", {line: "", + line_old: last_line, line_new: last_line, bottom: true} + +- if diff_file.diff.blank? && diff_file.mode_changed? + .file-mode-changed + File mode changed diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml new file mode 100644 index 0000000000..47abbba2eb --- /dev/null +++ b/app/views/projects/diffs/_warning.html.haml @@ -0,0 +1,19 @@ +.alert.alert-warning + %h4 + Too many changes. + .pull-right + - unless diff_hard_limit_enabled? + = link_to "Reload with full diff", url_for(params.merge(force_show_diff: true)), class: "btn btn-sm btn-warning" + + - if current_controller?(:commit) or current_controller?(:merge_requests) + - if current_controller?(:commit) + = link_to "Plain diff", namespace_project_commit_path(@project.namespace, @project, @commit, format: :diff), class: "btn btn-warning btn-sm" + = link_to "Email patch", namespace_project_commit_path(@project.namespace, @project, @commit, format: :patch), class: "btn btn-warning btn-sm" + - elsif @merge_request && @merge_request.persisted? + = link_to "Plain diff", merge_request_path(@merge_request, format: :diff), class: "btn btn-warning btn-sm" + = link_to "Email patch", merge_request_path(@merge_request, format: :patch), class: "btn btn-warning btn-sm" + %p + To preserve performance only + %strong #{allowed_diff_size} of #{diffs.size} + files are displayed. + diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index d9acb68551..fbf04847e4 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -3,17 +3,17 @@ .project-edit-content %div %h3.page-title - Project settings: - %p.light Some settings, such as "Transfer Project", are hidden inside the danger area below. + Project settings %hr .panel-body - = form_for @project, remote: true, html: { class: "edit_project form-horizontal" } do |f| + = form_for [@project.namespace.becomes(Namespace), @project], remote: true, html: { multipart: true, class: "edit_project form-horizontal" }, 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, placeholder: "Example Project", class: "form-control" + = f.text_field :name, placeholder: "Example Project", class: "form-control", id: "project_name_edit" .form-group @@ -31,14 +31,11 @@ = render "visibility_level", f: f, visibility_level: @project.visibility_level, can_change_visibility_level: can?(current_user, :change_visibility_level, @project) - %fieldset.features - %legend - Tags: - .form-group - = f.label :tag_list, "Tags", class: 'control-label' - .col-sm-10 - = f.text_field :tag_list, maxlength: 2000, class: "form-control" - %p.hint Separate tags with commas. + .form-group + = f.label :tag_list, "Tags", class: 'control-label' + .col-sm-10 + = f.text_field :tag_list, maxlength: 2000, class: "form-control" + %p.help-block Separate tags with commas. %fieldset.features %legend @@ -50,15 +47,6 @@ = f.check_box :issues_enabled %span.descr Lightweight issue tracking system for this project - - if Project.issues_tracker.values.count > 1 - .form-group - = f.label :issues_tracker, "Issues tracker", class: 'control-label' - .col-sm-10= f.select(:issues_tracker, project_issues_trackers(@project.issues_tracker), {}, { disabled: !@project.issues_enabled }) - - .form-group - = f.label :issues_tracker_id, "Project name or id in issues tracker", class: 'control-label' - .col-sm-10= f.text_field :issues_tracker_id, disabled: !@project.can_have_issues_tracker_id?, class: 'form-control' - .form-group = f.label :merge_requests_enabled, "Merge Requests", class: 'control-label' .col-sm-10 @@ -80,111 +68,139 @@ = f.check_box :snippets_enabled %span.descr Share code pastes with others out of git repository + %fieldset.features + %legend + Project avatar: + .form-group + .col-sm-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 ... +   + %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.js-toggle-container - .centered-light-block - %h3 - %i.icon-warning-sign - Dangerous settings - - %p Project settings below may result in data loss! - = link_to '#', class: 'btn js-toggle-button' do - Show them to me - %i.icon-chevron-down - - .js-toggle-content.hide - - if can? current_user, :archive_project, @project - .panel.panel-default.panel.panel-warning + .danger-settings + - if can? current_user, :archive_project, @project + - if @project.archived? + .panel.panel-success .panel-heading - - if @project.archived? - Unarchive project - - else - Archive project + Unarchive project .panel-body - - if @project.archived? - %p - Unarchiving the project will mark its repository as active. - %br - The project can be committed to. - %br - %strong Once active this project shows up in the search and on the dashboard. - = link_to 'Unarchive', unarchive_project_path(@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-remove" - - else - %p - Archiving the project will mark its repository as read-only. - %br - It is hidden from the dashboard and doesn't show up in searches. - %br - %strong Archived projects cannot be committed to! - = link_to 'Archive', archive_project_path(@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" + %p + Unarchiving the project will mark its repository as active. + %br + The project can be committed to. + %br + %strong Once active this project shows up in the search and on the dashboard. + = link_to 'Unarchive', 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 archive a project + .panel.panel-warning + .panel-heading + Archive project + .panel-body + %p + Archiving the project will mark its repository as read-only. + %br + It is hidden from the dashboard and doesn't show up in searches. + %br + %strong Archived projects cannot be committed to! + = link_to 'Archive', 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 + .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, placeholder: "Example Project", class: "form-control" + .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' + %span.input-group-addon .git + %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', 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, html: { class: 'form-horizontal' }) do |f| + = 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 - = f.label :path, class: 'control-label' do - %span Path - .col-sm-9 + = label_tag :new_namespace_id, nil, class: 'control-label' do + %span Namespace + .col-sm-10 .form-group - .input-group - = f.text_field :path, class: 'form-control' - %span.input-group-addon .git + = select_tag :new_namespace_id, namespaces_options(@project.namespace_id), { prompt: 'Choose a project namespace', class: 'select2' } %ul - %li Be careful. Renaming a project's repository can have unintended side effects. + %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. .form-actions - = f.submit 'Rename', class: "btn btn-warning" + = f.submit 'Transfer', 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 can?(current_user, :change_namespace, @project) - .panel.panel-default.panel.panel-danger - .panel-heading Transfer project - .errors-holder - .panel-body - = form_for(@project, url: transfer_project_path(@project), method: :put, remote: true, html: { class: 'transfer-project form-horizontal' }) do |f| - .form-group - = f.label :namespace_id, class: 'control-label' do - %span Namespace - .col-sm-10 - .form-group - = f.select :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. - .form-actions - = f.submit 'Transfer', class: "btn btn-remove" - - else - .nothing-here-block Only the project owner can transfer a project - - - if can?(current_user, :remove_project, @project) - .panel.panel-default.panel.panel-danger - .panel-heading Remove project - .panel-body + - 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, html: { 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! - = link_to 'Remove project', @project, data: { confirm: remove_project_message(@project) }, method: :delete, class: "btn btn-remove" - - else - .nothing-here-block Only project owner can remove a project + = link_to 'Remove project', '#', class: "btn btn-remove js-confirm-danger", data: { "confirm-danger-message" => remove_project_message(@project) } + - else + .nothing-here-block Only project owner can remove a project .save-project-loader.hide .center %h2 - %i.icon-spinner.icon-spin + %i.fa.fa-spinner.fa-spin 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/edit_tree/_diff.html.haml b/app/views/projects/edit_tree/_diff.html.haml deleted file mode 100644 index cf044feb9a..0000000000 --- a/app/views/projects/edit_tree/_diff.html.haml +++ /dev/null @@ -1,13 +0,0 @@ -%table.text-file - - each_diff_line(diff, 1) do |line, type, line_code, line_new, line_old, raw_line| - %tr.line_holder{ id: line_code, class: "#{type}" } - - if type == "match" - %td.old_line= "..." - %td.new_line= "..." - %td.line_content.matched= line - - else - %td.old_line - = link_to raw(type == "new" ? " " : line_old), "##{line_code}", id: line_code - %td.new_line= link_to raw(type == "old" ? " " : line_new) , "##{line_code}", id: line_code - %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line) - diff --git a/app/views/projects/edit_tree/preview.html.haml b/app/views/projects/edit_tree/preview.html.haml deleted file mode 100644 index 87ce5dc31d..0000000000 --- a/app/views/projects/edit_tree/preview.html.haml +++ /dev/null @@ -1,26 +0,0 @@ -.diff-file - .diff-content - - if gitlab_markdown?(@blob.name) - .file-content.wiki - = preserve do - = markdown(@content) - - elsif markup?(@blob.name) - .file-content.wiki - = raw render_markup(@blob.name, @content) - - else - .file-content.code - - unless @diff.empty? - %table.text-file - - @diff.each do |line, type, line_code, line_new, line_old, raw_line| - %tr.line_holder{ id: line_code, class: "#{type}" } - - if type == "match" - %td.old_line= "..." - %td.new_line= "..." - %td.line_content.matched= line - - else - %td.old_line - = link_to raw(type == "new" ? " " : line_old), "##{line_code}", id: line_code - %td.new_line= link_to raw(type == "old" ? " " : line_new) , "##{line_code}", id: line_code - %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line) - - else - .nothing-here-block No changes. diff --git a/app/views/projects/edit_tree/show.html.haml b/app/views/projects/edit_tree/show.html.haml deleted file mode 100644 index 05050e7df7..0000000000 --- a/app/views/projects/edit_tree/show.html.haml +++ /dev/null @@ -1,81 +0,0 @@ -.file-editor - %ul.nav.nav-tabs.js-edit-mode - %li.active - = link_to 'Edit', '#editor' - %li - = link_to editing_preview_title(@blob.name), '#preview', 'data-preview-url' => preview_project_edit_tree_path(@project, @id) - - = form_tag(project_edit_tree_path(@project, @id), method: :put, class: "form-horizontal") do - .file-holder.file - .file-title - %i.icon-file - %span.file_name - %span.monospace.light #{@ref}: - = @path - %span.options - .btn-group.tree-btn-group - = link_to "Cancel", @after_edit_path, class: "btn btn-tiny btn-cancel", data: { confirm: leave_edit_message } - .file-content.code - %pre.js-edit-mode-pane#editor - .js-edit-mode-pane#preview.hide - .center - %h2 - %i.icon-spinner.icon-spin - - .form-group.commit_message-group - = label_tag 'commit_message', class: "control-label" do - Commit message - .col-sm-10 - = render 'shared/commit_message_container', {textarea: text_area_tag('commit_message', '', - placeholder: "Update #{@blob.name}", required: true, rows: 3, class: 'form-control')} - .form-actions - = hidden_field_tag 'last_commit', @last_commit - = hidden_field_tag 'content', '', id: "file-content" - = hidden_field_tag 'from_merge_request_id', params[:from_merge_request_id] - .commit-button-annotation - = button_tag "Commit changes", class: 'btn commit-btn js-commit-button btn-primary' - .message - to branch - %strong= @ref - = link_to "Cancel", @after_edit_path, class: "btn btn-cancel", data: { confirm: leave_edit_message} - -:javascript - ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace") - var ace_mode = "#{@blob.language.try(:ace_mode)}"; - var editor = ace.edit("editor"); - editor.setValue("#{escape_javascript(@blob.data)}"); - if (ace_mode) { - editor.getSession().setMode('ace/mode/' + ace_mode); - } - - disableButtonIfEmptyField("#commit_message", ".js-commit-button"); - - $(".js-commit-button").click(function(){ - $("#file-content").val(editor.getValue()); - $(".file-editor form").submit(); - }); - - var editModePanes = $('.js-edit-mode-pane'), - editModeLinks = $('.js-edit-mode a'); - - editModeLinks.click(function(event) { - event.preventDefault(); - - var currentLink = $(this), - paneId = currentLink.attr('href'), - currentPane = editModePanes.filter(paneId); - - editModeLinks.parent().removeClass('active hover'); - currentLink.parent().addClass('active hover'); - editModePanes.hide(); - - if (paneId == '#preview') { - currentPane.fadeIn(200); - $.post(currentLink.data('preview-url'), { content: editor.getValue() }, function(response) { - currentPane.empty().append(response); - }) - } else { - currentPane.fadeIn(200); - editor.focus() - } - }) diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 97dc73bce1..49806ceaa9 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -1,28 +1,43 @@ +- if current_user && can?(current_user, :download_code, @project) + = render 'shared/no_ssh' + = render 'shared/no_password' + = render "home_panel" +.center.well + %h3 + The repository for this project is empty + %h4 + You can + = link_to namespace_project_new_blob_path(@project.namespace, @project, 'master'), class: 'btn btn-new btn-lg' do + add a file +  or do a push via the command line. + +%h4 + %strong Command line instructions %div.git-empty %fieldset - %legend Git global setup: + %legend Git global setup %pre.dark :preserve git config --global user.name "#{git_user_name}" git config --global user.email "#{git_user_email}" %fieldset - %legend Create Repository + %legend Create a new repository %pre.dark :preserve mkdir #{@project.path} cd #{@project.path} git init - touch README - git add README - git commit -m 'first commit' + touch README.md + git add README.md + git commit -m "first commit" git remote add origin #{ content_tag(:span, default_url_to_repo, class: 'clone')} git push -u origin master %fieldset - %legend Existing Git Repo? + %legend Push an existing Git repository %pre.dark :preserve cd existing_git_repo @@ -31,4 +46,4 @@ - if can? current_user, :remove_project, @project .prepend-top-20 - = link_to 'Remove project', @project, data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right" + = link_to 'Remove project', [@project.namespace.becomes(Namespace), @project], data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right" diff --git a/app/views/projects/fork.html.haml b/app/views/projects/fork.html.haml deleted file mode 100644 index bdd75de447..0000000000 --- a/app/views/projects/fork.html.haml +++ /dev/null @@ -1,19 +0,0 @@ -.alert.alert-danger.alert-block - %h4 - %i.icon-code-fork - Fork Error! - %p - You tried to fork - = link_to_project @project - but it failed for the following reason: - - - - if @forked_project && @forked_project.errors.any? - %p - – - = @forked_project.errors.full_messages.first - - %p - = link_to fork_project_path(@project), title: "Fork", class: "btn", method: "POST" do - %i.icon-code-fork - Try to Fork again diff --git a/app/views/projects/forks/error.html.haml b/app/views/projects/forks/error.html.haml new file mode 100644 index 0000000000..8eb4f79597 --- /dev/null +++ b/app/views/projects/forks/error.html.haml @@ -0,0 +1,20 @@ +- if @forked_project && !@forked_project.saved? + .alert.alert-danger.alert-block + %h4 + %i.fa.fa-code-fork + Fork Error! + %p + You tried to fork + = link_to_project @project + but it failed for the following reason: + + + - if @forked_project && @forked_project.errors.any? + %p + – + = @forked_project.errors.full_messages.first + + %p + = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork", class: "btn" do + %i.fa.fa-code-fork + Try to Fork again diff --git a/app/views/projects/forks/new.html.haml b/app/views/projects/forks/new.html.haml new file mode 100644 index 0000000000..5a6c46f320 --- /dev/null +++ b/app/views/projects/forks/new.html.haml @@ -0,0 +1,39 @@ +%h3.page-title Fork project +%p.lead + Click to fork the project to a user or group +%hr + +.fork-namespaces + - @namespaces.in_groups_of(6, false) do |group| + .row + - group.each do |namespace| + .col-md-2.col-sm-3 + - if fork = namespace.find_fork_of(@project) + .thumbnail.fork-exists-thumbnail + = link_to project_path(fork), title: "Visit project fork", class: 'has_tooltip' do + = image_tag namespace_icon(namespace, 200) + .caption + %h4=namespace.human_name + %p + = namespace.path + - else + .thumbnail.fork-thumbnail + = link_to namespace_project_fork_path(@project.namespace, @project, namespace_key: namespace.id), title: "Fork here", method: "POST", class: 'has_tooltip' do + = image_tag namespace_icon(namespace, 200) + .caption + %h4=namespace.human_name + %p + = namespace.path + + %p.light + Fork is a copy of a project repository. + %br + Forking a repository allows you to do changes without affecting the original project. + +.save-project-loader.hide + .center + %h2 + %i.fa.fa-spinner.fa-spin + Forking repository + %p Please wait a moment, this page will automatically refresh when ready. + diff --git a/app/views/projects/go_import.html.haml b/app/views/projects/go_import.html.haml new file mode 100644 index 0000000000..87ac75a350 --- /dev/null +++ b/app/views/projects/go_import.html.haml @@ -0,0 +1,5 @@ +!!! 5 +%html + %head + - web_url = [Gitlab.config.gitlab.url, @namespace, @id].join('/') + %meta{name: "go-import", content: "#{web_url.split('://')[1]} git #{web_url}.git"} diff --git a/app/views/projects/graphs/_head.html.haml b/app/views/projects/graphs/_head.html.haml new file mode 100644 index 0000000000..9383df1330 --- /dev/null +++ b/app/views/projects/graphs/_head.html.haml @@ -0,0 +1,5 @@ +%ul.nav.nav-tabs + = nav_link(action: :show) do + = link_to 'Contributors', namespace_project_graph_path + = nav_link(action: :commits) do + = link_to 'Commits', commits_namespace_project_graph_path diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml new file mode 100644 index 0000000000..78b4c1923d --- /dev/null +++ b/app/views/projects/graphs/commits.html.haml @@ -0,0 +1,85 @@ += render 'head' + +%p.lead + Commit statistics for + %strong #{@repository.root_ref} + #{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')} + +.row + .col-md-6 + %ul + %li + %p.lead + %strong #{@commits_graph.commits.size} + commits during + %strong #{@commits_graph.duration} + days + %li + %p.lead + Average + %strong #{@commits_graph.commit_per_day} + commits per day + %li + %p.lead + Contributed by + %strong #{@commits_graph.authors} + authors + .col-md-6 + %div + %p.slead + Commits per day of month + %canvas#month-chart{width: 800, height: 400} +.row + .col-md-6 + %div + %p.slead + Commits per day hour (UTC) + %canvas#hour-chart{width: 800, height: 400} + .col-md-6 + %div + %p.slead + Commits per weekday + %canvas#weekday-chart{width: 800, height: 400} + +:coffeescript + data = { + labels : #{@commits_per_time.keys.to_json}, + datasets : [{ + fillColor : "rgba(220,220,220,0.5)", + strokeColor : "rgba(220,220,220,1)", + pointColor : "rgba(220,220,220,1)", + pointStrokeColor : "#EEE", + data : #{@commits_per_time.values.to_json} + }] + } + + ctx = $("#hour-chart").get(0).getContext("2d"); + new Chart(ctx).Line(data,{"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2}) + + data = { + labels : #{@commits_per_week_days.keys.to_json}, + datasets : [{ + fillColor : "rgba(220,220,220,0.5)", + strokeColor : "rgba(220,220,220,1)", + pointColor : "rgba(220,220,220,1)", + pointStrokeColor : "#EEE", + data : #{@commits_per_week_days.values.to_json} + }] + } + + ctx = $("#weekday-chart").get(0).getContext("2d"); + new Chart(ctx).Line(data,{"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2}) + + data = { + labels : #{@commits_per_month.keys.to_json}, + datasets : [{ + fillColor : "rgba(220,220,220,0.5)", + strokeColor : "rgba(220,220,220,1)", + pointColor : "rgba(220,220,220,1)", + pointStrokeColor : "#EEE", + data : #{@commits_per_month.values.to_json} + }] + } + + ctx = $("#month-chart").get(0).getContext("2d"); + new Chart(ctx).Line(data, {"scaleOverlay": true, responsive: true, pointHitDetectionRadius: 2}) diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml index 8e4548f26d..e3d5094ddc 100644 --- a/app/views/projects/graphs/show.html.haml +++ b/app/views/projects/graphs/show.html.haml @@ -1,17 +1,13 @@ += render 'head' .loading-graph .center %h3.page-title - %i.icon-spinner.icon-spin + %i.fa.fa-spinner.fa-spin Building repository graph. - %p Please wait a moment, this page will automatically refresh when ready. + %p.slead Please wait a moment, this page will automatically refresh when ready. -.stat-graph +.stat-graph.hide .header.clearfix - .pull-right - %select - %option{:value => "commits"} Commits - %option{:value => "additions"} Additions - %option{:value => "deletions"} Deletions %h3#date_header.page-title %p.light Commits to #{@project.default_branch}, excluding merge commits. Limited by 6,000 commits @@ -21,15 +17,21 @@ #contributors.clearfix %ol.contributors-list.clearfix -:javascript - $(".stat-graph").hide(); - $.ajax({ + +:coffeescript + $.ajax type: "GET", url: location.href, - complete: function() { + success: (data) -> + graph = new ContributorsStatGraph() + graph.init(data) + + $("#brush_change").change -> + graph.change_date_header() + graph.redraw_authors() + $(".stat-graph").fadeIn(); $(".loading-graph").hide(); - }, - dataType: "script" - }); + dataType: "json" + diff --git a/app/views/projects/graphs/show.js.haml b/app/views/projects/graphs/show.js.haml deleted file mode 100644 index dcf6cacdce..0000000000 --- a/app/views/projects/graphs/show.js.haml +++ /dev/null @@ -1,19 +0,0 @@ -- if @success - :plain - controller = new ContributorsStatGraph - controller.init(#{@log}) - - $("select").change( function () { - var field = $(this).val() - controller.set_current_field(field) - controller.redraw_master() - controller.redraw_authors() - }) - - $("#brush_change").change( function () { - controller.change_date_header() - controller.redraw_authors() - }) -- else - :plain - $('.stat-graph').replaceWith('
    Failed to load graph
    ') diff --git a/app/views/projects/hooks/index.html.haml b/app/views/projects/hooks/index.html.haml index 9a003c87f6..bbaddba31b 100644 --- a/app/views/projects/hooks/index.html.haml +++ b/app/views/projects/hooks/index.html.haml @@ -7,7 +7,7 @@ %hr.clearfix -= form_for [@project, @hook], as: :hook, url: project_hooks_path(@project), html: { class: 'form-horizontal' } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @hook], as: :hook, url: namespace_project_hooks_path(@project.namespace, @project), html: { class: 'form-horizontal' } do |f| -if @hook.errors.any? .alert.alert-danger - @hook.errors.full_messages.each do |msg| @@ -58,8 +58,8 @@ - @hooks.each do |hook| %li .pull-right - = link_to 'Test Hook', test_project_hook_path(@project, hook), class: "btn btn-small btn-grouped" - = link_to 'Remove', project_hook_path(@project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-small btn-grouped" + = link_to 'Test Hook', test_namespace_project_hook_path(@project.namespace, @project, hook), class: "btn btn-sm btn-grouped" + = link_to 'Remove', namespace_project_hook_path(@project.namespace, @project, hook), data: { confirm: 'Are you sure?'}, method: :delete, class: "btn btn-remove btn-sm btn-grouped" .clearfix %span.monospace= hook.url %p diff --git a/app/views/projects/import.html.haml b/app/views/projects/import.html.haml deleted file mode 100644 index 9efb1658c2..0000000000 --- a/app/views/projects/import.html.haml +++ /dev/null @@ -1,30 +0,0 @@ -- if @project.import_in_progress? - .save-project-loader - .center - %h2 - %i.icon-spinner.icon-spin - Import in progress. - %p.monospace git clone --bare #{@project.import_url} - %p Please wait while we import the repository for you. Refresh at will. - :javascript - new ProjectImport(); - -- elsif @project.import_failed? - .save-project-loader - .center - %h2 - Import failed. Retry? - %hr - - if can?(current_user, :admin_project, @project) - = form_for @project, url: retry_import_project_path(@project), method: :put, html: { class: 'form-horizontal' } do |f| - .form-group.import-url-data - = f.label :import_url, class: 'control-label' do - %span Import existing repo - .col-sm-10 - = f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git' - .bs-callout.bs-callout-info - This url must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git. - %br - The import will time out after 4 minutes. For big repositories, use a clone/push combination. - .form-actions - = f.submit 'Retry import', class: "btn btn-create", tabindex: 4 diff --git a/app/views/projects/imports/new.html.haml b/app/views/projects/imports/new.html.haml new file mode 100644 index 0000000000..934b6b8c01 --- /dev/null +++ b/app/views/projects/imports/new.html.haml @@ -0,0 +1,21 @@ +%h3.page-title + - if @project.import_failed? + Import failed. Retry? + - else + Import repository + +%hr + += form_for @project, url: namespace_project_import_path(@project.namespace, @project), method: :post, html: { class: 'form-horizontal' } do |f| + .form-group.import-url-data + = f.label :import_url, class: 'control-label' do + %span Import existing git repo + .col-sm-10 + = f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git' + .well.prepend-top-20 + This URL must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git. + %br + The import will time out after 4 minutes. For big repositories, use a clone/push combination. + For SVN repositories, check #{link_to "this migrating from SVN doc.", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"} + .form-actions + = f.submit 'Start import', class: "btn btn-create", tabindex: 4 diff --git a/app/views/projects/imports/show.html.haml b/app/views/projects/imports/show.html.haml new file mode 100644 index 0000000000..2d1fdafed2 --- /dev/null +++ b/app/views/projects/imports/show.html.haml @@ -0,0 +1,9 @@ +.save-project-loader + .center + %h2 + %i.fa.fa-spinner.fa-spin + Import in progress. + %p.monospace git clone --bare #{hidden_pass_url(@project.import_url)} + %p Please wait while we import the repository for you. Refresh at will. + :javascript + new ProjectImport(); diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml new file mode 100644 index 0000000000..288b48f458 --- /dev/null +++ b/app/views/projects/issues/_discussion.html.haml @@ -0,0 +1,33 @@ +- content_for :note_actions do + - if can?(current_user, :modify_issue, @issue) + - if @issue.closed? + = link_to 'Reopen Issue', issue_path(@issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen js-note-target-reopen", title: 'Reopen Issue' + - else + = link_to 'Close Issue', issue_path(@issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close js-note-target-close", title: "Close Issue" +.row + %section.col-md-9 + .votes-holder.pull-right + #votes= render 'votes/votes_block', votable: @issue + .participants + %span= pluralize(@issue.participants(current_user).count, 'participant') + - @issue.participants(current_user).each do |participant| + = link_to_member(@project, participant, name: false, size: 24) + .voting_notes#notes= render "projects/notes/notes_with_form" + %aside.col-md-3 + .issuable-affix + .clearfix + %span.slead.has_tooltip{:"data-original-title" => 'Cross-project reference'} + = cross_project_reference(@project, @issue) + %hr + .context + = render partial: 'issue_context', locals: { issue: @issue } + + - if @issue.labels.any? + .issuable-context-title + %label Labels + .issue-show-labels + - @issue.labels.each do |label| + = link_to namespace_project_issues_path(@project.namespace, @project, label_name: label.name) do + = render_colored_label(label) + = link_to '#aside', class: 'show-aside' do + %i.fa.fa-angle-left diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml index b2a8e8e091..7d7217eb2a 100644 --- a/app/views/projects/issues/_form.html.haml +++ b/app/views/projects/issues/_form.html.haml @@ -1,67 +1,9 @@ %div.issue-form-holder - %h3.page-title= @issue.new_record? ? "New Issue" : "Edit Issue ##{@issue.iid}" + %h3.page-title= @issue.new_record? ? "Create Issue" : "Edit Issue ##{@issue.iid}" %hr - - if @repository.exists? && !@repository.empty? && @repository.contribution_guide && !@issue.persisted? - - contribution_guide_url = project_blob_path(@project, tree_join(@repository.root_ref, @repository.contribution_guide.name)) - .row - .col-sm-10.col-sm-offset-2 - .alert.alert-info - = "Please review the #{link_to "guidelines for contribution", contribution_guide_url} to this repository.".html_safe - = form_for [@project, @issue], html: { class: 'form-horizontal issue-form' } do |f| - -if @issue.errors.any? - .row - .col-sm-10.col-sm-offset-2 - .alert.alert-danger - - @issue.errors.full_messages.each do |msg| - %span= msg - %br - .form-group - = f.label :title, class: 'control-label' do - %strong= 'Title *' - .col-sm-10 - = f.text_field :title, maxlength: 255, class: "form-control js-gfm-input", autofocus: true, required: true - .form-group - = f.label :description, 'Description', class: 'control-label' - .col-sm-10 - = f.text_area :description, class: 'form-control js-gfm-input markdown-area', rows: 14 - .col-sm-12.hint - .pull-left Issues are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}. - .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. - .clearfix - .error-alert - %hr - .form-group - .issue-assignee - = f.label :assignee_id, class: 'control-label' do - %i.icon-user - Assign to - .col-sm-10 - = project_users_select_tag('issue[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @issue.assignee_id) -   - = link_to 'Assign to me', '#', class: 'btn assign-to-me-link' - .form-group - .issue-milestone - = f.label :milestone_id, class: 'control-label' do - %i.icon-time - Milestone - .col-sm-10= f.select(:milestone_id, milestone_options(@issue), { include_blank: "Select milestone" }, {class: 'select2'}) - - .form-group - = f.label :label_ids, class: 'control-label' do - %i.icon-tag - Labels - .col-sm-10 - = f.collection_select :label_ids, @project.labels.all, :id, :name, { selected: @issue.label_ids }, multiple: true, class: 'select2' - - .form-actions - - if @issue.new_record? - = f.submit 'Submit new issue', class: "btn btn-create" - -else - = f.submit 'Save changes', class: "btn-save btn" - - - cancel_path = @issue.new_record? ? project_issues_path(@project) : project_issue_path(@project, @issue) - = link_to "Cancel", cancel_path, class: 'btn btn-cancel' + = form_for [@project.namespace.becomes(Namespace), @project, @issue], html: { class: 'form-horizontal issue-form gfm-form' } do |f| + = render 'projects/issuable_form', f: f, issuable: @issue :javascript $('.assign-to-me-link').on('click', function(e){ @@ -69,4 +11,4 @@ e.preventDefault(); }); - window.project_image_path_upload = "#{upload_image_project_path @project}"; + window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; diff --git a/app/views/projects/issues/_head.html.haml b/app/views/projects/issues/_head.html.haml deleted file mode 100644 index dad547d4eb..0000000000 --- a/app/views/projects/issues/_head.html.haml +++ /dev/null @@ -1,36 +0,0 @@ -%ul.nav.nav-tabs - = nav_link(controller: :issues) do - = link_to project_issues_path(@project), class: "tab" do - Browse Issues - = nav_link(controller: :milestones) do - = link_to 'Milestones', project_milestones_path(@project), class: "tab" - = nav_link(controller: :labels) do - = link_to 'Labels', project_labels_path(@project), class: "tab" - - - if current_controller?(:milestones) - %li.pull-right - %button.btn.btn-default.sidebar-expand-button - %i.icon.icon-list - - - if current_controller?(:issues) - - if current_user - %li - = link_to project_issues_path(@project, :atom, { private_token: current_user.private_token }) do - %i.icon-rss - - %li.pull-right - .pull-right - %button.btn.btn-default.sidebar-expand-button - %i.icon.icon-list - = form_tag project_issues_path(@project), method: :get, id: "issue_search_form", class: 'pull-left issue-search-form' do - .append-right-10.hidden-xs.hidden-sm - = search_field_tag :issue_search, nil, { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300' } - = hidden_field_tag :state, params['state'] - = hidden_field_tag :scope, params['scope'] - = hidden_field_tag :assignee_id, params['assignee_id'] - = hidden_field_tag :milestone_id, params['milestone_id'] - = hidden_field_tag :label_id, params['label_id'] - - if can? current_user, :write_issue, @project - = link_to new_project_issue_path(@project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do - %i.icon-plus - New Issue diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml index db28b83118..998e74d12c 100644 --- a/app/views/projects/issues/_issue.html.haml +++ b/app/views/projects/issues/_issue.html.haml @@ -1,46 +1,52 @@ -%li{ id: dom_id(issue), class: issue_css_classes(issue), url: project_issue_path(issue.project, issue) } +%li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue) } - if controller.controller_name == 'issues' .issue-check = check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue", disabled: !can?(current_user, :modify_issue, issue) .issue-title - %span.light= "##{issue.iid}" %span.str-truncated - = link_to_gfm issue.title, project_issue_path(issue.project, issue), class: "row_title" - - if issue.closed? - %small.pull-right - = "CLOSED" + = link_to_gfm issue.title, issue_path(issue), class: "row_title" + .pull-right.light + - if issue.closed? + %span + CLOSED + - note_count = issue.notes.user.count + - if note_count > 0 +   + %span + %i.fa.fa-comments + = note_count .issue-info + = link_to "##{issue.iid}", issue_path(issue), class: "light" - if issue.assignee assigned to #{link_to_member(@project, issue.assignee)} - - else - unassigned - if issue.votes_count > 0 = render 'votes/votes_inline', votable: issue - - if issue.notes.any? - %span - %i.icon-comments - = issue.notes.count - if issue.milestone %span - %i.icon-time + %i.fa.fa-clock-o = issue.milestone.title - .pull-right + - if issue.tasks? + %span.task-status + = issue.task_status + + .pull-right.issue-updated-at %small updated #{time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_update_ago')} .issue-labels - issue.labels.each do |label| - = render_colored_label(label) + = link_to namespace_project_issues_path(issue.project.namespace, issue.project, label_name: label.name) do + = render_colored_label(label) .issue-actions - if can? current_user, :modify_issue, issue - if issue.closed? - = link_to 'Reopen', project_issue_path(issue.project, issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-small btn-grouped reopen_issue btn-reopen", remote: true + = link_to 'Reopen', issue_path(issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-sm btn-grouped reopen_issue btn-reopen", remote: true - else - = link_to 'Close', project_issue_path(issue.project, issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-small btn-grouped close_issue btn-close", remote: true - = link_to edit_project_issue_path(issue.project, issue), class: "btn btn-small edit-issue-link btn-grouped" do - %i.icon-edit + = link_to 'Close', issue_path(issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-sm btn-grouped close_issue btn-close", remote: true + = link_to edit_namespace_project_issue_path(issue.project.namespace, issue.project, issue), class: "btn btn-sm edit-issue-link btn-grouped" do + %i.fa.fa-pencil-square-o Edit diff --git a/app/views/projects/issues/_issue_context.html.haml b/app/views/projects/issues/_issue_context.html.haml index d7987f43fb..9228074d83 100644 --- a/app/views/projects/issues/_issue_context.html.haml +++ b/app/views/projects/issues/_issue_context.html.haml @@ -1,24 +1,46 @@ -= form_for [@project, @issue], remote: true, html: {class: 'edit-issue inline-update'} do |f| - .row - .col-sm-6 - %strong.append-right-10 += form_for [@project.namespace.becomes(Namespace), @project, @issue], remote: true, html: {class: 'edit-issue inline-update'} do |f| + %div.prepend-top-20 + .issuable-context-title + %label Assignee: - - - if can?(current_user, :modify_issue, @issue) - = project_users_select_tag('issue[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control', selected: @issue.assignee_id) - - elsif issue.assignee - = link_to_member(@project, @issue.assignee) + - if issue.assignee + %strong= link_to_member(@project, @issue.assignee, size: 24) - else - None + none + - if can?(current_user, :modify_issue, @issue) + = users_select_tag('issue[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @issue.assignee_id, null_user: true, first_user: true) - .col-sm-6.text-right - %strong.append-right-10 + %div.prepend-top-20.clearfix + .issuable-context-title + %label Milestone: - - if can?(current_user, :modify_issue, @issue) - = f.select(:milestone_id, milestone_options(@issue), { include_blank: "Select milestone" }, {class: 'select2 select2-compact'}) - = hidden_field_tag :issue_context - = f.submit class: 'btn' - - elsif issue.milestone - = link_to issue.milestone.title, project_milestone_path + - if issue.milestone + %span.back-to-milestone + = link_to namespace_project_milestone_path(@project.namespace, @project, @issue.milestone) do + %strong + %i.fa.fa-clock-o + = @issue.milestone.title - else - None + none + - if can?(current_user, :modify_issue, @issue) + = f.select(:milestone_id, milestone_options(@issue), { include_blank: "Select milestone" }, {class: 'select2 select2-compact js-select2 js-milestone'}) + = hidden_field_tag :issue_context + = f.submit class: 'btn' + + - if current_user + %div.prepend-top-20.clearfix + .issuable-context-title + %label + Subscription: + %button.btn.btn-block.subscribe-button + %i.fa.fa-eye + %span= @issue.subscribed?(current_user) ? "Unsubscribe" : "Subscribe" + - subscribtion_status = @issue.subscribed?(current_user) ? "subscribed" : "unsubscribed" + .subscription-status{"data-status" => subscribtion_status} + .description-block.unsubscribed{class: ( "hidden" if @issue.subscribed?(current_user) )} + You're not receiving notifications from this thread. + .description-block.subscribed{class: ( "hidden" unless @issue.subscribed?(current_user) )} + You're receiving notifications because you're subscribed to this thread. + +:coffeescript + new Subscription("#{toggle_subscription_namespace_project_issue_path(@issue.project.namespace, @project, @issue)}") diff --git a/app/views/projects/issues/_issues.html.haml b/app/views/projects/issues/_issues.html.haml index 1d0dcd7f07..5d243adb5f 100644 --- a/app/views/projects/issues/_issues.html.haml +++ b/app/views/projects/issues/_issues.html.haml @@ -1,66 +1,3 @@ -.append-bottom-10 - .check-all-holder - = check_box_tag "check_all_issues", nil, false, class: "check_all_issues left" - .issues-filters - .dropdown.inline - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} - %i.icon-user - %span.light assignee: - - if @assignee.present? - %strong= @assignee.name - - elsif params[:assignee_id] == "0" - Unassigned - - else - Any - %b.caret - %ul.dropdown-menu - %li - = link_to project_filter_path(assignee_id: nil) do - Any - = link_to project_filter_path(assignee_id: 0) do - Unassigned - - @assignees.sort_by(&:name).each do |user| - %li - = link_to project_filter_path(assignee_id: user.id) do - = image_tag avatar_icon(user.email), class: "avatar s16", alt: '' - = user.name - - .dropdown.inline.prepend-left-10 - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} - %i.icon-time - %span.light milestone: - - if @milestone.present? - %strong= @milestone.title - - elsif params[:milestone_id] == "0" - None (backlog) - - else - Any - %b.caret - %ul.dropdown-menu - %li - = link_to project_filter_path(milestone_id: nil) do - Any - = link_to project_filter_path(milestone_id: 0) do - None (backlog) - - project_active_milestones.each do |milestone| - %li - = link_to project_filter_path(milestone_id: milestone.id) do - %strong= milestone.title - %small.light= milestone.expires_at - - .pull-right - = render 'shared/sort_dropdown' - - .clearfix - .issues_bulk_update.hide - = form_tag bulk_update_project_issues_path(@project), method: :post do - = select_tag('update[status]', options_for_select([['Open', 'open'], ['Closed', 'closed']]), prompt: "Status") - = project_users_select_tag('update[assignee_id]', placeholder: 'Assignee') - = select_tag('update[milestone_id]', bulk_update_milestone_options, prompt: "Milestone") - = hidden_field_tag 'update[issues_ids]', [] - = hidden_field_tag :status, params[:status] - = button_tag "Update issues", class: "btn update_selected_issues btn-save" - .panel.panel-default %ul.well-list.issues-list = render @issues diff --git a/app/views/projects/issues/index.atom.builder b/app/views/projects/issues/index.atom.builder index 012ba23595..126f2c07fa 100644 --- a/app/views/projects/issues/index.atom.builder +++ b/app/views/projects/issues/index.atom.builder @@ -1,23 +1,12 @@ xml.instruct! xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do xml.title "#{@project.name} issues" - xml.link :href => project_issues_url(@project, :atom), :rel => "self", :type => "application/atom+xml" - xml.link :href => project_issues_url(@project), :rel => "alternate", :type => "text/html" - xml.id project_issues_url(@project) + xml.link :href => namespace_project_issues_url(@project.namespace, @project, :atom), :rel => "self", :type => "application/atom+xml" + xml.link :href => namespace_project_issues_url(@project.namespace, @project), :rel => "alternate", :type => "text/html" + xml.id namespace_project_issues_url(@project.namespace, @project) xml.updated @issues.first.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") if @issues.any? @issues.each do |issue| - xml.entry do - xml.id project_issue_url(@project, issue) - xml.link :href => project_issue_url(@project, issue) - xml.title truncate(issue.title, :length => 80) - xml.updated issue.created_at.strftime("%Y-%m-%dT%H:%M:%SZ") - xml.media :thumbnail, :width => "40", :height => "40", :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 + issue_to_atom(xml, issue) end end diff --git a/app/views/projects/issues/index.html.haml b/app/views/projects/issues/index.html.haml index 5de77b8bf3..d3c7ae24a7 100644 --- a/app/views/projects/issues/index.html.haml +++ b/app/views/projects/issues/index.html.haml @@ -1,9 +1,19 @@ -= render "head" -.row - .fixed.fixed.sidebar-expand-button.hidden-lg.hidden-md.hidden-xs - %i.icon-list.icon-2x - .col-md-3.responsive-side - = render 'shared/project_filter', project_entities_path: project_issues_path(@project), - labels: true, redirect: 'issues', entity: 'issue' - .col-md-9.issues-holder - = render "issues" +.append-bottom-10 + .pull-right + .pull-left + - if current_user + .hidden-xs.pull-left + = link_to namespace_project_issues_path(@project.namespace, @project, :atom, { private_token: current_user.private_token }), class: 'btn append-right-10' do + %i.fa.fa-rss + + = render 'shared/issuable_search_form', path: namespace_project_issues_path(@project.namespace, @project) + + - if can? current_user, :write_issue, @project + = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: params[:assignee_id], milestone_id: params[:milestone_id]}), class: "btn btn-new pull-left", title: "New Issue", id: "new_issue_link" do + %i.fa.fa-plus + New Issue + + = render 'shared/issuable_filter' + +.issues-holder + = render "issues" diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml index bd5f01ff6a..bd28d8a1db 100644 --- a/app/views/projects/issues/show.html.haml +++ b/app/views/projects/issues/show.html.haml @@ -1,73 +1,40 @@ -%h3.page-title - Issue ##{@issue.iid} +.issue + .issue-details + %h4.page-title + .issue-box{ class: issue_box_class(@issue) } + - if @issue.closed? + Closed + - else + Open + Issue ##{@issue.iid} + %small.creator + · created by #{link_to_member(@project, @issue.author)} #{issue_timestamp(@issue)} - %span.pull-right.issue-btn-group - - if can?(current_user, :write_issue, @project) - = link_to new_project_issue_path(@project), class: "btn btn-grouped", title: "New Issue", id: "new_issue_link" do - %i.icon-plus - New Issue - - if can?(current_user, :modify_issue, @issue) - - if @issue.closed? - = link_to 'Reopen', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen" - - else - = link_to 'Close', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close", title: "Close Issue" + .pull-right + - if can?(current_user, :write_issue, @project) + = link_to new_namespace_project_issue_path(@project.namespace, @project), class: "btn btn-grouped new-issue-link", title: "New Issue", id: "new_issue_link" do + %i.fa.fa-plus + New Issue + - if can?(current_user, :modify_issue, @issue) + - if @issue.closed? + = link_to 'Reopen', issue_path(@issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen" + - else + = link_to 'Close', issue_path(@issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close", title: "Close Issue" - = link_to edit_project_issue_path(@project, @issue), class: "btn btn-grouped" do - %i.icon-edit - Edit + = link_to edit_namespace_project_issue_path(@project.namespace, @project, @issue), class: "btn btn-grouped issuable-edit" do + %i.fa.fa-pencil-square-o + Edit -.clearfix - .votes-holder - #votes= render 'votes/votes_block', votable: @issue + %hr + %h2.issue-title + = gfm escape_once(@issue.title) + %div + - if @issue.description.present? + .description + .wiki + = preserve do + = markdown(@issue.description, parse_tasks: true) - .back-link - = link_to project_issues_path(@project) do - ← To issues list - %span.milestone-nav-link - - if @issue.milestone - | - %span.light Milestone - = link_to project_milestone_path(@project, @issue.milestone) do - = @issue.milestone.title - -.issue-box{ class: issue_box_class(@issue) } - .state.clearfix - .state-label - - if @issue.closed? - Closed - - else - Open - - .creator - Created by #{link_to_member(@project, @issue.author)} #{time_ago_with_tooltip(@issue.created_at)} - - %h4.title - = gfm escape_once(@issue.title) - - - if @issue.description.present? - .description - .wiki - = preserve do - = markdown @issue.description - .context - %cite.cgray - = render partial: 'issue_context', locals: { issue: @issue } - - -- content_for :note_actions do - - if can?(current_user, :modify_issue, @issue) - - if @issue.closed? - = link_to 'Reopen Issue', project_issue_path(@project, @issue, issue: {state_event: :reopen }, status_only: true), method: :put, class: "btn btn-grouped btn-reopen" - - else - = link_to 'Close Issue', project_issue_path(@project, @issue, issue: {state_event: :close }, status_only: true), method: :put, class: "btn btn-grouped btn-close", title: "Close Issue" - -.participants - %cite.cgray #{@issue.participants.count} participants - - @issue.participants.each do |participant| - = link_to_member(@project, participant, name: false, size: 24) - - .issue-show-labels.pull-right - - @issue.labels.each do |label| - = render_colored_label(label) - -.voting_notes#notes= render "projects/notes/notes_with_form" + %hr + .issue-discussion + = render "projects/issues/discussion" diff --git a/app/views/projects/issues/update.js.haml b/app/views/projects/issues/update.js.haml index 5199e9fc61..1d38662bff 100644 --- a/app/views/projects/issues/update.js.haml +++ b/app/views/projects/issues/update.js.haml @@ -3,8 +3,15 @@ :plain $("##{dom_id(@issue)}").fadeOut(); - elsif params[:issue_context] - $('.issue-box .context').effect('highlight'); + $('.context').html("#{escape_javascript(render partial: 'issue_context', locals: { issue: @issue })}"); + $('.context').effect('highlight'); - if @issue.milestone - $('.milestone-nav-link').replaceWith("| Milestone #{escape_javascript(link_to @issue.milestone.title, project_milestone_path(@issue.project, @issue.milestone))}") + $('.milestone-nav-link').replaceWith("| Milestone #{escape_javascript(link_to @issue.milestone.title, namespace_project_milestone_path(@issue.project.namespace, @issue.project, @issue.milestone))}") - else $('.milestone-nav-link').html('') + + +$('select.select2').select2({width: 'resolve', dropdownAutoWidth: true}) +$('.edit-issue.inline-update input[type="submit"]').hide(); +new UsersSelect() +new Issue(); diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml index 72a01e1c27..261d52dedc 100644 --- a/app/views/projects/labels/_form.html.haml +++ b/app/views/projects/labels/_form.html.haml @@ -1,8 +1,8 @@ -= form_for [@project, @label], html: { class: 'form-horizontal label-form' } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @label], html: { class: 'form-horizontal label-form' } do |f| -if @label.errors.any? .row .col-sm-10.col-sm-offset-2 - .bs-callout.bs-callout-danger + .alert.alert-danger - @label.errors.full_messages.each do |msg| %span= msg %br @@ -16,9 +16,9 @@ .col-sm-10 .input-group .input-group-addon.label-color-preview   - = f.text_field :color, placeholder: "#AA33EE", class: "form-control" + = f.color_field :color, class: "form-control" .help-block - 6 character hex values starting with a # sign. + Choose any color. %br Or you can choose one of suggested colors below @@ -29,5 +29,5 @@ .form-actions = f.submit 'Save', class: 'btn btn-save js-save-button' - = link_to "Cancel", project_labels_path(@project), class: 'btn btn-cancel' + = link_to "Cancel", namespace_project_labels_path(@project.namespace, @project), class: 'btn btn-cancel' diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml index 03a8f0921b..8282945286 100644 --- a/app/views/projects/labels/_label.html.haml +++ b/app/views/projects/labels/_label.html.haml @@ -2,9 +2,9 @@ = render_colored_label(label) .pull-right %strong.append-right-20 - = link_to project_issues_path(@project, label_name: label.name) do + = link_to namespace_project_issues_path(@project.namespace, @project, label_name: label.name) do = pluralize label.open_issues_count, 'open issue' - if can? current_user, :admin_label, @project - = link_to 'Edit', edit_project_label_path(@project, label), class: 'btn' - = link_to 'Remove', project_label_path(@project, label), class: 'btn btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"} + = link_to 'Edit', edit_namespace_project_label_path(@project.namespace, @project, label), class: 'btn' + = link_to 'Remove', namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"} diff --git a/app/views/projects/labels/destroy.js.haml b/app/views/projects/labels/destroy.js.haml new file mode 100644 index 0000000000..1b4c83ab09 --- /dev/null +++ b/app/views/projects/labels/destroy.js.haml @@ -0,0 +1,2 @@ +- if @project.labels.size == 0 + $('.labels').load(document.URL + ' .light-well').hide().fadeIn(1000) diff --git a/app/views/projects/labels/edit.html.haml b/app/views/projects/labels/edit.html.haml index 52435c5d89..e003d1dfe7 100644 --- a/app/views/projects/labels/edit.html.haml +++ b/app/views/projects/labels/edit.html.haml @@ -2,7 +2,7 @@ Edit label %span.light #{@label.name} .back-link - = link_to project_labels_path(@project) do + = link_to namespace_project_labels_path(@project.namespace, @project) do ← To labels list %hr = render 'form' diff --git a/app/views/projects/labels/index.html.haml b/app/views/projects/labels/index.html.haml index 075779a9c8..0700e72d39 100644 --- a/app/views/projects/labels/index.html.haml +++ b/app/views/projects/labels/index.html.haml @@ -1,17 +1,15 @@ -= render "projects/issues/head" - - if can? current_user, :admin_label, @project - = link_to new_project_label_path(@project), class: "pull-right btn btn-new" do + = link_to new_namespace_project_label_path(@project.namespace, @project), class: "pull-right btn btn-new" do New label %h3.page-title Labels %hr -- if @labels.present? - %ul.bordered-list.manage-labels-list - = render @labels - = paginate @labels, theme: 'gitlab' - -- else - .light-well - .nothing-here-block Create first label or #{link_to 'generate', generate_project_labels_path(@project), method: :post} default set of labels +.labels + - if @labels.present? + %ul.bordered-list.manage-labels-list + = render @labels + = paginate @labels, theme: 'gitlab' + - else + .light-well + .nothing-here-block Create first label or #{link_to 'generate', generate_namespace_project_labels_path(@project.namespace, @project), method: :post} default set of labels diff --git a/app/views/projects/labels/new.html.haml b/app/views/projects/labels/new.html.haml index 850da0b192..0683ed5d4f 100644 --- a/app/views/projects/labels/new.html.haml +++ b/app/views/projects/labels/new.html.haml @@ -1,6 +1,6 @@ %h3 New label .back-link - = link_to project_labels_path(@project) do + = link_to namespace_project_labels_path(@project.namespace, @project) do ← To labels list %hr = render 'form' diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml new file mode 100644 index 0000000000..eb72eaabd8 --- /dev/null +++ b/app/views/projects/merge_requests/_discussion.html.haml @@ -0,0 +1,31 @@ +- content_for :note_actions do + - if can?(current_user, :modify_merge_request, @merge_request) + - if @merge_request.open? + = link_to 'Close', merge_request_path(@merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close close-mr-link js-note-target-close", title: "Close merge request" + - if @merge_request.closed? + = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link js-note-target-reopen", title: "Reopen merge request" + +.row + %section.col-md-9 + .votes-holder.pull-right + #votes= render 'votes/votes_block', votable: @merge_request + = render "projects/merge_requests/show/participants" + = render "projects/notes/notes_with_form" + %aside.col-md-3 + .issuable-affix + .clearfix + %span.slead.has_tooltip{:"data-original-title" => 'Cross-project reference'} + = cross_project_reference(@project, @merge_request) + %hr + .context + = render partial: 'projects/merge_requests/show/context', locals: { merge_request: @merge_request } + + - if @merge_request.labels.any? + .issuable-context-title + %label Labels + .merge-request-show-labels + - @merge_request.labels.each do |label| + = link_to namespace_project_merge_requests_path(@project.namespace, @project, label_name: label.name) do + = render_colored_label(label) + = link_to '#aside', class: 'show-aside' do + %i.fa.fa-angle-left diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml index 0af89b6e37..1c7160bce5 100644 --- a/app/views/projects/merge_requests/_form.html.haml +++ b/app/views/projects/merge_requests/_form.html.haml @@ -1,69 +1,6 @@ -= form_for [@project, @merge_request], html: { class: "merge-request-form form-horizontal" } do |f| - .row - .col-sm-2 - .col-sm-10 - - if @repository.contribution_guide && !@merge_request.persisted? - - contribution_guide_url = project_blob_path(@project, tree_join(@repository.root_ref, @repository.contribution_guide.name)) - .alert.alert-info - Please review the - %strong #{link_to "guidelines for contribution", contribution_guide_url} - to this repository. - - -if @merge_request.errors.any? - .alert.alert-danger - - @merge_request.errors.full_messages.each do |msg| - %div= msg - += form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form' } do |f| .merge-request-form-info - .form-group - = f.label :title, class: 'control-label' do - %strong= "Title *" - .col-sm-10= f.text_field :title, class: "form-control pad js-gfm-input", maxlength: 255, rows: 5, required: true - .form-group - = f.label :description, "Description", class: 'control-label' - .col-sm-10 - = f.text_area :description, class: "form-control js-gfm-input markdown-area", rows: 14 - .col-sm-12.hint - .pull-left Description is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}. - .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. - .clearfix - .error-alert - %hr - .form-group - .issue-assignee - = f.label :assignee_id, class: 'control-label' do - %i.icon-user - Assign to - .col-sm-10 - = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @merge_request.assignee_id) -   - = link_to 'Assign to me', '#', class: 'btn assign-to-me-link' - .form-group - .issue-milestone - = f.label :milestone_id, class: 'control-label' do - %i.icon-time - Milestone - .col-sm-10= f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2'}) - - - .form-group - = f.label :label_ids, class: 'control-label' do - %i.icon-tag - Labels - .col-sm-10 - = f.collection_select :label_ids, @merge_request.target_project.labels.all, :id, :name, { selected: @merge_request.label_ids }, multiple: true, class: 'select2' - - .form-actions - - if @merge_request.new_record? - = f.submit 'Submit merge request', class: "btn btn-create" - -else - = f.submit 'Save changes', class: "btn btn-save" - - if @merge_request.new_record? - = link_to project_merge_requests_path(@source_project), class: "btn btn-cancel" do - Cancel - - else - = link_to project_merge_request_path(@target_project, @merge_request), class: "btn btn-cancel" do - Cancel + = render 'projects/issuable_form', f: f, issuable: @merge_request :javascript disableButtonIfEmptyField("#merge_request_title", ".btn-save"); @@ -72,4 +9,4 @@ e.preventDefault(); }); - window.project_image_path_upload = "#{upload_image_project_path @project}"; + window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; diff --git a/app/views/projects/merge_requests/_head.html.haml b/app/views/projects/merge_requests/_head.html.haml index 35a86e6511..19e4dab874 100644 --- a/app/views/projects/merge_requests/_head.html.haml +++ b/app/views/projects/merge_requests/_head.html.haml @@ -1,5 +1,5 @@ .top-tabs - = link_to project_merge_requests_path(@project), class: "tab #{'active' if current_page?(project_merge_requests_path(@project)) }" do + = link_to namespace_project_merge_requests_path(@project.namespace, @project), class: "tab #{'active' if current_page?(namespace_project_merge_requests_path(@project.namespace, @project)) }" do %span Merge Requests diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml index 7f5de232dc..4f30d1e69f 100644 --- a/app/views/projects/merge_requests/_merge_request.html.haml +++ b/app/views/projects/merge_requests/_merge_request.html.haml @@ -1,37 +1,48 @@ %li{ class: mr_css_classes(merge_request) } .merge-request-title - %span.light= "##{merge_request.iid}" - = link_to_gfm truncate(merge_request.title, length: 80), project_merge_request_path(merge_request.target_project, merge_request), class: "row_title" - - if merge_request.merged? - %small.pull-right - %i.icon-ok - = "MERGED" - - else - %span.pull-right - - if merge_request.for_fork? - %span.light - #{merge_request.source_project_namespace}: - = truncate merge_request.source_branch, length: 25 - %i.icon-angle-right.light - = merge_request.target_branch + %span.str-truncated + = link_to_gfm merge_request.title, merge_request_path(merge_request), class: "row_title" + .pull-right.light + - if merge_request.merged? + %span + %i.fa.fa-check + MERGED + - elsif merge_request.closed? + %span + %i.fa.fa-close + CLOSED + - else + %span.hidden-xs.hidden-sm + %span.label-branch< + %i.fa.fa-code-fork + %span= merge_request.target_branch + - note_count = merge_request.mr_and_commit_notes.user.count + - if note_count > 0 +   + %span + %i.fa.fa-comments + = note_count .merge-request-info - - if merge_request.author - authored by #{link_to_member(merge_request.source_project, merge_request.author)} + = link_to "##{merge_request.iid}", merge_request_path(merge_request), class: "light" + - if merge_request.assignee + assigned to #{link_to_member(merge_request.source_project, merge_request.assignee)} + - else + Unassigned - if merge_request.votes_count > 0 = render 'votes/votes_inline', votable: merge_request - - if merge_request.notes.any? - %span - %i.icon-comments - = merge_request.mr_and_commit_notes.count - if merge_request.milestone_id? %span - %i.icon-time + %i.fa.fa-clock-o = merge_request.milestone.title + - if merge_request.tasks? + %span.task-status + = merge_request.task_status - .pull-right + .pull-right.hidden-xs %small updated #{time_ago_with_tooltip(merge_request.updated_at, 'bottom', 'merge_request_updated_ago')} .merge-request-labels - merge_request.labels.each do |label| - = render_colored_label(label) + = link_to namespace_project_merge_requests_path(merge_request.project.namespace, merge_request.project, label_name: label.name) do + = render_colored_label(label) diff --git a/app/views/projects/merge_requests/_merge_requests.html.haml b/app/views/projects/merge_requests/_merge_requests.html.haml new file mode 100644 index 0000000000..b8a0ca9a42 --- /dev/null +++ b/app/views/projects/merge_requests/_merge_requests.html.haml @@ -0,0 +1,13 @@ +.panel.panel-default + %ul.well-list.mr-list + = render @merge_requests + - if @merge_requests.blank? + %li + .nothing-here-block No merge requests to show + +- if @merge_requests.present? + .pull-right + %span.cgray.pull-right #{@merge_requests.total_count} merge requests for this filter + + = paginate @merge_requests, theme: "gitlab" + diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml index 9972617215..17e76059fd 100644 --- a/app/views/projects/merge_requests/_new_compare.html.haml +++ b/app/views/projects/merge_requests/_new_compare.html.haml @@ -1,7 +1,7 @@ %h3.page-title Compare branches for new Merge Request %hr -= form_for [@project, @merge_request], url: new_project_merge_request_path(@project), method: :get, html: { class: "merge-request-form form-inline" } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @merge_request], url: new_namespace_project_merge_request_path(@project.namespace, @project), method: :get, html: { class: "merge-request-form form-inline" } do |f| .hide.alert.alert-danger.mr-compare-errors .merge-request-branches.row .col-md-6 @@ -60,19 +60,19 @@ , target_branch = $("#merge_request_target_branch") , target_project = $("#merge_request_target_project_id"); - $.get("#{branch_from_project_merge_requests_path(@source_project)}", {ref: source_branch.val() }); - $.get("#{branch_to_project_merge_requests_path(@source_project)}", {target_project_id: target_project.val(),ref: target_branch.val() }); + $.get("#{branch_from_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {ref: source_branch.val() }); + $.get("#{branch_to_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {target_project_id: target_project.val(),ref: target_branch.val() }); target_project.on("change", function() { - $.get("#{update_branches_project_merge_requests_path(@source_project)}", {target_project_id: $(this).val() }); + $.get("#{update_branches_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {target_project_id: $(this).val() }); }); source_branch.on("change", function() { - $.get("#{branch_from_project_merge_requests_path(@source_project)}", {ref: $(this).val() }); + $.get("#{branch_from_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {ref: $(this).val() }); $(".mr-compare-errors").fadeOut(); $(".mr-compare-btn").enable(); }); target_branch.on("change", function() { - $.get("#{branch_to_project_merge_requests_path(@source_project)}", {target_project_id: target_project.val(),ref: $(this).val() }); + $.get("#{branch_to_namespace_project_merge_requests_path(@source_project.namespace, @source_project)}", {target_project_id: target_project.val(),ref: $(this).val() }); $(".mr-compare-errors").fadeOut(); $(".mr-compare-btn").enable(); }); diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml index 7c43d35598..4e72458932 100644 --- a/app/views/projects/merge_requests/_new_submit.html.haml +++ b/app/views/projects/merge_requests/_new_submit.html.haml @@ -7,84 +7,105 @@ %strong.label-branch #{@merge_request.target_project_namespace}:#{@merge_request.target_branch} %span.pull-right - = link_to 'Change branches', new_project_merge_request_path(@project) + = link_to 'Change branches', new_namespace_project_merge_request_path(@project.namespace, @project) -= form_for [@project, @merge_request], html: { class: "merge-request-form" } do |f| - .panel.panel-default += form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: "merge-request-form form-horizontal gfm-form" } do |f| + .merge-request-form-info + .form-group + = f.label :title, class: 'control-label' do + %strong Title * + .col-sm-10 + = f.text_field :title, maxlength: 255, autofocus: true, class: 'form-control pad js-gfm-input', required: true + .form-group.issuable-description + = f.label :description, 'Description', class: 'control-label' + .col-sm-10 + = render layout: 'projects/md_preview', locals: { preview_class: "wiki" } do + = render 'projects/zen', f: f, attr: :description, classes: 'description form-control' - .panel-body - .form-group - .light - = f.label :title do - = "Title *" - = f.text_field :title, class: "form-control input-lg js-gfm-input", maxlength: 255, rows: 5, required: true - .form-group - .light - = f.label :description, "Description" - = f.text_area :description, class: "form-control js-gfm-input markdown-area", rows: 10 - .clearfix.hint - .pull-left Description is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}. - .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. - .error-alert - .form-group - .issue-assignee - = f.label :assignee_id do - %i.icon-user - Assign to - %div - = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @merge_request.assignee_id, project_id: @merge_request.target_project_id) -   - = link_to 'Assign to me', '#', class: 'btn assign-to-me-link' - .form-group - .issue-milestone - = f.label :milestone_id do - %i.icon-time - Milestone - %div= f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2'}) - .form-group - = f.label :label_ids do - %i.icon-tag - Labels - %div - = f.collection_select :label_ids, @merge_request.target_project.labels.all, :id, :name, { selected: @merge_request.label_ids }, multiple: true, class: 'select2' + .col-sm-12-hint + .pull-left + Parsed with + #{link_to 'Gitlab Flavored Markdown', help_page_path('markdown', 'markdown'), target: '_blank'}. + .pull-right + Attach files by dragging & dropping + or #{link_to 'selecting them', '#', class: 'markdown-selector'}. - .panel-footer - - if contribution_guide_url(@target_project) + .clearfix + .error-alert + %hr + .form-group + .issue-assignee + = f.label :assignee_id, class: 'control-label' do + %i.fa.fa-user + Assign to + .col-sm-10 + = users_select_tag('merge_request[assignee_id]', placeholder: 'Select a user', class: 'custom-form-control', selected: @merge_request.assignee_id, project_id: @merge_request.target_project_id) +   + = link_to 'Assign to me', '#', class: 'btn assign-to-me-link' + .form-group + .issue-milestone + = f.label :milestone_id, class: 'control-label' do + %i.fa.fa-clock-o + Milestone + .col-sm-10 + - if milestone_options(@merge_request).present? + = f.select(:milestone_id, milestone_options(@merge_request), {include_blank: 'Select milestone'}, {class: 'select2'}) + - else + %span.light No open milestones available. +   + - if can? current_user, :admin_milestone, @merge_request.target_project + = link_to 'Create new milestone', new_namespace_project_milestone_path(@merge_request.target_project.namespace, @merge_request.target_project), target: :blank + .form-group + = f.label :label_ids, class: 'control-label' do + %i.fa.fa-tag + Labels + .col-sm-10 + - if @merge_request.target_project.labels.any? + = f.collection_select :label_ids, @merge_request.target_project.labels.all, :id, :name, {selected: @merge_request.label_ids}, multiple: true, class: 'select2' + - else + %span.light No labels yet. +   + - if can? current_user, :admin_label, @merge_request.target_project + = link_to 'Create new label', new_namespace_project_label_path(@merge_request.target_project.namespace, @merge_request.target_project), target: :blank + + .form-actions + - if guide_url = contribution_guide_url(@target_project) %p Please review the - %strong #{link_to "guidelines for contribution", contribution_guide_url(@target_project)} + %strong #{link_to 'guidelines for contribution', guide_url} to this repository. = f.hidden_field :source_project_id + = f.hidden_field :source_branch = f.hidden_field :target_project_id = f.hidden_field :target_branch - = f.hidden_field :source_branch - = f.submit 'Submit merge request', class: "btn btn-create" + = f.submit 'Submit merge request', class: 'btn btn-create' -.mr-compare - %div.panel.panel-default - .panel-heading - Commits (#{@commits.count}) - - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE - %ul.well-list - - Commit.decorate(@commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE)).each do |commit| - = render "projects/commits/inline_commit", commit: commit, project: @project - %li.warning-row.unstyled - other #{@commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE} commits hidden to prevent performance issues. +.mr-compare.merge-request + %ul.nav.nav-tabs.merge-request-tabs + %li.commits-tab{data: {action: 'commits', toggle: 'tab'}} + = link_to url_for(params) do + %i.fa.fa-history + Commits + %span.badge= @commits.size + %li.diffs-tab{data: {action: 'diffs', toggle: 'tab'}} + = link_to url_for(params) do + %i.fa.fa-list-alt + Changes + %span.badge= @diffs.size + + .commits.tab-content + = render "projects/commits/commits", project: @project + .diffs.tab-content + - if @diffs.present? + = render "projects/diffs/diffs", diffs: @diffs, project: @project + - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE + .alert.alert-danger + %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits. + %p To preserve performance the line changes are not shown. - else - %ul.well-list= render Commit.decorate(@commits), project: @project - - %h4 Changes - - if @diffs.present? - = render "projects/commits/diffs", diffs: @diffs, project: @project - - elsif @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE - .bs-callout.bs-callout-danger - %h4 This comparison includes more than #{MergeRequestDiff::COMMITS_SAFE_SIZE} commits. - %p To preserve performance the line changes are not shown. - - else - .bs-callout.bs-callout-danger - %h4 This comparison includes huge diff. - %p To preserve performance the line changes are not shown. - + .alert.alert-danger + %h4 This comparison includes a huge diff. + %p To preserve performance the line changes are not shown. :javascript $('.assign-to-me-link').on('click', function(e){ @@ -92,4 +113,11 @@ e.preventDefault(); }); - window.project_image_path_upload = "#{upload_image_project_path @project}"; + window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; + +:javascript + var merge_request + merge_request = new MergeRequest({ + action: 'commits' + }); + diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml index 8fca9f0212..a74aede4e6 100644 --- a/app/views/projects/merge_requests/_show.html.haml +++ b/app/views/projects/merge_requests/_show.html.haml @@ -1,46 +1,76 @@ -.merge-request - = render "projects/merge_requests/show/mr_title" - = render "projects/merge_requests/show/how_to_merge" - = render "projects/merge_requests/show/mr_box" - = render "projects/merge_requests/show/state_widget" - = render "projects/merge_requests/show/commits" - = render "projects/merge_requests/show/participants" +.merge-request{'data-url' => merge_request_path(@merge_request)} + .merge-request-details + = render "projects/merge_requests/show/mr_title" + %hr + = render "projects/merge_requests/show/mr_box" + %hr + .append-bottom-20 + .slead + %span From + - if @merge_request.for_fork? + %strong.label-branch< + - if @merge_request.source_project + = link_to @merge_request.source_project_namespace, namespace_project_path(@merge_request.source_project.namespace, @merge_request.source_project) + - else + \ #{@merge_request.source_project_namespace} + \:#{@merge_request.source_branch} + %span into + %strong.label-branch #{@merge_request.target_project_namespace}:#{@merge_request.target_branch} + - else + %strong.label-branch #{@merge_request.source_branch} + %span into + %strong.label-branch #{@merge_request.target_branch} + - if @merge_request.open? + %span.pull-right + .btn-group + %a.btn.dropdown-toggle{ data: {toggle: :dropdown} } + %i.fa.fa-download + Download as + %span.caret + %ul.dropdown-menu + %li= link_to "Email Patches", merge_request_path(@merge_request, format: :patch) + %li= link_to "Plain Diff", merge_request_path(@merge_request, format: :diff) + + = render "projects/merge_requests/show/how_to_merge" + = render "projects/merge_requests/show/state_widget" - if @commits.present? - %ul.nav.nav-pills.merge-request-tabs + %ul.nav.nav-tabs.merge-request-tabs %li.notes-tab{data: {action: 'notes'}} - = link_to project_merge_request_path(@project, @merge_request) do - %i.icon-comment + = link_to merge_request_path(@merge_request) do + %i.fa.fa-comments Discussion - %span.badge= @merge_request.mr_and_commit_notes.count + %span.badge= @merge_request.mr_and_commit_notes.user.count + %li.commits-tab{data: {action: 'commits'}} + = link_to merge_request_path(@merge_request), title: 'Commits' do + %i.fa.fa-history + Commits + %span.badge= @commits.size %li.diffs-tab{data: {action: 'diffs'}} - = link_to diffs_project_merge_request_path(@project, @merge_request) do - %i.icon-list-alt + = link_to diffs_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) do + %i.fa.fa-list-alt Changes %span.badge= @merge_request.diffs.size - - content_for :note_actions do - - if can?(current_user, :modify_merge_request, @merge_request) - - unless @merge_request.closed? || @merge_request.merged? - = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :close }), method: :put, class: "btn btn-grouped btn-close close-mr-link", title: "Close merge request" - - if @merge_request.closed? - = link_to 'Reopen', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link", title: "Close merge request" - + .notes.tab-content.voting_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" } + = render "projects/merge_requests/discussion" + .commits.tab-content + = render "projects/merge_requests/show/commits" .diffs.tab-content - if current_page?(action: 'diffs') = render "projects/merge_requests/show/diffs" - .notes.tab-content.voting_notes#notes{ class: (controller.action_name == 'show') ? "" : "hide" } - = render "projects/notes/notes_with_form" + .mr-loading-status = spinner + :javascript var merge_request; merge_request = new MergeRequest({ - url_to_automerge_check: "#{automerge_check_project_merge_request_path(@project, @merge_request)}", + url_to_automerge_check: "#{automerge_check_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", check_enable: #{@merge_request.unchecked? ? "true" : "false"}, - url_to_ci_check: "#{ci_status_project_merge_request_path(@project, @merge_request)}", + url_to_ci_check: "#{ci_status_namespace_project_merge_request_path(@project.namespace, @project, @merge_request)}", ci_enable: #{@project.ci_service ? "true" : "false"}, current_status: "#{@merge_request.merge_status_name}", action: "#{controller.action_name}" diff --git a/app/views/projects/merge_requests/automerge.js.haml b/app/views/projects/merge_requests/automerge.js.haml index e01ff662e7..a53cbb150a 100644 --- a/app/views/projects/merge_requests/automerge.js.haml +++ b/app/views/projects/merge_requests/automerge.js.haml @@ -1,7 +1,6 @@ -if @status :plain - location.reload(); + merge_request.mergeInProgress(); -else :plain merge_request.alreadyOrCannotBeMerged() - diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml index 0954fa8fce..d7992bdd19 100644 --- a/app/views/projects/merge_requests/index.html.haml +++ b/app/views/projects/merge_requests/index.html.haml @@ -1,78 +1,11 @@ -- if can? current_user, :write_merge_request, @project - = link_to new_project_merge_request_path(@project), class: "pull-right btn btn-new", title: "New Merge Request" do - %i.icon-plus - New Merge Request -%h3.page-title - Merge Requests -%hr -.row - .fixed.sidebar-expand-button.hidden-lg.hidden-md - %i.icon-list.icon-2x - .col-md-3.responsive-side - = render 'shared/project_filter', project_entities_path: project_merge_requests_path(@project), - labels: true, redirect: 'merge_requests', entity: 'merge_request' - .col-md-9 - .mr-filters.append-bottom-10 - .dropdown.inline - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} - %i.icon-user - %span.light assignee: - - if @assignee.present? - %strong= @assignee.name - - elsif params[:assignee_id] == "0" - Unassigned - - else - Any - %b.caret - %ul.dropdown-menu - %li - = link_to project_filter_path(assignee_id: nil) do - Any - = link_to project_filter_path(assignee_id: 0) do - Unassigned - - @assignees.sort_by(&:name).each do |user| - %li - = link_to project_filter_path(assignee_id: user.id) do - = image_tag avatar_icon(user.email), class: "avatar s16", alt: '' - = user.name +.append-bottom-10 + .pull-right + = render 'shared/issuable_search_form', path: namespace_project_merge_requests_path(@project.namespace, @project) - .dropdown.inline.prepend-left-10 - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} - %i.icon-time - %span.light milestone: - - if @milestone.present? - %strong= @milestone.title - - elsif params[:milestone_id] == "0" - None (backlog) - - else - Any - %b.caret - %ul.dropdown-menu - %li - = link_to project_filter_path(milestone_id: nil) do - Any - = link_to project_filter_path(milestone_id: 0) do - None (backlog) - - project_active_milestones.each do |milestone| - %li - = link_to project_filter_path(milestone_id: milestone.id) do - %strong= milestone.title - %small.light= milestone.expires_at - - .pull-right - = render 'shared/sort_dropdown' - - .panel.panel-default - %ul.well-list.mr-list - = render @merge_requests - - if @merge_requests.blank? - %li - .nothing-here-block No merge requests to show - - if @merge_requests.present? - .pull-right - %span.cgray.pull-right #{@merge_requests.total_count} merge requests for this filter - - = paginate @merge_requests, theme: "gitlab" - -:javascript - $(merge_requestsPage); + - if can? current_user, :write_merge_request, @project + = link_to new_namespace_project_merge_request_path(@project.namespace, @project), class: "btn btn-new pull-left", title: "New Merge Request" do + %i.fa.fa-plus + New Merge Request + = render 'shared/issuable_filter' +.merge-requests-holder + = render 'merge_requests' diff --git a/app/views/projects/merge_requests/show/_commits.html.haml b/app/views/projects/merge_requests/show/_commits.html.haml index ede709ea1d..3b7f283daf 100644 --- a/app/views/projects/merge_requests/show/_commits.html.haml +++ b/app/views/projects/merge_requests/show/_commits.html.haml @@ -1,30 +1 @@ -- if @commits.present? - .panel.panel-default - .panel-heading - %i.icon-list - Commits (#{@commits.count}) - .commits.mr-commits - - if @commits.count > 8 - %ul.first-commits.well-list - - @commits.first(8).each do |commit| - = render "projects/commits/commit", commit: commit, project: @merge_request.source_project - %li.bottom - 8 of #{@commits.count} commits displayed. - %strong - %a.show-all-commits Click here to show all - - if @commits.size > MergeRequestDiff::COMMITS_SAFE_SIZE - %ul.all-commits.hide.well-list - - @commits.first(MergeRequestDiff::COMMITS_SAFE_SIZE).each do |commit| - = render "projects/commits/inline_commit", commit: commit, project: @merge_request.source_project - %li - other #{@commits.size - MergeRequestDiff::COMMITS_SAFE_SIZE} commits hidden to prevent performance issues. - - else - %ul.all-commits.hide.well-list - - @commits.each do |commit| - = render "projects/commits/inline_commit", commit: commit, project: @merge_request.source_project - - - else - %ul.well-list - - @commits.each do |commit| - = render "projects/commits/commit", commit: commit, project: @merge_request.source_project - += render "projects/commits/commits", project: @merge_request.source_project diff --git a/app/views/projects/merge_requests/show/_context.html.haml b/app/views/projects/merge_requests/show/_context.html.haml index ab00b34242..105562fb05 100644 --- a/app/views/projects/merge_requests/show/_context.html.haml +++ b/app/views/projects/merge_requests/show/_context.html.haml @@ -1,24 +1,48 @@ -= form_for [@project, @merge_request], remote: true, html: {class: 'edit-merge_request inline-update'} do |f| - .row - .col-sm-6 - %strong.append-right-10 += form_for [@project.namespace.becomes(Namespace), @project, @merge_request], remote: true, html: {class: 'edit-merge_request inline-update'} do |f| + %div.prepend-top-20 + .issuable-context-title + %label Assignee: - - - if can?(current_user, :modify_merge_request, @merge_request) - = project_users_select_tag('merge_request[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control', selected: @merge_request.assignee_id) - - elsif merge_request.assignee - = link_to_member(@project, @merge_request.assignee) + - if @merge_request.assignee + %strong= link_to_member(@project, @merge_request.assignee, size: 24) - else - None - - .col-sm-6.text-right - %strong.append-right-10 - Milestone: + none + .issuable-context-selectbox - if can?(current_user, :modify_merge_request, @merge_request) - = f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2 select2-compact'}) + = users_select_tag('merge_request[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @merge_request.assignee_id, null_user: true) + + %div.prepend-top-20.clearfix + .issuable-context-title + %label + Milestone: + - if @merge_request.milestone + %span.back-to-milestone + = link_to namespace_project_milestone_path(@project.namespace, @project, @merge_request.milestone) do + %strong + %i.fa.fa-clock-o + = @merge_request.milestone.title + - else + none + .issuable-context-selectbox + - if can?(current_user, :modify_merge_request, @merge_request) + = f.select(:milestone_id, milestone_options(@merge_request), { include_blank: "Select milestone" }, {class: 'select2 select2-compact js-select2 js-milestone'}) = hidden_field_tag :merge_request_context = f.submit class: 'btn' - - elsif merge_request.milestone - = link_to merge_request.milestone.title, project_milestone_path - - else - None + + - if current_user + %div.prepend-top-20.clearfix + .issuable-context-title + %label + Subscription: + %button.btn.btn-block.subscribe-button + %i.fa.fa-eye + %span= @merge_request.subscribed?(current_user) ? "Unsubscribe" : "Subscribe" + - subscribtion_status = @merge_request.subscribed?(current_user) ? "subscribed" : "unsubscribed" + .subscription-status{"data-status" => subscribtion_status} + .description-block.unsubscribed{class: ( "hidden" if @merge_request.subscribed?(current_user) )} + You're not receiving notifications from this thread. + .description-block.subscribed{class: ( "hidden" unless @merge_request.subscribed?(current_user) )} + You're receiving notifications because you're subscribed to this thread. + +:coffeescript + new Subscription("#{toggle_subscription_namespace_project_merge_request_path(@merge_request.project.namespace, @project, @merge_request)}") diff --git a/app/views/projects/merge_requests/show/_diffs.html.haml b/app/views/projects/merge_requests/show/_diffs.html.haml index eb63b68106..786b5f3906 100644 --- a/app/views/projects/merge_requests/show/_diffs.html.haml +++ b/app/views/projects/merge_requests/show/_diffs.html.haml @@ -1,12 +1,12 @@ - if @merge_request_diff.collected? - = render "projects/commits/diffs", diffs: @merge_request.diffs, project: @merge_request.source_project + = render "projects/diffs/diffs", diffs: @merge_request.diffs, project: @merge_request.source_project - elsif @merge_request_diff.empty? .nothing-here-block Nothing to merge from #{@merge_request.source_branch} into #{@merge_request.target_branch} - else - .bs-callout.bs-callout-warning + .alert.alert-warning %h4 Changes view for this comparison is extremely large. %p You can - = link_to "download it", project_merge_request_path(@merge_request.target_project, @merge_request, format: :diff), class: "vlink" + = link_to "download it", merge_request_path(@merge_request, format: :diff), class: "vlink" instead. diff --git a/app/views/projects/merge_requests/show/_how_to_merge.html.haml b/app/views/projects/merge_requests/show/_how_to_merge.html.haml index 9540453ce3..63db4b3096 100644 --- a/app/views/projects/merge_requests/show/_how_to_merge.html.haml +++ b/app/views/projects/merge_requests/show/_how_to_merge.html.haml @@ -10,11 +10,11 @@ - target_remote = @merge_request.target_project.namespace.nil? ? "target" :@merge_request.target_project.namespace.path %p %strong Step 1. - Checkout the branch we are going to merge and pull in the code + Fetch the code and create a new branch pointing to it %pre.dark :preserve - git checkout -b #{@merge_request.source_project_path}-#{@merge_request.source_branch} #{@merge_request.target_branch} - git pull #{@merge_request.source_project.http_url_to_repo} #{@merge_request.source_branch} + git fetch #{@merge_request.source_project.http_url_to_repo} #{@merge_request.source_branch} + git checkout -b #{@merge_request.source_project_path}-#{@merge_request.source_branch} FETCH_HEAD %p %strong Step 2. Merge the branch and push the changes to GitLab diff --git a/app/views/projects/merge_requests/show/_mr_accept.html.haml b/app/views/projects/merge_requests/show/_mr_accept.html.haml index d7d5f970c9..9f51f84d40 100644 --- a/app/views/projects/merge_requests/show/_mr_accept.html.haml +++ b/app/views/projects/merge_requests/show/_mr_accept.html.haml @@ -12,34 +12,25 @@ - if @show_merge_controls .automerge_widget.can_be_merged.hide .clearfix - = form_for [:automerge, @project, @merge_request], remote: true, method: :post do |f| - %h4 - You can accept this request automatically. - .accept-merge-holder.clearfix - .js-toggle-container - %p - You can - %strong= link_to "modify merge commit message", "#", class: "modify-merge-commit-link js-toggle-button", title: "Modify merge commit message" - before accepting merge request - .js-toggle-content.hide - .form-group - = label_tag :merge_commit_message, "Commit message", class: 'control-label' - .col-sm-10 - = render 'shared/commit_message_container', {textarea: text_area_tag(:merge_commit_message, - @merge_request.merge_commit_message, class: "form-control js-gfm-input", rows: 14, required: true)} - %p.hint - The recommended maximum line length is 52 characters for the first line and 72 characters for all following lines. + = form_for [:automerge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post do |f| + .accept-merge-holder.clearfix.js-toggle-container + .accept-action + = f.submit "Accept Merge Request", class: "btn btn-create accept_merge_request" + - if can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && !@merge_request.for_fork? + .accept-control.checkbox + = label_tag :should_remove_source_branch, class: "remove_source_checkbox" do + = check_box_tag :should_remove_source_branch + Remove source-branch + .accept-control + = link_to "#", class: "modify-merge-commit-link js-toggle-button", title: "Modify merge commit message" do + %i.fa.fa-edit + Modify commit message + .js-toggle-content.hide.prepend-top-20 + = render 'shared/commit_message_container', params: params, + text: @merge_request.merge_commit_message, + rows: 14, hint: true - .accept-group - .pull-left - = f.submit "Accept Merge Request", class: "btn btn-create accept_merge_request" - - if can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && !@merge_request.for_fork? - .remove_branch_holder.pull-left - = label_tag :should_remove_source_branch, class: "checkbox" do - = check_box_tag :should_remove_source_branch - Remove source-branch - - %hr + %br .light If you still want to merge this request manually - use %strong @@ -54,15 +45,22 @@ .automerge_widget.cannot_be_merged.hide %h4 This request can't be merged with GitLab. - %p You should do it manually with %strong - = link_to "command line", "#modal_merge_info", class: "how_to_merge_link", title: "How To Merge", "data-toggle" => "modal" + = link_to "#modal_merge_info", class: "underlined-link how_to_merge_link", title: "How To Merge", "data-toggle" => "modal" do + command line + + %p + %button.btn.disabled + %i.fa.fa-warning + Accept Merge Request +   + This usually happens when git can not resolve conflicts between branches automatically. .automerge_widget.unchecked %p %strong - %i.icon-spinner.icon-spin + %i.fa.fa-spinner.fa-spin Checking for ability to automatically merge… .automerge_widget.already_cannot_be_merged.hide @@ -71,6 +69,6 @@ .merge-in-progress.hide %p - %i.icon-spinner.icon-spin + %i.fa.fa-spinner.fa-spin   Merge is in progress. Please wait. Page will be automatically reloaded.   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 f1aaba2010..ada9ae58b8 100644 --- a/app/views/projects/merge_requests/show/_mr_box.html.haml +++ b/app/views/projects/merge_requests/show/_mr_box.html.haml @@ -1,25 +1,9 @@ -.issue-box{ class: issue_box_class(@merge_request) } - .state.clearfix - .state-label - - if @merge_request.merged? - Merged - - elsif @merge_request.closed? - Closed - - else - Open - - .creator - Created by #{link_to_member(@project, @merge_request.author)} #{time_ago_with_tooltip(@merge_request.created_at)} - - %h4.title - = gfm escape_once(@merge_request.title) +%h2.issue-title + = gfm escape_once(@merge_request.title) +%div - if @merge_request.description.present? .description .wiki = preserve do - = markdown @merge_request.description - - .context - %cite.cgray - = render partial: 'projects/merge_requests/show/context', locals: { merge_request: @merge_request } + = markdown(@merge_request.description, parse_tasks: true) diff --git a/app/views/projects/merge_requests/show/_mr_ci.html.haml b/app/views/projects/merge_requests/show/_mr_ci.html.haml index b77eeac612..ffa3f7b0e3 100644 --- a/app/views/projects/merge_requests/show/_mr_ci.html.haml +++ b/app/views/projects/merge_requests/show/_mr_ci.html.haml @@ -1,29 +1,34 @@ - if @commits.any? .ci_widget.ci-success{style: "display:none"} - %i.icon-ok + %i.fa.fa-check %span CI build passed for #{@merge_request.last_commit_short_sha}. - = link_to "Build page", ci_build_details_path(@merge_request) + = link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink" .ci_widget.ci-failed{style: "display:none"} - %i.icon-remove + %i.fa.fa-times %span CI build failed for #{@merge_request.last_commit_short_sha}. - = link_to "Build page", ci_build_details_path(@merge_request) + = link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink" - [:running, :pending].each do |status| .ci_widget{class: "ci-#{status}", style: "display:none"} - %i.icon-time + %i.fa.fa-clock-o %span CI build #{status} for #{@merge_request.last_commit_short_sha}. - = link_to "Build page", ci_build_details_path(@merge_request) + = link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink" .ci_widget - %strong - %i.icon-spinner - Checking for CI status for #{@merge_request.last_commit_short_sha} + %i.fa.fa-spinner + Checking for CI status for #{@merge_request.last_commit_short_sha} + + .ci_widget.ci-canceled{style: "display:none"} + %i.fa.fa-times + %span CI build canceled + for #{@merge_request.last_commit_short_sha}. + = link_to "View build page", ci_build_details_path(@merge_request), :"data-no-turbolink" => "data-no-turbolink" .ci_widget.ci-error{style: "display:none"} - %i.icon-remove + %i.fa.fa-times %span Cannot connect to the CI server. Please check your settings and try again. diff --git a/app/views/projects/merge_requests/show/_mr_title.html.haml b/app/views/projects/merge_requests/show/_mr_title.html.haml index 563a524499..46e92a9c55 100644 --- a/app/views/projects/merge_requests/show/_mr_title.html.haml +++ b/app/views/projects/merge_requests/show/_mr_title.html.haml @@ -1,40 +1,22 @@ -%h3.page-title +%h4.page-title + .issue-box{ class: issue_box_class(@merge_request) } + - if @merge_request.merged? + Merged + - elsif @merge_request.closed? + Closed + - else + Open = "Merge Request ##{@merge_request.iid}" + %small.creator + · + created by #{link_to_member(@project, @merge_request.author)} #{time_ago_with_tooltip(@merge_request.created_at)} - %span.pull-right.issue-btn-group + .issue-btn-group.pull-right - if can?(current_user, :modify_merge_request, @merge_request) - if @merge_request.open? - .btn-group.pull-left - %a.btn.btn-grouped.dropdown-toggle{ data: {toggle: :dropdown} } - %i.icon-download-alt - Download as - %span.caret - %ul.dropdown-menu - %li= link_to "Email Patches", project_merge_request_path(@project, @merge_request, format: :patch) - %li= link_to "Plain Diff", project_merge_request_path(@project, @merge_request, format: :diff) - - = link_to 'Close', project_merge_request_path(@project, @merge_request, merge_request: { state_event: :close }), method: :put, class: "btn btn-grouped btn-close", title: "Close merge request" - - = link_to edit_project_merge_request_path(@project, @merge_request), class: "btn btn-grouped", id:"edit_merge_request" do - %i.icon-edit + = link_to 'Close', merge_request_path(@merge_request, merge_request: { state_event: :close }), method: :put, class: "btn btn-grouped btn-close", title: "Close merge request" + = link_to edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request), class: "btn btn-grouped issuable-edit", id: "edit_merge_request" do + %i.fa.fa-pencil-square-o Edit - if @merge_request.closed? - = link_to 'Reopen', project_merge_request_path(@project, @merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link", title: "Close merge request" - -.votes-holder.hidden-sm.hidden-xs - #votes= render 'votes/votes_block', votable: @merge_request - -.back-link - = link_to project_merge_requests_path(@project) do - ← To merge requests - - %span.prepend-left-20 - %span From - - if @merge_request.for_fork? - %strong.label-branch #{@merge_request.source_project_namespace}:#{@merge_request.source_branch} - %span into - %strong.label-branch #{@merge_request.target_project_namespace}:#{@merge_request.target_branch} - - else - %strong.label-branch #{@merge_request.source_branch} - %span into - %strong.label-branch #{@merge_request.target_branch} + = link_to 'Reopen', merge_request_path(@merge_request, merge_request: {state_event: :reopen }), method: :put, class: "btn btn-grouped btn-reopen reopen-mr-link", title: "Close merge request" diff --git a/app/views/projects/merge_requests/show/_participants.html.haml b/app/views/projects/merge_requests/show/_participants.html.haml index 007c111f7f..9c93fa55fe 100644 --- a/app/views/projects/merge_requests/show/_participants.html.haml +++ b/app/views/projects/merge_requests/show/_participants.html.haml @@ -1,8 +1,4 @@ .participants - %cite.cgray #{@merge_request.participants.count} participants - - @merge_request.participants.each do |participant| + %span #{@merge_request.participants(current_user).count} participants + - @merge_request.participants(current_user).each do |participant| = link_to_member(@project, participant, name: false, size: 24) - - .merge-request-show-labels.pull-right - - @merge_request.labels.each do |label| - = render_colored_label(label) diff --git a/app/views/projects/merge_requests/show/_remove_source_branch.html.haml b/app/views/projects/merge_requests/show/_remove_source_branch.html.haml index 491360af1b..59cb85edfc 100644 --- a/app/views/projects/merge_requests/show/_remove_source_branch.html.haml +++ b/app/views/projects/merge_requests/show/_remove_source_branch.html.haml @@ -4,14 +4,14 @@ - elsif can_remove_branch?(@merge_request.source_project, @merge_request.source_branch) && @merge_request.merged? .remove_source_branch_widget %p Changes merged into #{@merge_request.target_branch}. You can remove source branch now - = link_to project_branch_path(@merge_request.source_project, @source_branch), remote: true, method: :delete, class: "btn btn-primary btn-small remove_source_branch" do - %i.icon-remove + = link_to namespace_project_branch_path(@merge_request.source_project.namespace, @merge_request.source_project, @source_branch), remote: true, method: :delete, class: "btn btn-primary btn-sm remove_source_branch" do + %i.fa.fa-times Remove Source Branch .remove_source_branch_widget.failed.hide Failed to remove source branch '#{@merge_request.source_branch}' .remove_source_branch_in_progress.hide - %i.icon-refresh.icon-spin + %i.fa.fa-spinner.fa-spin   Removing source branch '#{@merge_request.source_branch}'. Please wait. Page will be automatically reloaded.   diff --git a/app/views/projects/merge_requests/show/_state_widget.html.haml b/app/views/projects/merge_requests/show/_state_widget.html.haml index c30310f125..44bd9347f5 100644 --- a/app/views/projects/merge_requests/show/_state_widget.html.haml +++ b/app/views/projects/merge_requests/show/_state_widget.html.haml @@ -1,8 +1,8 @@ -.panel.mr-state-widget.panel-default +.mr-state-widget - if @merge_request.source_project.ci_service && @commits.any? - .panel-heading + .mr-widget-heading = render "projects/merge_requests/show/mr_ci" - .panel-body + .mr-widget-body - if @merge_request.open? - if @merge_request.source_branch_exists? && @merge_request.target_branch_exists? = render "projects/merge_requests/show/mr_accept" @@ -11,16 +11,26 @@ - if @merge_request.closed? %h4 - Closed by #{link_to_member(@project, @merge_request.closed_event.author, avatar: false)} - #{time_ago_with_tooltip(@merge_request.closed_event.created_at)} - %p Changes was not merged into target branch + Closed + - if @merge_request.closed_event + by #{link_to_member(@project, @merge_request.closed_event.author, avatar: false)} + #{time_ago_with_tooltip(@merge_request.closed_event.created_at)} + %p Changes were not merged into target branch - if @merge_request.merged? %h4 - Merged by #{link_to_member(@project, @merge_request.merge_event.author, avatar: false)} - #{time_ago_with_tooltip(@merge_request.merge_event.created_at)} + Merged + - if @merge_request.merge_event + by #{link_to_member(@project, @merge_request.merge_event.author, avatar: false)} + #{time_ago_with_tooltip(@merge_request.merge_event.created_at)} = render "projects/merge_requests/show/remove_source_branch" + - if @merge_request.locked? + %h4 + Merge in progress... + %p + Merging is in progress. While merging this request is locked and cannot be closed. + - unless @commits.any? %h4 Nothing to merge %p @@ -31,11 +41,10 @@ %br Try to use different branches or push new code. - - if !@closes_issues.empty? && @merge_request.open? - .panel-footer + - if @closes_issues.present? && @merge_request.open? + .mr-widget-footer %span - %i.icon-ok + %i.fa.fa-check Accepting this merge request will close #{@closes_issues.size == 1 ? 'issue' : 'issues'} = succeed '.' do != gfm(issues_sentence(@closes_issues)) - diff --git a/app/views/projects/merge_requests/update.js.haml b/app/views/projects/merge_requests/update.js.haml index 6452cc6382..b4df1d2073 100644 --- a/app/views/projects/merge_requests/update.js.haml +++ b/app/views/projects/merge_requests/update.js.haml @@ -1,2 +1,8 @@ - if params[:merge_request_context] - $('.issue-box .context').effect('highlight'); + $('.context').html("#{escape_javascript(render partial: 'projects/merge_requests/show/context', locals: { issue: @issue })}"); + $('.context').effect('highlight'); + + new UsersSelect() + + $('select.select2').select2({width: 'resolve', dropdownAutoWidth: true}); + merge_request = new MergeRequest(); diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml index df79125eae..95b7070ce5 100644 --- a/app/views/projects/milestones/_form.html.haml +++ b/app/views/projects/milestones/_form.html.haml @@ -1,11 +1,11 @@ %h3.page-title= @milestone.new_record? ? "New Milestone" : "Edit Milestone ##{@milestone.iid}" .back-link - = link_to project_milestones_path(@project) do + = link_to namespace_project_milestones_path(@project.namespace, @project) do ← To milestones %hr -= form_for [@project, @milestone], html: {class: "new_milestone form-horizontal"} do |f| += form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form'} do |f| -if @milestone.errors.any? .alert.alert-danger %ul @@ -18,13 +18,14 @@ .col-sm-10 = f.text_field :title, maxlength: 255, class: "form-control" %p.hint Required - .form-group + .form-group.milestone-description = f.label :description, "Description", class: "control-label" .col-sm-10 - = f.text_area :description, maxlength: 65535, class: "form-control markdown-area", rows: 10 - .hint - .pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}. - .pull-left Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. + = render layout: 'projects/md_preview', locals: { preview_class: "wiki" } do + = render 'projects/zen', f: f, attr: :description, classes: 'description form-control' + .hint + .pull-left Milestones are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'}. + .pull-left Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. .clearfix .error-alert .col-md-6 @@ -37,10 +38,10 @@ .form-actions - if @milestone.new_record? = f.submit 'Create milestone', class: "btn-create btn" - = link_to "Cancel", project_milestones_path(@project), class: "btn btn-cancel" + = link_to "Cancel", namespace_project_milestones_path(@project.namespace, @project), class: "btn btn-cancel" -else = f.submit 'Save changes', class: "btn-save btn" - = link_to "Cancel", project_milestone_path(@project, @milestone), class: "btn btn-cancel" + = link_to "Cancel", namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-cancel" :javascript @@ -50,4 +51,4 @@ onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) } }).datepicker("setDate", $.datepicker.parseDate('yy-mm-dd', $('#milestone_due_date').val())); - window.project_image_path_upload = "#{upload_image_project_path @project}"; + window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; diff --git a/app/views/projects/milestones/_issue.html.haml b/app/views/projects/milestones/_issue.html.haml index b5ec0fc988..88fccfe498 100644 --- a/app/views/projects/milestones/_issue.html.haml +++ b/app/views/projects/milestones/_issue.html.haml @@ -1,9 +1,9 @@ -%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => project_issue_path(@project, issue) } - %span.str-truncated - = link_to [@project, issue] do - %span.cgray ##{issue.iid} - = link_to_gfm issue.title, [@project, issue], title: issue.title +%li{ id: dom_id(issue, 'sortable'), class: 'issue-row', 'data-iid' => issue.iid, 'data-url' => issue_path(issue) } .pull-right.assignee-icon - if issue.assignee - = image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16" + = image_tag avatar_icon(issue.assignee.email, 16), class: "avatar s16", alt: '' + %span + = link_to [@project.namespace.becomes(Namespace), @project, issue] do + %span.cgray ##{issue.iid} + = link_to_gfm issue.title, [@project.namespace.becomes(Namespace), @project, issue], title: issue.title diff --git a/app/views/projects/milestones/_merge_request.html.haml b/app/views/projects/milestones/_merge_request.html.haml index d54cb3f8e7..0d7a118569 100644 --- a/app/views/projects/milestones/_merge_request.html.haml +++ b/app/views/projects/milestones/_merge_request.html.haml @@ -1,5 +1,8 @@ -%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid, 'data-url' => project_merge_request_path(@project, merge_request) } +%li{ id: dom_id(merge_request, 'sortable'), class: 'mr-row', 'data-iid' => merge_request.iid, 'data-url' => merge_request_path(merge_request) } %span.str-truncated - = link_to [@project, merge_request] do + = link_to [@project.namespace.becomes(Namespace), @project, merge_request] do %span.cgray ##{merge_request.iid} - = link_to_gfm merge_request.title, [@project, merge_request], title: merge_request.title + = link_to_gfm merge_request.title, [@project.namespace.becomes(Namespace), @project, merge_request], title: merge_request.title + .pull-right.assignee-icon + - if merge_request.assignee + = image_tag avatar_icon(merge_request.assignee.email, 16), class: "avatar s16", alt: '' diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml index 4018d132a5..62360158ff 100644 --- a/app/views/projects/milestones/_milestone.html.haml +++ b/app/views/projects/milestones/_milestone.html.haml @@ -1,27 +1,24 @@ %li{class: "milestone milestone-#{milestone.closed? ? 'closed' : 'open'}", id: dom_id(milestone) } .pull-right - if can?(current_user, :admin_milestone, milestone.project) and milestone.active? - = link_to edit_project_milestone_path(milestone.project, milestone), class: "btn btn-small edit-milestone-link btn-grouped" do - %i.icon-edit + = link_to edit_namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), class: "btn btn-sm edit-milestone-link btn-grouped" do + %i.fa.fa-pencil-square-o Edit - = link_to 'Close Milestone', project_milestone_path(@project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-small btn-close" + = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, milestone, milestone: {state_event: :close }), method: :put, remote: true, class: "btn btn-sm btn-close" %h4 - = link_to_gfm truncate(milestone.title, length: 100), project_milestone_path(milestone.project, milestone) + = link_to_gfm truncate(milestone.title, length: 100), namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone) - if milestone.expired? and not milestone.closed? %span.cred (Expired) %small = milestone.expires_at - - if milestone.is_empty? - %span.muted Empty - - else - %div - %div - = link_to project_issues_path(milestone.project, milestone_id: milestone.id) do - = pluralize milestone.issues.count, 'Issue' -   - = link_to project_merge_requests_path(milestone.project, milestone_id: milestone.id) do - = pluralize milestone.merge_requests.count, 'Merge Request' -   - %span.light #{milestone.percent_complete}% complete - .progress.progress-info - .progress-bar{style: "width: #{milestone.percent_complete}%;"} + .row + .col-sm-6 + = link_to namespace_project_issues_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do + = pluralize milestone.issues.count, 'Issue' +   + = link_to namespace_project_merge_requests_path(milestone.project.namespace, milestone.project, milestone_id: milestone.id) do + = pluralize milestone.merge_requests.count, 'Merge Request' +   + %span.light #{milestone.percent_complete}% complete + .col-sm-6 + = milestone_progress_bar(milestone) diff --git a/app/views/projects/milestones/index.html.haml b/app/views/projects/milestones/index.html.haml index f0e48a5177..d3eab8d6d7 100644 --- a/app/views/projects/milestones/index.html.haml +++ b/app/views/projects/milestones/index.html.haml @@ -1,33 +1,17 @@ -= render "projects/issues/head" -.milestones_content - %h3.page-title - Milestones - - if can? current_user, :admin_milestone, @project - = link_to new_project_milestone_path(@project), class: "pull-right btn btn-new", title: "New Milestone" do - %i.icon-plus - New Milestone +.pull-right + - if can? current_user, :admin_milestone, @project + = link_to new_namespace_project_milestone_path(@project.namespace, @project), class: "pull-right btn btn-new", title: "New Milestone" do + %i.fa.fa-plus + New Milestone += render 'shared/milestones_filter' - .row - .fixed.sidebar-expand-button.hidden-lg.hidden-md.hidden-xs - %i.icon-list.icon-2x - .col-md-3.responsive-side - %ul.nav.nav-pills.nav-stacked - %li{class: ("active" if (params[:f] == "active" || !params[:f]))} - = link_to project_milestones_path(@project, f: "active") do - Active - %li{class: ("active" if params[:f] == "closed")} - = link_to project_milestones_path(@project, f: "closed") do - Closed - %li{class: ("active" if params[:f] == "all")} - = link_to project_milestones_path(@project, f: "all") do - All - .col-md-9 - .panel.panel-default - %ul.well-list - = render @milestones +.milestones + .panel.panel-default + %ul.well-list + = render @milestones - - if @milestones.blank? - %li - .nothing-here-block No milestones to show + - if @milestones.blank? + %li + .nothing-here-block No milestones to show - = paginate @milestones, theme: "gitlab" + = paginate @milestones, theme: "gitlab" diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml index 42c3f45f6c..25cc003096 100644 --- a/app/views/projects/milestones/show.html.haml +++ b/app/views/projects/milestones/show.html.haml @@ -1,55 +1,49 @@ -= render "projects/issues/head" -%h3.page-title +%h4.page-title + .issue-box{ class: issue_box_class(@milestone) } + - if @milestone.closed? + Closed + - elsif @milestone.expired? + Expired + - else + Open Milestone ##{@milestone.iid} + %small.creator + = @milestone.expires_at .pull-right - if can?(current_user, :admin_milestone, @project) - = link_to edit_project_milestone_path(@project, @milestone), class: "btn btn-grouped" do - %i.icon-edit + = link_to edit_namespace_project_milestone_path(@project.namespace, @project, @milestone), class: "btn btn-grouped" do + %i.fa.fa-pencil-square-o Edit - if @milestone.active? - = link_to 'Close Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-grouped" + = link_to 'Close Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :close }), method: :put, class: "btn btn-close btn-grouped" - else - = link_to 'Reopen Milestone', project_milestone_path(@project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped" + = link_to 'Reopen Milestone', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped" +%hr - if @milestone.issues.any? && @milestone.can_be_closed? .alert.alert-success %span All issues for this milestone are closed. You may close milestone now. -.back-link - = link_to project_milestones_path(@project) do - ← To milestones list - - -.issue-box{ class: issue_box_class(@milestone) } - .state.clearfix - .state-label - - if @milestone.closed? - Closed - - elsif @milestone.expired? - Expired - - else - Open - .creator - = @milestone.expires_at - - %h4.title - = gfm escape_once(@milestone.title) - +%h3.issue-title + = gfm escape_once(@milestone.title) +%div - if @milestone.description.present? .description .wiki = preserve do = markdown @milestone.description - .context - %p - Progress: - #{@milestone.closed_items_count} closed - – - #{@milestone.open_items_count} open - %span.pull-right= @milestone.expires_at - .progress.progress-info - .progress-bar{style: "width: #{@milestone.percent_complete}%;"} +%hr +.context + %p.lead + Progress: + #{@milestone.closed_items_count} closed + – + #{@milestone.open_items_count} open +   + %span.light #{@milestone.percent_complete}% complete + %span.pull-right= @milestone.expires_at + = milestone_progress_bar(@milestone) %ul.nav.nav-tabs @@ -66,11 +60,12 @@ Participants %span.badge= @users.count - .pull-right - = link_to new_project_issue_path(@project, issue: { milestone_id: @milestone.id }), class: "btn btn-small btn-grouped", title: "New Issue" do - %i.icon-plus - New Issue - = link_to 'Browse Issues', project_issues_path(@milestone.project, milestone_id: @milestone.id), class: "btn btn-small edit-milestone-link btn-grouped" + - if @project.issues_enabled + .pull-right + = link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { milestone_id: @milestone.id }), class: "btn btn-grouped", title: "New Issue" do + %i.fa.fa-plus + New Issue + = link_to 'Browse Issues', namespace_project_issues_path(@milestone.project.namespace, @milestone.project, milestone_id: @milestone.id), class: "btn edit-milestone-link btn-grouped" .tab-content .tab-pane.active#tab-issues diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml index 5310822823..c36bad1e94 100644 --- a/app/views/projects/network/show.html.haml +++ b/app/views/projects/network/show.html.haml @@ -1,10 +1,10 @@ = render "head" .project-network .controls - = form_tag project_network_path(@project, @id), method: :get, class: 'form-inline network-form' do |f| - = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Input an extended SHA1 syntax", class: "search-input form-control input-mx-250" - = button_tag type: 'submit', class: 'btn btn-success' do - %i.icon-search + = form_tag namespace_project_network_path(@project.namespace, @project, @id), method: :get, class: 'form-inline network-form' do |f| + = text_field_tag :extended_sha1, @options[:extended_sha1], placeholder: "Input an extended SHA1 syntax", class: 'search-input form-control input-mx-250 search-sha' + = button_tag class: 'btn btn-success btn-search-sha' do + %i.fa.fa-search .inline.prepend-left-20 .checkbox.light = label_tag :filter_ref do @@ -15,9 +15,12 @@ = spinner nil, true :javascript - new Network({ - url: '#{project_network_path(@project, @ref, @options.merge(format: :json))}', - commit_url: '#{project_commit_path(@project, 'ae45ca32').gsub("ae45ca32", "%s")}', + disableButtonIfEmptyField('#extended_sha1', '.btn-search-sha') + + network_graph = new Network({ + url: '#{namespace_project_network_path(@project.namespace, @project, @ref, @options.merge(format: :json))}', + commit_url: '#{namespace_project_commit_path(@project.namespace, @project, 'ae45ca32').gsub("ae45ca32", "%s")}', ref: '#{@ref}', commit_id: '#{@commit.id}' }) + new ShortcutsNetwork(network_graph.branch_graph) diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 7efaf5a087..a06c85b425 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -3,12 +3,15 @@ = render 'projects/errors' .project-edit-content - = form_for @project, remote: true, html: { class: 'new_project form-horizontal' } do |f| + = form_for @project, html: { class: 'new_project form-horizontal' } do |f| .form-group.project-name-holder - = f.label :name, class: 'control-label' do - %strong Project name + = f.label :path, class: 'control-label' do + %strong Project path .col-sm-10 - = f.text_field :name, placeholder: "Example Project", class: "form-control", tabindex: 1, autofocus: true + .input-group + = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 1, autofocus: true + .input-group-addon + \.git - if current_user.can_select_namespace? .form-group @@ -18,40 +21,71 @@ = f.select :namespace_id, namespaces_options(params[:namespace_id] || :current_user), {}, {class: 'select2', tabindex: 2} %hr - .js-toggle-container - .form-group - .col-sm-2 - .col-sm-10 - = link_to "#", class: 'js-toggle-button' do - %i.icon-edit - %span Customize repository name? - .js-toggle-content.hide - .form-group - = f.label :path, class: 'control-label' do - %span Repository name - .col-sm-10 - .input-group - = f.text_field :path, class: 'form-control' - %span.input-group-addon .git - .js-toggle-container + .project-import.js-toggle-container .form-group - .col-sm-2 + %label.control-label Import project from .col-sm-10 - = link_to "#", class: 'js-toggle-button' do - %i.icon-upload-alt - %span Import existing repository? + - if github_import_enabled? + = link_to status_import_github_path, class: 'btn' do + %i.fa.fa-github + GitHub + - else + = link_to '#', class: 'how_to_import_link light btn' do + %i.fa.fa-github + GitHub + = render 'github_import_modal' + + + - if bitbucket_import_enabled? + = link_to status_import_bitbucket_path, class: 'btn' do + %i.fa.fa-bitbucket + Bitbucket + - else + = link_to '#', class: 'how_to_import_link light btn' do + %i.fa.fa-bitbucket + Bitbucket + = render 'bitbucket_import_modal' + + - unless request.host == 'gitlab.com' + - if gitlab_import_enabled? + = link_to status_import_gitlab_path, class: 'btn' do + %i.fa.fa-heart + GitLab.com + - else + = link_to '#', class: 'how_to_import_link light btn' do + %i.fa.fa-heart + GitLab.com + = render 'gitlab_import_modal' + + = link_to new_import_gitorious_path, class: 'btn' do + %i.icon-gitorious.icon-gitorious-small + Gitorious.org + + = link_to new_import_google_code_path, class: 'btn' do + %i.fa.fa-google + Google Code + + = link_to "#", class: 'btn js-toggle-button' do + %i.fa.fa-git + %span Any repo by URL + .js-toggle-content.hide .form-group.import-url-data = f.label :import_url, class: 'control-label' do - %span Import existing repo + %span Git repository URL .col-sm-10 - = f.text_field :import_url, class: 'form-control', placeholder: 'https://github.com/randx/six.git' - .bs-callout.bs-callout-info - This url must be publicly accessible or you can add a username and password like this: https://username:password@gitlab.com/company/project.git. - %br - The import will time out after 2 minutes. For big repositories, use a clone/push combination. - %hr + = f.text_field :import_url, class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git' + .well.prepend-top-20 + %ul + %li + The repository must be accessible over HTTP(S). If it is not publicly accessible, you can add authentication information to the URL: https://username:password@gitlab.company.com/group/project.git. + %li + The import will time out after 4 minutes. For big repositories, use a clone/push combination. + %li + To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/migrating_from_svn.html"}. + + %hr.prepend-botton-10 .form-group = f.label :description, class: 'control-label' do @@ -68,12 +102,19 @@ .pull-right .light Need a group for several dependent projects? - = link_to new_group_path, class: "btn btn-tiny" do + = link_to new_group_path, class: "btn btn-xs" do Create a group .save-project-loader.hide .center %h2 - %i.icon-spinner.icon-spin + %i.fa.fa-spinner.fa-spin Creating project & repository. %p Please wait a moment, this page will automatically refresh when ready. + +:coffeescript + $('.how_to_import_link').bind 'click', (e) -> + e.preventDefault() + import_modal = $(this).next(".modal").show() + $('.modal-header .close').bind 'click', -> + $(".modal").hide() diff --git a/app/views/projects/new_tree/show.html.haml b/app/views/projects/new_tree/show.html.haml deleted file mode 100644 index 9ecbbe7508..0000000000 --- a/app/views/projects/new_tree/show.html.haml +++ /dev/null @@ -1,54 +0,0 @@ -%h3.page-title New file -%hr -.file-editor - = form_tag(project_new_tree_path(@project, @id), method: :put, class: "form-horizontal") do - .form-group.commit_message-group - = label_tag 'file_name', class: "control-label" do - File name - .col-sm-10 - .input-group - %span.input-group-addon - = @path[-1] == "/" ? @path : @path + "/" - = text_field_tag 'file_name', params[:file_name], placeholder: "sample.rb", required: true, class: 'form-control' - %span.input-group-addon - on - %span= @ref - - .form-group.commit_message-group - = label_tag :encoding, class: "control-label" do - Encoding - .col-sm-10 - = select_tag :encoding, options_for_select([ "base64", "text" ], "text"), class: 'form-control' - - .form-group.commit_message-group - = label_tag 'commit_message', class: "control-label" do - Commit message - .col-sm-10 - = render 'shared/commit_message_container', {textarea: text_area_tag('commit_message', - params[:commit_message], placeholder: "Added new file", required: true, rows: 3, class: 'form-control')} - - .file-holder - .file-title - %i.icon-file - .file-content.code - %pre#editor= params[:content] - - .form-actions - = hidden_field_tag 'content', '', id: "file-content" - .commit-button-annotation - = button_tag "Commit changes", class: 'btn commit-btn js-commit-button btn-create' - .message - to branch - %strong= @ref - = link_to "Cancel", project_tree_path(@project, @id), class: "btn btn-cancel", data: { confirm: leave_edit_message} - -:javascript - ace.config.set("modePath", gon.relative_url_root + "#{Gitlab::Application.config.assets.prefix}/ace-src-noconflict") - var editor = ace.edit("editor"); - - disableButtonIfEmptyField("#commit_message", ".js-commit-button"); - - $(".js-commit-button").click(function(){ - $("#file-content").val(editor.getValue()); - $(".file-editor form").submit(); - }); diff --git a/app/views/projects/no_repo.html.haml b/app/views/projects/no_repo.html.haml new file mode 100644 index 0000000000..720957e833 --- /dev/null +++ b/app/views/projects/no_repo.html.haml @@ -0,0 +1,22 @@ +%h2 + %i.fa.fa-warning + No repository + +%p.slead + The repository for this project does not exist. + %br + This means you can not push code until you create an empty repository or import existing one. +%hr + +.no-repo-actions + = link_to namespace_project_repository_path(@project.namespace, @project), method: :post, class: 'btn btn-primary' do + Create empty bare repository + + %strong.prepend-left-10.append-right-10 or + + = link_to new_namespace_project_import_path(@project.namespace, @project), class: 'btn' do + Import repository + +- if can? current_user, :remove_project, @project + .prepend-top-20 + = link_to 'Remove project', project_path(@project), data: { confirm: remove_project_message(@project)}, method: :delete, class: "btn btn-remove pull-right" diff --git a/app/views/projects/notes/_diff_note_link.html.haml b/app/views/projects/notes/_diff_note_link.html.haml deleted file mode 100644 index 377c926a20..0000000000 --- a/app/views/projects/notes/_diff_note_link.html.haml +++ /dev/null @@ -1,10 +0,0 @@ -- note = @project.notes.new(@comments_target.merge({ line_code: line_code })) -= link_to "", - "javascript:;", - class: "add-diff-note js-add-diff-note-button", - 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 }, - title: "Add a comment to this line" 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 79a66eff12..c731baf0a6 100644 --- a/app/views/projects/notes/_diff_notes_with_reply.html.haml +++ b/app/views/projects/notes/_diff_notes_with_reply.html.haml @@ -3,8 +3,8 @@ - if !defined?(line) || line == note.diff_line %tr.notes_holder %td.notes_line{ colspan: 2 } - %span.btn.disabled - %i.icon-comment + %span.discussion-notes-count + %i.fa.fa-comment = notes.count %td.notes_content %ul.notes{ rel: note.discussion_id } 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 8adf903a9a..789f3e19fd 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,14 +1,13 @@ -- note1 = notes1.first # example note -- note2 = notes2.first # example note --# Check if line want not changed since comment was left -/- if !defined?(line) || line == note.diff_line +- note1 = notes1.present? ? notes1.first : nil +- note2 = notes2.present? ? notes2.first : nil + %tr.notes_holder - if note1 %td.notes_line %span.btn.disabled - %i.icon-comment + %i.fa.fa-comment = notes1.count - %td.notes_content + %td.notes_content.parallel %ul.notes{ rel: note1.discussion_id } = render notes1 @@ -21,9 +20,9 @@ - if note2 %td.notes_line %span.btn.disabled - %i.icon-comment + %i.fa.fa-comment = notes2.count - %td.notes_content + %td.notes_content.parallel %ul.notes{ rel: note2.discussion_id } = render notes2 diff --git a/app/views/projects/notes/_discussion.html.haml b/app/views/projects/notes/_discussion.html.haml index 8c7964cbf3..b8068835b3 100644 --- a/app/views/projects/notes/_discussion.html.haml +++ b/app/views/projects/notes/_discussion.html.haml @@ -1,8 +1,13 @@ - note = discussion_notes.first -- if note.for_merge_request? - - if note.outdated? - = render "projects/notes/discussions/outdated", discussion_notes: discussion_notes - - else - = render "projects/notes/discussions/active", discussion_notes: discussion_notes -- else - = render "projects/notes/discussions/commit", discussion_notes: discussion_notes +.timeline-entry + .timeline-entry-inner + .timeline-icon + = link_to user_path(note.author) do + = image_tag avatar_icon(note.author_email), 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 diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml new file mode 100644 index 0000000000..acb3991d29 --- /dev/null +++ b/app/views/projects/notes/_edit_form.html.haml @@ -0,0 +1,15 @@ +.note-edit-form + = form_for note, url: namespace_project_note_path(@project.namespace, @project, note), method: :put, remote: true, authenticity_token: true do |f| + = note_target_fields(note) + = render layout: 'projects/md_preview', locals: { preview_class: "note-text" } do + = render 'projects/zen', f: f, attr: :note, + classes: 'note_text js-note-text' + + .comment-hints.clearfix + .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }} + .pull-right Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }. + + .note-form-actions + .buttons + = f.submit 'Save Comment', class: "btn btn-primary btn-save btn-grouped js-comment-button" + = link_to 'Cancel', "#", class: "btn btn-cancel note-edit-cancel" \ No newline at end of file diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml index 5ebafb13f1..2ada6cb670 100644 --- a/app/views/projects/notes/_form.html.haml +++ b/app/views/projects/notes/_form.html.haml @@ -1,27 +1,18 @@ -= form_for [@project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new_note js-new-note-form common-note-form" }, authenticity_token: true do |f| - = note_target_fields += form_for [@project.namespace.becomes(Namespace), @project, @note], remote: true, html: { :'data-type' => 'json', multipart: true, id: nil, class: "new_note js-new-note-form common-note-form gfm-form" }, authenticity_token: true do |f| + = note_target_fields(@note) = f.hidden_field :commit_id = f.hidden_field :line_code = f.hidden_field :noteable_id = f.hidden_field :noteable_type - %ul.nav.nav-tabs - %li.active - = link_to '#note-write-holder', class: 'js-note-write-button' do - Write - %li - = link_to '#note-preview-holder', class: 'js-note-preview-button', data: { url: preview_project_notes_path(@project) } do - Preview - %div - .note-write-holder - = f.text_area :note, size: 255, class: 'note_text js-note-text js-gfm-input markdown-area' + = render layout: 'projects/md_preview', locals: { preview_class: "note-text" } do + = render 'projects/zen', f: f, attr: :note, + classes: 'note_text js-note-text' - .light.clearfix - .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }} - .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }. - - .note-preview-holder.hide - .js-note-preview + .comment-hints.clearfix + .pull-left Comments are parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }} + .pull-right Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }. + .error-alert .note-form-actions .buttons @@ -29,13 +20,5 @@ = yield(:note_actions) %a.btn.grouped.js-close-discussion-note-form Cancel - .note-form-option - %a.choose-btn.btn.js-choose-note-attachment-button - %i.icon-paper-clip - %span Choose File ... -   - %span.file_name.js-attachment-filename File name... - = f.file_field :attachment, class: "js-note-attachment-input hidden" - :javascript - window.project_image_path_upload = "#{upload_image_project_path @project}"; + window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml index 5e84aed0cc..0728f8fa42 100644 --- a/app/views/projects/notes/_note.html.haml +++ b/app/views/projects/notes/_note.html.haml @@ -1,66 +1,71 @@ -%li{ id: dom_id(note), class: dom_class(note), data: { discussion: note.discussion_id } } - .note-header - .note-actions - = link_to "##{dom_id(note)}", name: dom_id(note) do - %i.icon-link - Link here -   - - if(note.author_id == current_user.try(:id)) || can?(current_user, :admin_note, @project) - = link_to "#", title: "Edit comment", class: "js-note-edit" do - %i.icon-edit - Edit -   - = link_to project_note_path(@project, note), title: "Remove comment", method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: "danger js-note-delete" do - %i.icon-trash.cred - Remove - = image_tag avatar_icon(note.author_email), class: "avatar s32" - = link_to_member(@project, note.author, avatar: false) - %span.note-last-update - = note_timestamp(note) - - - if note.upvote? - %span.vote.upvote.label.label-success - %i.icon-thumbs-up - \+1 - - if note.downvote? - %span.vote.downvote.label.label-danger - %i.icon-thumbs-down - \-1 - - - .note-body - .note-text - = preserve do - = markdown(note.note, {no_header_anchors: true}) - - .note-edit-form - = form_for note, url: project_note_path(@project, note), method: :put, remote: true, authenticity_token: true do |f| - = f.text_area :note, class: 'note_text js-note-text js-gfm-input turn-on' - - .form-actions.clearfix - = f.submit 'Save changes', class: "btn btn-primary btn-save js-comment-button" - - .note-form-option - %a.choose-btn.btn.js-choose-note-attachment-button - %i.icon-paper-clip - %span Choose File ... +%li.timeline-entry{ id: dom_id(note), class: [dom_class(note), "note-row-#{note.id}", ('system-note' if note.system)], data: { discussion: note.discussion_id } } + .timeline-entry-inner + .timeline-icon + - if note.system + %span.fa.fa-circle + - else + = link_to user_path(note.author) do + = image_tag avatar_icon(note.author_email), class: "avatar s40", alt: '' + .timeline-content + .note-header + .note-actions + = link_to "##{dom_id(note)}", name: dom_id(note) do + %i.fa.fa-link + Link here +   + - if can?(current_user, :admin_note, note) && note.editable? + = link_to "#", title: "Edit comment", class: "js-note-edit" do + %i.fa.fa-pencil-square-o + Edit   - %span.file_name.js-attachment-filename File name... - = f.file_field :attachment, class: "js-note-attachment-input hidden" + = link_to namespace_project_note_path(@project.namespace, @project, note), title: "Remove comment", method: :delete, data: { confirm: 'Are you sure you want to remove this comment?' }, remote: true, class: "danger js-note-delete" do + %i.fa.fa-trash-o.cred + Remove + - if note.system + = link_to user_path(note.author) do + = image_tag avatar_icon(note.author_email), class: "avatar s16", alt: '' + = link_to_member(@project, note.author, avatar: false) + %span.author-username + = '@' + note.author.username + %span.note-last-update + = note_timestamp(note) - = link_to 'Cancel', "#", class: "btn btn-cancel note-edit-cancel" + - if note.superceded?(@notes) + - if note.upvote? + %span.vote.upvote.label.label-gray.strikethrough + %i.fa.fa-thumbs-up + \+1 + - if note.downvote? + %span.vote.downvote.label.label-gray.strikethrough + %i.fa.fa-thumbs-down + \-1 + - else + - if note.upvote? + %span.vote.upvote.label.label-success + %i.fa.fa-thumbs-up + \+1 + - if note.downvote? + %span.vote.downvote.label.label-danger + %i.fa.fa-thumbs-down + \-1 - - if note.attachment.url - .note-attachment - - if note.attachment.image? - = link_to note.attachment.secure_url, target: '_blank' do - = image_tag note.attachment.secure_url, class: 'note-image-attach' - .attachment.pull-right - = link_to note.attachment.secure_url, target: "_blank" do - %i.icon-paper-clip - = note.attachment_identifier - = link_to delete_attachment_project_note_path(@project, note), - title: "Delete this attachment", method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: "danger js-note-attachment-delete" do - %i.icon-trash.cred - .clear + .note-body + .note-text + = preserve do + = markdown(note.note, {no_header_anchors: true}) + = render 'projects/notes/edit_form', note: note + + - if note.attachment.url + .note-attachment + - if note.attachment.image? + = link_to note.attachment.url, target: '_blank' do + = image_tag note.attachment.url, class: 'note-image-attach' + .attachment + = link_to note.attachment.url, target: "_blank" do + %i.fa.fa-paperclip + = note.attachment_identifier + = link_to delete_attachment_namespace_project_note_path(@project.namespace, @project, note), + title: "Delete this attachment", method: :delete, remote: true, data: { confirm: 'Are you sure you want to remove the attachment?' }, class: "danger js-note-attachment-delete" do + %i.fa.fa-trash-o.cred + .clear diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml index 052661962e..813e37276b 100644 --- a/app/views/projects/notes/_notes_with_form.html.haml +++ b/app/views/projects/notes/_notes_with_form.html.haml @@ -1,4 +1,4 @@ -%ul#notes-list.notes.main-notes-list +%ul#notes-list.notes.main-notes-list.timeline = render "projects/notes/notes" .js-notes-busy @@ -7,4 +7,4 @@ = render "projects/notes/form" :javascript - new Notes("#{project_notes_path(target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}) + new Notes("#{namespace_project_notes_path(namespace_id: @project.namespace, target_id: @noteable.id, target_type: @noteable.class.name.underscore)}", #{@notes.map(&:id).to_json}, #{Time.now.to_i}) diff --git a/app/views/projects/notes/discussions/_active.html.haml b/app/views/projects/notes/discussions/_active.html.haml index ef296b35dd..7c6f724317 100644 --- a/app/views/projects/notes/discussions/_active.html.haml +++ b/app/views/projects/notes/discussions/_active.html.haml @@ -3,13 +3,12 @@ .discussion-header .discussion-actions = link_to "#", class: "js-toggle-button" do - %i.icon-chevron-up + %i.fa.fa-chevron-up Show/hide discussion - = image_tag avatar_icon(note.author_email), class: "avatar s32" %div = link_to_member(@project, note.author, avatar: false) started a discussion - = link_to diffs_project_merge_request_path(note.project, note.noteable, anchor: note.line_code) do + = link_to diffs_namespace_project_merge_request_path(note.project.namespace, note.project, note.noteable, anchor: note.line_code) do %strong on the diff .last-update.hide.js-toggle-content - last_note = discussion_notes.last diff --git a/app/views/projects/notes/discussions/_commit.html.haml b/app/views/projects/notes/discussions/_commit.html.haml index 78460974a9..62609cfc1c 100644 --- a/app/views/projects/notes/discussions/_commit.html.haml +++ b/app/views/projects/notes/discussions/_commit.html.haml @@ -3,13 +3,12 @@ .discussion-header .discussion-actions = link_to "#", class: "js-toggle-button" do - %i.icon-chevron-up + %i.fa.fa-chevron-up Show/hide discussion - = image_tag avatar_icon(note.author_email), class: "avatar s32" %div = link_to_member(@project, note.author, avatar: false) started a discussion on commit - = link_to(note.noteable.short_id, project_commit_path(note.project, note.noteable), class: 'monospace') + = link_to(note.noteable.short_id, namespace_project_commit_path(note.project.namespace, note.project, note.noteable), class: 'monospace') .last-update.hide.js-toggle-content - last_note = discussion_notes.last last updated by diff --git a/app/views/projects/notes/discussions/_diff.html.haml b/app/views/projects/notes/discussions/_diff.html.haml index 26c5494f46..711aa39101 100644 --- a/app/views/projects/notes/discussions/_diff.html.haml +++ b/app/views/projects/notes/discussions/_diff.html.haml @@ -2,25 +2,28 @@ - if diff .diff-file .diff-header - - if diff.deleted_file - %span= diff.old_path - - else - %span= 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}" - %br/ + %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 %table - - each_diff_line_near(diff, note.diff_file_index, note.line_code) do |line, type, line_code, line_new, line_old| + - note.truncated_diff_lines.each do |line| + - line_code = generate_line_code(note.file_path, line) %tr.line_holder{ id: line_code } - - if type == "match" + - if line.type == "match" %td.old_line= "..." %td.new_line= "..." - %td.line_content.matched= line + %td.line_content.matched= line.text - else - %td.old_line= raw(type == "new" ? " " : line_old) - %td.new_line= raw(type == "old" ? " " : line_new) - %td.line_content{class: "noteable_line #{type} #{line_code}", "line_code" => line_code}= raw "#{line}  " + %td.old_line{class: line.type == "new" ? "new" : "old"} + = raw(line.type == "new" ? " " : line.old_pos) + %td.new_line{class: line.type == "new" ? "new" : "old"} + = raw(line.type == "old" ? " " : line.new_pos) + %td.line_content{class: "noteable_line #{line.type} #{line_code}", "line_code" => line_code}= raw diff_line_content(line.text) - if line_code == note.line_code = render "projects/notes/diff_notes_with_reply", notes: discussion_notes diff --git a/app/views/projects/notes/discussions/_outdated.html.haml b/app/views/projects/notes/discussions/_outdated.html.haml index 67c29be8ac..52a1d342f5 100644 --- a/app/views/projects/notes/discussions/_outdated.html.haml +++ b/app/views/projects/notes/discussions/_outdated.html.haml @@ -3,9 +3,8 @@ .discussion-header .discussion-actions = link_to "#", class: "js-toggle-button" do - %i.icon-chevron-down + %i.fa.fa-chevron-down Show/hide discussion - = image_tag avatar_icon(note.author_email), class: "avatar s32" %div = link_to_member(@project, note.author, avatar: false) started a discussion on the diff --git a/app/views/projects/project_members/_group_members.html.haml b/app/views/projects/project_members/_group_members.html.haml new file mode 100644 index 0000000000..43e92437cf --- /dev/null +++ b/app/views/projects/project_members/_group_members.html.haml @@ -0,0 +1,16 @@ +.panel.panel-default + .panel-heading + %strong #{@group.name} + group members + %small + (#{members.count}) + .panel-head-actions + = link_to group_group_members_path(@group), class: 'btn btn-sm' do + %i.fa.fa-pencil-square-o + Edit group members + %ul.well-list + - members.each do |member| + = render 'groups/group_members/group_member', member: member, show_controls: false + - if members.count > 20 + %li + and #{members.count - 20} more. For full list visit #{link_to 'group members page', group_group_members_path(@group)} diff --git a/app/views/projects/project_members/_new_project_member.html.haml b/app/views/projects/project_members/_new_project_member.html.haml new file mode 100644 index 0000000000..d708b01a11 --- /dev/null +++ b/app/views/projects/project_members/_new_project_member.html.haml @@ -0,0 +1,18 @@ += form_for @project_member, as: :project_member, url: namespace_project_project_members_path(@project.namespace, @project), html: { class: 'form-horizontal users-project-form' } do |f| + .form-group + = f.label :user_ids, "People", class: 'control-label' + .col-sm-10 + = users_select_tag(:user_ids, multiple: true, class: 'input-large', scope: :all, email_user: true) + .help-block + Search for existing users or invite new ones using their email address. + + .form-group + = f.label :access_level, "Project Access", class: 'control-label' + .col-sm-10 + = select_tag :access_level, options_for_select(ProjectMember.access_roles, @project_member.access_level), class: "project-access-select select2" + .help-block + Read more about role permissions + %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink" + + .form-actions + = f.submit 'Add users to project', class: "btn btn-create" diff --git a/app/views/projects/project_members/_project_member.html.haml b/app/views/projects/project_members/_project_member.html.haml new file mode 100644 index 0000000000..635e4d7094 --- /dev/null +++ b/app/views/projects/project_members/_project_member.html.haml @@ -0,0 +1,53 @@ +- user = member.user +- return unless user || member.invite? + +%li{class: "#{dom_class(member)} js-toggle-container project_member_row access-#{member.human_access.downcase}", id: dom_id(member)} + %span.list-item-name + - if member.user + = image_tag avatar_icon(user.email, 16), class: "avatar s16", alt: '' + %strong + = link_to user.name, user_path(user) + %span.cgray= user.username + - if user == current_user + %span.label.label-success It's you + - if user.blocked? + %label.label.label-danger + %strong Blocked + - else + = image_tag avatar_icon(member.invite_email, 16), class: "avatar s16", alt: '' + %strong + = member.invite_email + %span.cgray + invited + - if member.created_by + by + = link_to member.created_by.name, user_path(member.created_by) + = time_ago_with_tooltip(member.created_at) + + - if current_user_can_admin_project + = link_to resend_invite_namespace_project_project_member_path(@project.namespace, @project, member), method: :post, class: "btn-xs btn", title: 'Resend invite' do + Resend invite + + - if current_user_can_admin_project + - unless @project.personal? && user == current_user + .pull-right + %strong= member.human_access + = button_tag class: "btn-xs btn js-toggle-button", + title: 'Edit access level', type: 'button' do + %i.fa.fa-pencil-square-o + +   + - if current_user == user + = link_to leave_namespace_project_project_members_path(@project.namespace, @project), data: { confirm: "Leave project?"}, method: :delete, class: "btn-xs btn btn-remove", title: 'Leave project' do + %i.fa.fa-minus.fa-inverse + - else + = link_to namespace_project_project_member_path(@project.namespace, @project, member), data: { confirm: remove_from_project_team_message(@project, member) }, method: :delete, remote: true, class: "btn-xs btn btn-remove", title: 'Remove user from team' do + %i.fa.fa-minus.fa-inverse + + .edit-member.hide.js-toggle-content + %br + = form_for member, as: :project_member, url: namespace_project_project_member_path(@project.namespace, @project, member), remote: true do |f| + .prepend-top-10 + = f.select :access_level, options_for_select(ProjectMember.access_roles, member.access_level), {}, class: 'form-control' + .prepend-top-10 + = f.submit 'Save', class: 'btn btn-save' diff --git a/app/views/projects/project_members/_team.html.haml b/app/views/projects/project_members/_team.html.haml new file mode 100644 index 0000000000..615c425e59 --- /dev/null +++ b/app/views/projects/project_members/_team.html.haml @@ -0,0 +1,11 @@ +- can_admin_project = can?(current_user, :admin_project, @project) + +.panel.panel-default.prepend-top-20 + .panel-heading + %strong #{@project.name} + project members + %small + (#{members.count}) + %ul.well-list + - members.each do |project_member| + = render 'project_member', member: project_member, current_user_can_admin_project: can_admin_project diff --git a/app/views/projects/team_members/import.html.haml b/app/views/projects/project_members/import.html.haml similarity index 53% rename from app/views/projects/team_members/import.html.haml rename to app/views/projects/project_members/import.html.haml index d3e4a76201..293754cd0c 100644 --- a/app/views/projects/team_members/import.html.haml +++ b/app/views/projects/project_members/import.html.haml @@ -1,14 +1,14 @@ %h3.page-title - = "Import members from another project" + Import members from another project %p.light Only project members will be imported. Group members will be skipped. %hr -= form_tag apply_import_project_team_members_path(@project), method: 'post', class: 'form-horizontal' do += form_tag apply_import_namespace_project_project_members_path(@project.namespace, @project), method: 'post', class: 'form-horizontal' do .form-group = label_tag :source_project_id, "Project", class: 'control-label' .col-sm-10= select_tag(:source_project_id, options_from_collection_for_select(current_user.authorized_projects, :id, :name_with_namespace), prompt: "Select project", class: "select2 lg", required: true) .form-actions - = submit_tag 'Import project members', class: "btn btn-create" - = link_to "Cancel", project_team_index_path(@project), class: "btn btn-cancel" + = button_tag 'Import project members', class: "btn btn-create" + = link_to "Cancel", namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-cancel" diff --git a/app/views/projects/project_members/index.html.haml b/app/views/projects/project_members/index.html.haml new file mode 100644 index 0000000000..36a6f6a155 --- /dev/null +++ b/app/views/projects/project_members/index.html.haml @@ -0,0 +1,35 @@ +%h3.page-title + Users with access to this project + +%p.light + Read more about project permissions + %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink" + +%hr + +.clearfix.js-toggle-container + = form_tag namespace_project_project_members_path(@project.namespace, @project), method: :get, class: 'form-inline member-search-form' do + .form-group + = search_field_tag :search, params[:search], { placeholder: 'Find existing member by name', class: 'form-control search-text-input input-mn-300' } + = button_tag 'Search', class: 'btn' + + - if can?(current_user, :admin_project_member, @project) + %span.pull-right + = button_tag class: 'btn btn-new btn-grouped js-toggle-button', type: 'button' do + Add members + %i.fa.fa-chevron-down + = link_to import_namespace_project_project_members_path(@project.namespace, @project), class: "btn btn-grouped", title: "Import members from another project" do + Import members + + .js-toggle-content.hide.new-group-member-holder + = render "new_project_member" + += render "team", members: @project_members + +- if @group + = render "group_members", members: @group_members + +:coffeescript + $('form.member-search-form').on 'submit', (event) -> + event.preventDefault() + Turbolinks.visit @.action + '?' + $(@).serialize() diff --git a/app/views/projects/project_members/update.js.haml b/app/views/projects/project_members/update.js.haml new file mode 100644 index 0000000000..811b185882 --- /dev/null +++ b/app/views/projects/project_members/update.js.haml @@ -0,0 +1,3 @@ +- can_admin_project = can?(current_user, :admin_project, @project) +:plain + $("##{dom_id(@project_member)}").replaceWith('#{escape_javascript(render("project_member", member: @project_member, current_user_can_admin_project: can_admin_project))}'); diff --git a/app/views/projects/protected_branches/_branches_list.html.haml b/app/views/projects/protected_branches/_branches_list.html.haml new file mode 100644 index 0000000000..bb49f4de87 --- /dev/null +++ b/app/views/projects/protected_branches/_branches_list.html.haml @@ -0,0 +1,34 @@ +- unless @branches.empty? + %br + %h4 Already Protected: + %table.table.protected-branches-list + %thead + %tr.no-border + %th Branch + %th Developers can push + %th Last commit + %th + + %tbody + - @branches.each do |branch| + - @url = namespace_project_protected_branch_path(@project.namespace, @project, branch) + %tr + %td + = link_to namespace_project_commits_path(@project.namespace, @project, branch.name) do + %strong= branch.name + - if @project.root_ref?(branch.name) + %span.label.label-info default + %td + = check_box_tag "developers_can_push", branch.id, branch.developers_can_push, "data-url" => @url + %td + - if commit = branch.commit + = link_to namespace_project_commit_path(@project.namespace, @project, commit.id), class: 'commit_short_id' do + = commit.short_id + · + #{time_ago_with_tooltip(commit.committed_date)} + - else + (branch was removed from repository) + %td + .pull-right + - if can? current_user, :admin_project, @project + = link_to 'Unprotect', [@project.namespace.becomes(Namespace), @project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-remove btn-sm" diff --git a/app/views/projects/protected_branches/index.html.haml b/app/views/projects/protected_branches/index.html.haml index 7925e80030..a3464c0e5e 100644 --- a/app/views/projects/protected_branches/index.html.haml +++ b/app/views/projects/protected_branches/index.html.haml @@ -1,17 +1,17 @@ %h3.page-title Protected branches -%p.light This ability allows to keep stable branches secured and force code review before merge to protected branches +%p.light Keep stable branches secure and force developers to use Merge Requests %hr -.bs-callout.bs-callout-info - %p Protected branches designed to +.well.append-bottom-20 + %p Protected branches are designed to %ul - %li prevent push for all except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"} - %li prevent branch from force push - %li prevent branch from removal - %p Read more about project permissions #{link_to "here", help_page_path("permissions", "permissions"), class: "underlined-link"} + %li prevent pushes from everybody except #{link_to "masters", help_page_path("permissions", "permissions"), class: "vlink"} + %li prevent anyone from force pushing to the branch + %li prevent anyone from deleting the branch + %p Read more about #{link_to "project permissions", help_page_path("permissions", "permissions"), class: "underlined-link"} - if can? current_user, :admin_project, @project - = form_for [@project, @protected_branch], html: { class: 'form-horizontal' } do |f| + = form_for [@project.namespace.becomes(Namespace), @project, @protected_branch], html: { class: 'form-horizontal' } do |f| -if @protected_branch.errors.any? .alert.alert-danger %ul @@ -22,29 +22,14 @@ = f.label :name, "Branch", class: 'control-label' .col-sm-10 = f.select(:name, @project.open_branches.map { |br| [br.name, br.name] } , {include_blank: "Select branch"}, {class: "select2"}) + .form-group + .col-sm-offset-2.col-sm-10 + .checkbox + = f.label :developers_can_push do + = f.check_box :developers_can_push + %strong Developers can push + .help-block Allow developers to push to this branch .form-actions = f.submit 'Protect', class: "btn-create btn" -- unless @branches.empty? - %h5 Already Protected: - %ul.bordered-list.protected-branches-list - - @branches.each do |branch| - %li - %h4 - = link_to project_commits_path(@project, branch.name) do - %strong= branch.name - - if @project.root_ref?(branch.name) - %span.label.label-info default - %span.label.label-success - %i.icon-lock - .pull-right - - if can? current_user, :admin_project, @project - = link_to 'Unprotect', [@project, branch], data: { confirm: 'Branch will be writable for developers. Are you sure?' }, method: :delete, class: "btn btn-remove btn-small" += render 'branches_list' - - if commit = branch.commit - = link_to project_commit_path(@project, commit.id), class: 'commit_short_id' do - = commit.short_id - %span.light - = gfm escape_once(truncate(commit.title, length: 40)) - #{time_ago_with_tooltip(commit.committed_date)} - - else - (branch was removed from repository) diff --git a/app/views/projects/refs/logs_tree.js.haml b/app/views/projects/refs/logs_tree.js.haml index 948a21aa81..35c15cf3a9 100644 --- a/app/views/projects/refs/logs_tree.js.haml +++ b/app/views/projects/refs/logs_tree.js.haml @@ -11,9 +11,9 @@ - if @logs.present? :plain var current_url = location.href.replace(/\/?$/, '/'); - var log_url = '#{project_tree_url(@project, tree_join(@ref, @path || '/'))}'.replace(/\/?$/, '/'); + var log_url = '#{namespace_project_tree_url(@project.namespace, @project, tree_join(@ref, @path || '/'))}'.replace(/\/?$/, '/'); if(current_url == log_url) { // Load 10 more commit log for each file in tree // if we still on the same page - ajaxGet('#{logs_file_project_ref_path(@project, @ref, @path || '/', offset: (@offset + @limit))}'); + ajaxGet('#{logs_file_namespace_project_ref_path(@project.namespace, @project, @ref, @path || '', offset: (@offset + @limit))}'); } diff --git a/app/views/projects/repositories/_download_archive.html.haml b/app/views/projects/repositories/_download_archive.html.haml index 0bf59a2038..b9486a9b49 100644 --- a/app/views/projects/repositories/_download_archive.html.haml +++ b/app/views/projects/repositories/_download_archive.html.haml @@ -1,37 +1,37 @@ - ref = ref || nil - btn_class = btn_class || '' -- split_button = split_button || false +- split_button = split_button || false - if split_button == true %span.btn-group{class: btn_class} - = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do - %i.icon-download-alt + = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn col-xs-10', rel: 'nofollow' do + %i.fa.fa-download %span Download zip - %a.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' } + %a.col-xs-2.btn.dropdown-toggle{ 'data-toggle' => 'dropdown' } %span.caret %span.sr-only Select Archive Format - %ul.dropdown-menu{ role: 'menu' } + %ul.col-xs-10.dropdown-menu{ role: 'menu' } %li - = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), rel: 'nofollow' do - %i.icon-download-alt + = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), rel: 'nofollow' do + %i.fa.fa-download %span Download zip %li - = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do - %i.icon-download-alt + = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.gz'), rel: 'nofollow' do + %i.fa.fa-download %span Download tar.gz %li - = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do - %i.icon-download-alt + = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.bz2'), rel: 'nofollow' do + %i.fa.fa-download %span Download tar.bz2 %li - = link_to archive_project_repository_path(@project, ref: ref, format: 'tar'), rel: 'nofollow' do - %i.icon-download-alt + = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar'), rel: 'nofollow' do + %i.fa.fa-download %span Download tar - else %span.btn-group{class: btn_class} - = link_to archive_project_repository_path(@project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do - %i.icon-download-alt + = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'zip'), class: 'btn', rel: 'nofollow' do + %i.fa.fa-download %span zip - = link_to archive_project_repository_path(@project, ref: ref, format: 'tar.gz'), class: 'btn', rel: 'nofollow' do - %i.icon-download-alt + = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: ref, format: 'tar.gz'), class: 'btn', rel: 'nofollow' do + %i.fa.fa-download %span tar.gz diff --git a/app/views/projects/repositories/_feed.html.haml b/app/views/projects/repositories/_feed.html.haml index c77ffff43f..f3526ad074 100644 --- a/app/views/projects/repositories/_feed.html.haml +++ b/app/views/projects/repositories/_feed.html.haml @@ -1,7 +1,7 @@ - commit = update %tr %td - = link_to project_commits_path(@project, commit.head.name) do + = link_to namespace_project_commits_path(@project.namespace, @project, commit.head.name) do %strong = commit.head.name - if @project.root_ref?(commit.head.name) @@ -9,7 +9,7 @@ %td %div - = link_to project_commits_path(@project, commit.id) do + = 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: '' = gfm escape_once(truncate(commit.title, length: 40)) diff --git a/app/views/projects/repositories/stats.html.haml b/app/views/projects/repositories/stats.html.haml deleted file mode 100644 index 70db27d644..0000000000 --- a/app/views/projects/repositories/stats.html.haml +++ /dev/null @@ -1,33 +0,0 @@ -= render "projects/commits/head" -.row - .col-md-6 - %div#activity-chart.chart - %hr - %p - %b Total commits: - %span= @repository.commit_count - %p - %b Total files in #{@repository.root_ref}: - %span= @stats.files_count - %p - %b Authors: - %span= @stats.authors_count - - - .col-md-6 - %h4 Top 50 Committers: - %ol.styled - - @stats.authors[0...50].each do |author| - %li - = image_tag avatar_icon(author.email, 16), class: 'avatar s16', alt: '' - = author.name - %small.light= author.email - .pull-right - = author.commits - - -:javascript - var labels = [#{@graph.labels.to_json}]; - var commits = [#{@graph.commits.join(', ')}]; - var title = "Commit activity for last #{@graph.weeks} weeks"; - Chart.init(labels, commits, title); diff --git a/app/views/projects/services/_form.html.haml b/app/views/projects/services/_form.html.haml index a5db7969a6..ce6b7a0737 100644 --- a/app/views/projects/services/_form.html.haml +++ b/app/views/projects/services/_form.html.haml @@ -5,12 +5,12 @@ %p= @service.description .back-link - = link_to project_services_path(@project) do + = link_to namespace_project_services_path(@project.namespace, @project) do ← to services %hr -= form_for(@service, as: :service, url: project_service_path(@project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |f| += form_for(@service, as: :service, url: namespace_project_service_path(@project.namespace, @project, @service.to_param), method: :put, html: { class: 'form-horizontal' }) do |f| - if @service.errors.any? .alert.alert-danger %ul @@ -18,21 +18,72 @@ %li= msg - if @service.help.present? - .bs-callout - = @service.help + .well + = preserve do + = markdown @service.help .form-group = f.label :active, "Active", class: "control-label" .col-sm-10 = f.check_box :active + - if @service.supported_events.length > 1 + .form-group + = f.label :url, "Trigger", class: 'control-label' + .col-sm-10 + - if @service.supported_events.include?("push") + %div + = f.check_box :push_events, class: 'pull-left' + .prepend-left-20 + = f.label :push_events, class: 'list-label' do + %strong Push events + %p.light + This url will be triggered by a push to the repository + - if @service.supported_events.include?("tag_push") + %div + = f.check_box :tag_push_events, class: 'pull-left' + .prepend-left-20 + = f.label :tag_push_events, class: 'list-label' do + %strong Tag push events + %p.light + This url will be triggered when a new tag is pushed to the repository + - if @service.supported_events.include?("note") + %div + = f.check_box :note_events, class: 'pull-left' + .prepend-left-20 + = f.label :note_events, class: 'list-label' do + %strong Comments + %p.light + This url will be triggered when someone adds a comment + - if @service.supported_events.include?("issue") + %div + = f.check_box :issues_events, class: 'pull-left' + .prepend-left-20 + = f.label :issues_events, class: 'list-label' do + %strong Issues events + %p.light + This url will be triggered when an issue is created + - if @service.supported_events.include?("merge_request") + %div + = f.check_box :merge_requests_events, class: 'pull-left' + .prepend-left-20 + = f.label :merge_requests_events, class: 'list-label' do + %strong Merge Request events + %p.light + This url will be triggered when a merge request is created + - @service.fields.each do |field| - name = field[:name] + - title = field[:title] || name.humanize + - value = service_field_value(field[:type], @service.send(name)) - type = field[:type] - placeholder = field[:placeholder] + - choices = field[:choices] + - default_choice = field[:default_choice] + - help = field[:help] .form-group - = f.label name, class: "control-label" + = f.label name, title, class: "control-label" .col-sm-10 - if type == 'text' = f.text_field name, class: "form-control", placeholder: placeholder @@ -40,9 +91,16 @@ = f.text_area name, rows: 5, class: "form-control", placeholder: placeholder - elsif type == 'checkbox' = f.check_box name + - elsif type == 'select' + = f.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" } + - elsif type == 'password' + = f.password_field name, placeholder: value, class: 'form-control' + - if help + %span.help-block= help .form-actions = f.submit 'Save', class: 'btn btn-save'   - - if @service.valid? && @service.activated? && @service.can_test? - = link_to 'Test settings', test_project_service_path(@project, @service.to_param), class: 'btn' + - 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}" diff --git a/app/views/projects/services/index.html.haml b/app/views/projects/services/index.html.haml index 7271dd830c..0d3ccb6bb8 100644 --- a/app/views/projects/services/index.html.haml +++ b/app/views/projects/services/index.html.haml @@ -1,13 +1,22 @@ %h3.page-title Project services %p.light Project services allow you to integrate GitLab with other applications -%hr -%ul.bordered-list +%table.table + %thead + %tr + %th + %th Service + %th Description + %th Last edit - @services.sort_by(&:title).each do |service| - %li - %h4 - = link_to edit_project_service_path(@project, service.to_param) do - = service.title - .pull-right - = boolean_to_icon service.activated? - %p= service.description + %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 diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 0fd290b539..4464c51744 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -1,82 +1,108 @@ +- if current_user && can?(current_user, :download_code, @project) + = render 'shared/no_ssh' + = render 'shared/no_password' + = render "home_panel" -.row - %section.col-md-9 - = render "events/event_last_push", event: @last_push - = render 'shared/event_filter' - .content_list - = spinner - %aside.col-md-3.project-side.hidden-sm.hidden-xs - .clearfix - - if @project.archived? - .alert.alert-warning - %h4 - %i.icon-warning-sign - Archived project! - %p Repository is read-only +%ul.nav.nav-tabs + %li.active + = link_to '#tab-activity', 'data-toggle' => 'tab' do + Activity + - if @repository.readme + %li + = link_to '#tab-readme', 'data-toggle' => 'tab' do + Readme + - if @repository.changelog + %li + = link_to changelog_url(@project) do + Changelog + - if @repository.contribution_guide + %li + = link_to contribution_guide_url(@project) do + Contribution guide + - if @repository.license + %li + = link_to license_url(@project) do + License - - if @project.forked_from_project - .alert.alert-success - %i.icon-code-fork.project-fork-icon - Forked from: - %br - = link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project) + .project-home-links + - unless @project.empty_repo? + = link_to pluralize(number_with_delimiter(@repository.commit_count), 'commit'), namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref) + = link_to pluralize(number_with_delimiter(@repository.branch_names.count), 'branch'), namespace_project_branches_path(@project.namespace, @project) + = link_to pluralize(number_with_delimiter(@repository.tag_names.count), 'tag'), namespace_project_tags_path(@project.namespace, @project) + %span.light.prepend-left-20= repository_size - .star-buttons - %span.star.js-toggler-container{class: @show_star ? 'on' : ''} - - if current_user - = link_to_toggle_star('Star this project.', false, true) - = link_to_toggle_star('Unstar this project.', true, true) - - else - = link_to_toggle_star('You must sign in to star a project.', false, false) +.tab-content + .tab-pane.active#tab-activity + .row + = link_to '#aside', class: 'show-aside' do + %i.fa.fa-angle-left + %section.col-md-9 + = render "events/event_last_push", event: @last_push + = render 'shared/event_filter' + .content_list + = spinner + %aside.col-md-3.project-side + .clearfix + - if @project.archived? + .alert.alert-warning + %h4 + %i.fa.fa-exclamation-triangle + Archived project! + %p Repository is read-only - - unless @project.empty_repo? - .fork-buttons - - if current_user && can?(current_user, :fork_project, @project) && @project.namespace != current_user.namespace - - if current_user.already_forked?(@project) - = link_to project_path(current_user.fork_of(@project)), class: 'btn btn-block' do - %i.icon-compass - Go to fork + - if @project.forked_from_project + .well + %i.fa.fa-code-fork.project-fork-icon + Forked from: + %br + = link_to @project.forked_from_project.name_with_namespace, project_path(@project.forked_from_project) + + - unless @project.empty_repo? + = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: @ref || @repository.root_ref), class: 'btn btn-block' do + %i.fa.fa-exchange + Compare code + + - if can?(current_user, :download_code, @project) + = render 'projects/repositories/download_archive', split_button: true, btn_class: 'btn-block' + + - if version = @repository.version + - detail_url = changelog_url(@project) || version_url(@project) + = link_to detail_url, class: 'btn btn-block' do + %i.fa.fa-file-text-o + Version: %span.count - = @project.forks_count + = @repository.blob_by_oid(version.id).data + + .prepend-top-10.append-bottom-10 + %p + %span.light Created on + #{@project.created_at.stamp('Aug 22, 2013')} + %p + %span.light Owned by #{@project.group ? "the" : nil} + - if @project.group + #{link_to @project.group.name, @project.group} group - else - = link_to fork_project_path(@project), title: "Fork", class: "btn btn-block", method: "POST" do - %i.icon-code-fork - Fork repository - %span.count - = @project.forks_count - - unless @project.empty_repo? - - if can? current_user, :download_code, @project - = render 'projects/repositories/download_archive', btn_class: 'btn-block btn-group-justified', split_button: true + #{link_to @project.owner_name, @project.owner} - = link_to project_compare_index_path(@project, from: @repository.root_ref, to: @ref || @repository.root_ref), class: 'btn btn-block' do - Compare code - - if @repository.readme - - readme = @repository.readme - = link_to project_blob_path(@project, tree_join(@repository.root_ref, readme.name)), class: 'btn btn-block' do + .prepend-top-10 + - @project.ci_services.each do |ci_service| + - if ci_service.active? && ci_service.respond_to?(:builds_path) + %hr + - if ci_service.respond_to?(:status_img_path) + = link_to ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink' do + = image_tag ci_service.status_img_path, alt: "build status" + - else + %span.light CI provided by + = link_to ci_service.title, ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink' + + - if readme = @repository.readme + .tab-pane#tab-readme + %article.readme-holder#README + = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)) do + %h4.readme-file-title + %i.fa.fa-file = readme.name - - - if @repository.version - - version = @repository.version - = link_to project_blob_path(@project, tree_join(@repository.root_ref, version.name)), class: 'btn btn-block' do - Version: - %span.count - = @repository.blob_by_oid(version.id).data - - .prepend-top-10 - %p - %span.light Created on - #{@project.created_at.stamp('Aug 22, 2013')} - %p - %span.light Owned by - - if @project.group - #{link_to @project.group.name, @project.group} group - - else - #{link_to @project.owner_name, @project.owner} - - - - if @project.gitlab_ci? - %hr - = link_to @project.gitlab_ci_service.builds_path do - = image_tag @project.gitlab_ci_service.status_img_path, alt: "build status" + .wiki + = render_readme(readme) diff --git a/app/views/projects/snippets/edit.html.haml b/app/views/projects/snippets/edit.html.haml index f6a5bf9e4f..2d4d5d030a 100644 --- a/app/views/projects/snippets/edit.html.haml +++ b/app/views/projects/snippets/edit.html.haml @@ -1,4 +1,4 @@ %h3.page-title Edit snippet %hr -= render "shared/snippets/form", url: project_snippet_path(@project, @snippet) += render "shared/snippets/form", url: namespace_project_snippet_path(@project.namespace, @project, @snippet) diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml index e60f9a4432..e2d8ec673a 100644 --- a/app/views/projects/snippets/index.html.haml +++ b/app/views/projects/snippets/index.html.haml @@ -1,7 +1,7 @@ %h3.page-title Snippets - if can? current_user, :write_project_snippet, @project - = link_to new_project_snippet_path(@project), class: "btn btn-new pull-right", title: "New Snippet" do + = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new pull-right", title: "New Snippet" do Add new snippet %p.light diff --git a/app/views/projects/snippets/new.html.haml b/app/views/projects/snippets/new.html.haml index 10f684b631..bb659dba0c 100644 --- a/app/views/projects/snippets/new.html.haml +++ b/app/views/projects/snippets/new.html.haml @@ -1,4 +1,4 @@ %h3.page-title New snippet %hr -= render "shared/snippets/form", url: project_snippets_path(@project, @snippet) += render "shared/snippets/form", url: namespace_project_snippets_path(@project.namespace, @project, @snippet) diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml index e4fdbd868c..d19689a105 100644 --- a/app/views/projects/snippets/show.html.haml +++ b/app/views/projects/snippets/show.html.haml @@ -2,7 +2,7 @@ = @snippet.title .pull-right - = link_to new_project_snippet_path(@project), class: "btn btn-new", title: "New Snippet" do + = link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new", title: "New Snippet" do Add new snippet %hr @@ -17,21 +17,21 @@ = @snippet.author_name .back-link - = link_to project_snippets_path(@project) do + = link_to namespace_project_snippets_path(@project.namespace, @project) do ← project snippets .file-holder .file-title - %i.icon-file - %span.file_name + %i.fa.fa-file + %strong = @snippet.file_name - .options + .file-actions .btn-group - if can?(current_user, :modify_project_snippet, @snippet) - = link_to "edit", edit_project_snippet_path(@project, @snippet), class: "btn btn-small", title: 'Edit Snippet' - = link_to "raw", raw_project_snippet_path(@project, @snippet), class: "btn btn-small", target: "_blank" + = link_to "edit", edit_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", title: 'Edit Snippet' + = link_to "raw", raw_namespace_project_snippet_path(@project.namespace, @project, @snippet), class: "btn btn-sm", target: "_blank" - if can?(current_user, :admin_project_snippet, @snippet) - = link_to "remove", project_snippet_path(@project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-small btn-remove", title: 'Delete Snippet' + = link_to "remove", namespace_project_snippet_path(@project.namespace, @project, @snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-sm btn-remove", title: 'Delete Snippet' = render 'shared/snippets/blob' %div#notes= render "projects/notes/notes_with_form" diff --git a/app/views/projects/tags/_tag.html.haml b/app/views/projects/tags/_tag.html.haml index 9ed737b181..28ad272322 100644 --- a/app/views/projects/tags/_tag.html.haml +++ b/app/views/projects/tags/_tag.html.haml @@ -1,15 +1,18 @@ - commit = @repository.commit(tag.target) %li %h4 - = link_to project_commits_path(@project, tag.name), class: "" do - %i.icon-tag + = link_to namespace_project_commits_path(@project.namespace, @project, tag.name), class: "" do + %i.fa.fa-tag = tag.name + - if tag.message.present? +   + = strip_gpg_signature(tag.message) .pull-right - if can? current_user, :download_code, @project - = render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-small' + = render 'projects/repositories/download_archive', ref: tag.name, btn_class: 'btn-grouped btn-group-xs' - if can?(current_user, :admin_project, @project) - = link_to project_tag_path(@project, tag.name), class: 'btn btn-small btn-remove remove-row grouped', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'}, remote: true do - %i.icon-trash + = link_to namespace_project_tag_path(@project.namespace, @project, tag.name), class: 'btn btn-xs btn-remove remove-row grouped', method: :delete, data: { confirm: 'Removed tag cannot be restored. Are you sure?'}, remote: true do + %i.fa.fa-trash-o - if commit %ul.list-unstyled diff --git a/app/views/projects/tags/destroy.js.haml b/app/views/projects/tags/destroy.js.haml new file mode 100644 index 0000000000..ada6710f94 --- /dev/null +++ b/app/views/projects/tags/destroy.js.haml @@ -0,0 +1,3 @@ +$('.js-totaltags-count').html("#{@repository.tags.size}") +- if @repository.tags.size == 0 + $('.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 dc3188d43b..f1bc2bc9a2 100644 --- a/app/views/projects/tags/index.html.haml +++ b/app/views/projects/tags/index.html.haml @@ -4,26 +4,27 @@ Git Tags - if can? current_user, :push_code, @project .pull-right - = link_to new_project_tag_path(@project), class: 'btn btn-create new-tag-btn' do - %i.icon-add-sign + = link_to new_namespace_project_tag_path(@project.namespace, @project), class: 'btn btn-create new-tag-btn' do + %i.fa.fa-add-sign New tag %p.light Tags give the ability to mark specific points in history as being important %hr -- unless @tags.empty? - %ul.bordered-list - - @tags.each do |tag| - = render 'tag', tag: @repository.find_tag(tag) +.tags + - unless @tags.empty? + %ul.bordered-list + - @tags.each do |tag| + = render 'tag', tag: @repository.find_tag(tag) - = paginate @tags, theme: 'gitlab' + = paginate @tags, theme: 'gitlab' -- else - .nothing-here-block - Repository has no tags yet. - %br - %small - Use git tag command to add a new one: + - else + .nothing-here-block + Repository has no tags yet. %br - %span.monospace git tag -a v1.4 -m 'version 1.4' + %small + Use git tag command to add a new one: + %br + %span.monospace git tag -a v1.4 -m 'version 1.4' diff --git a/app/views/projects/tags/new.html.haml b/app/views/projects/tags/new.html.haml index a9fd97f891..655044438d 100644 --- a/app/views/projects/tags/new.html.haml +++ b/app/views/projects/tags/new.html.haml @@ -1,21 +1,31 @@ +- if @error + .alert.alert-danger + %button{ type: "button", class: "close", "data-dismiss" => "alert"} × + = @error %h3.page-title - %i.icon-code-fork + %i.fa.fa-code-fork New tag -= form_tag project_tags_path, method: :post, class: "form-horizontal" do += form_tag namespace_project_tags_path, method: :post, id: "new-tag-form", class: "form-horizontal" do .form-group = label_tag :tag_name, 'Name for new tag', class: 'control-label' .col-sm-10 - = text_field_tag :tag_name, nil, placeholder: 'v3.0.1', required: true, tabindex: 1, class: 'form-control' + = text_field_tag :tag_name, params[:tag_name], placeholder: 'v3.0.1', required: true, tabindex: 1, class: 'form-control' .form-group = label_tag :ref, 'Create from', class: 'control-label' .col-sm-10 - = text_field_tag :ref, nil, placeholder: 'master', required: true, tabindex: 2, class: 'form-control' + = text_field_tag :ref, params[:ref], placeholder: 'master', required: true, tabindex: 2, class: 'form-control' .light Branch name or commit SHA + .form-group + = label_tag :message, 'Message', class: 'control-label' + .col-sm-10 + = text_field_tag :message, nil, placeholder: 'Enter message.', required: false, tabindex: 3, class: 'form-control' + .light (Optional) Entering a message will create an annotated tag. .form-actions - = submit_tag 'Create tag', class: 'btn btn-create', tabindex: 3 - = link_to 'Cancel', project_tags_path(@project), class: 'btn btn-cancel' + = button_tag 'Create tag', class: 'btn btn-create', tabindex: 3 + = link_to 'Cancel', namespace_project_tags_path(@project.namespace, @project), class: 'btn btn-cancel' :javascript + disableButtonIfAnyEmptyField($("#new-tag-form"), ".form-control", ".btn-create"); var availableTags = #{@project.repository.ref_names.to_json}; $("#ref").autocomplete({ diff --git a/app/views/projects/team_members/_form.html.haml b/app/views/projects/team_members/_form.html.haml deleted file mode 100644 index dd059fb99d..0000000000 --- a/app/views/projects/team_members/_form.html.haml +++ /dev/null @@ -1,24 +0,0 @@ -%h3.page-title - = "New project member(s)" - -= form_for @user_project_relation, as: :team_member, url: project_team_members_path(@project), html: { class: "form-horizontal users-project-form" } do |f| - -if @user_project_relation.errors.any? - .alert.alert-danger - %ul - - @user_project_relation.errors.full_messages.each do |msg| - %li= msg - - %p 1. Choose people you want in the project - .form-group - = f.label :user_ids, "People", class: 'control-label' - .col-sm-10 - = users_select_tag(:user_ids, multiple: true) - - %p 2. Set access level for them - .form-group - = f.label :project_access, "Project Access", class: 'control-label' - .col-sm-10= select_tag :project_access, options_for_select(Gitlab::Access.options, @user_project_relation.project_access), class: "project-access-select select2" - - .form-actions - = f.submit 'Add users', class: "btn btn-create" - = link_to "Cancel", project_team_index_path(@project), class: "btn btn-cancel" diff --git a/app/views/projects/team_members/_group_members.html.haml b/app/views/projects/team_members/_group_members.html.haml deleted file mode 100644 index 83c4b6f87d..0000000000 --- a/app/views/projects/team_members/_group_members.html.haml +++ /dev/null @@ -1,14 +0,0 @@ -- group_users_count = @group.users_groups.count -.panel.panel-default - .panel-heading - %strong #{@group.name} - group members (#{group_users_count}) - .pull-right - = link_to members_group_path(@group), class: 'btn btn-small' do - %i.icon-edit - %ul.well-list - - @group.users_groups.order('group_access DESC').limit(20).each do |member| - = render 'users_groups/users_group', member: member, show_controls: false - - if group_users_count > 20 - %li - and #{group_users_count - 20} more. For full list visit #{link_to 'group members page', members_group_path(@group)} diff --git a/app/views/projects/team_members/_team.html.haml b/app/views/projects/team_members/_team.html.haml deleted file mode 100644 index 0e5b817613..0000000000 --- a/app/views/projects/team_members/_team.html.haml +++ /dev/null @@ -1,9 +0,0 @@ -.team-table - - can_admin_project = (can? current_user, :admin_project, @project) - .panel.panel-default - .panel-heading - %strong #{@project.name} - project members (#{members.count}) - %ul.well-list - - members.each do |team_member| - = render 'team_member', member: team_member, current_user_can_admin_project: can_admin_project diff --git a/app/views/projects/team_members/_team_member.html.haml b/app/views/projects/team_members/_team_member.html.haml deleted file mode 100644 index d93bb44ab9..0000000000 --- a/app/views/projects/team_members/_team_member.html.haml +++ /dev/null @@ -1,17 +0,0 @@ -- user = member.user -%li{id: dom_id(user), class: "team_member_row access-#{member.human_access.downcase}"} - .pull-right - - if current_user_can_admin_project - - unless @project.personal? && user == current_user - .pull-left - = form_for(member, as: :team_member, url: project_team_member_path(@project, member.user)) do |f| - = f.select :project_access, options_for_select(UsersProject.access_roles, member.project_access), {}, class: "medium project-access-select span2 trigger-submit" -   - = link_to project_team_member_path(@project, user), data: { confirm: remove_from_project_team_message(@project, user)}, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from team' do - %i.icon-minus.icon-white - = image_tag avatar_icon(user.email, 32), class: "avatar s32" - %p - %strong= user.name - %span.cgray= user.username - - diff --git a/app/views/projects/team_members/index.html.haml b/app/views/projects/team_members/index.html.haml deleted file mode 100644 index ddb3b9d4a9..0000000000 --- a/app/views/projects/team_members/index.html.haml +++ /dev/null @@ -1,16 +0,0 @@ -%h3.page-title - Users with access to this project - - - if can? current_user, :admin_team_member, @project - %span.pull-right - = link_to new_project_team_member_path(@project), class: "btn btn-new btn-grouped", title: "New project member" do - New project member - = link_to import_project_team_members_path(@project), class: "btn btn-grouped", title: "Import members from another project" do - Import members - -%p.light - Read more about project permissions - %strong= link_to "here", help_page_path("permissions", "permissions"), class: "vlink" -= render "team", members: @users_projects -- if @group - = render "group_members" diff --git a/app/views/projects/team_members/new.html.haml b/app/views/projects/team_members/new.html.haml deleted file mode 100644 index b1bc3ba0eb..0000000000 --- a/app/views/projects/team_members/new.html.haml +++ /dev/null @@ -1 +0,0 @@ -= render "form" diff --git a/app/views/projects/team_members/update.js.haml b/app/views/projects/team_members/update.js.haml deleted file mode 100644 index c68fe9574a..0000000000 --- a/app/views/projects/team_members/update.js.haml +++ /dev/null @@ -1,6 +0,0 @@ -- if @user_project_relation.valid? - :plain - $("##{dom_id(@user_project_relation)}").effect("highlight", {color: "#529214"}, 1000);; -- else - :plain - $("##{dom_id(@user_project_relation)}").effect("highlight", {color: "#D12F19"}, 1000);; diff --git a/app/views/projects/transfer.js.haml b/app/views/projects/transfer.js.haml index 10b0de98c0..17b9fecfeb 100644 --- a/app/views/projects/transfer.js.haml +++ b/app/views/projects/transfer.js.haml @@ -1,7 +1,2 @@ -- if @project.errors[:namespace_id].present? - :plain - $("#tab-transfer .errors-holder").replaceWith(errorMessage('#{escape_javascript(@project.errors[:namespace_id].first)}')); - $("#tab-transfer .form-actions input").removeAttr('disabled').removeClass('disabled'); -- else - :plain - location.href = "#{edit_project_path(@project)}"; +:plain + location.href = "#{edit_namespace_project_path(@project.namespace, @project)}"; diff --git a/app/views/projects/tree/_blob_item.html.haml b/app/views/projects/tree/_blob_item.html.haml index 393ef0e24b..02ecbade21 100644 --- a/app/views/projects/tree/_blob_item.html.haml +++ b/app/views/projects/tree/_blob_item.html.haml @@ -1,8 +1,8 @@ %tr{ class: "tree-item #{tree_hex_class(blob_item)}" } %td.tree-item-file-name - = tree_icon(type) + = tree_icon(type, blob_item.mode, blob_item.name) %span.str-truncated - = link_to blob_item.name, project_blob_path(@project, tree_join(@id || @commit.id, blob_item.name)) + = link_to blob_item.name, namespace_project_blob_path(@project.namespace, @project, tree_join(@id || @commit.id, blob_item.name)) %td.tree_time_ago.cgray = render 'spinner' %td.hidden-xs.tree_commit diff --git a/app/views/projects/tree/_readme.html.haml b/app/views/projects/tree/_readme.html.haml index 9d0292059d..f082d71186 100644 --- a/app/views/projects/tree/_readme.html.haml +++ b/app/views/projects/tree/_readme.html.haml @@ -1,6 +1,7 @@ %article.readme-holder#README - %h4.readme-file-title - %i.icon-file - = readme.name + = link_to '#README' do + %h4.readme-file-title + %i.fa.fa-file + = readme.name .wiki = render_readme(readme) diff --git a/app/views/projects/tree/_spinner.html.haml b/app/views/projects/tree/_spinner.html.haml index 5a9e77b63d..b47ad0f41e 100644 --- a/app/views/projects/tree/_spinner.html.haml +++ b/app/views/projects/tree/_spinner.html.haml @@ -1,3 +1,3 @@ %span.log_loading.hide - %i.icon-spinner.icon-spin + %i.fa.fa-spinner.fa-spin Loading commit data... diff --git a/app/views/projects/tree/_submodule_item.html.haml b/app/views/projects/tree/_submodule_item.html.haml index 474604df65..2b5f671c09 100644 --- a/app/views/projects/tree/_submodule_item.html.haml +++ b/app/views/projects/tree/_submodule_item.html.haml @@ -1,14 +1,6 @@ -- tree, commit = submodule_links(submodule_item) %tr{ class: "tree-item" } %td.tree-item-file-name - %i.icon-archive - %span - = link_to truncate(submodule_item.name, length: 40), tree - @ - %span.monospace - - if commit.nil? - #{submodule_item.id[0..10]} - - else - = link_to "#{submodule_item.id[0..10]}", commit + %i.fa.fa-archive.fa-fw + = submodule_link(submodule_item, @ref) %td %td.hidden-xs diff --git a/app/views/projects/tree/_tree.html.haml b/app/views/projects/tree/_tree.html.haml index 84c3682d7a..d304690d16 100644 --- a/app/views/projects/tree/_tree.html.haml +++ b/app/views/projects/tree/_tree.html.haml @@ -1,18 +1,18 @@ %ul.breadcrumb.repo-breadcrumb %li - = link_to project_tree_path(@project, @ref) do + = link_to namespace_project_tree_path(@project.namespace, @project, @ref) do = @project.path - tree_breadcrumbs(tree, 6) do |title, path| %li - if path - = link_to truncate(title, length: 40), project_tree_path(@project, path) + = link_to truncate(title, length: 40), namespace_project_tree_path(@project.namespace, @project, path) - else = link_to title, '#' - if current_user && can_push_branch?(@project, @ref) %li - = link_to project_new_tree_path(@project, @id), title: 'New file', id: 'new-file-link' do + = link_to namespace_project_new_blob_path(@project.namespace, @project, @id), title: 'New file', id: 'new-file-link' do %small - %i.icon-plus + %i.fa.fa-plus %div#tree-content-holder.tree-content-holder %table#tree-slider{class: "table_#{@hex_path} tree-table" } @@ -24,18 +24,18 @@ .pull-left Last Commit .last-commit.hidden-sm.pull-left   - %i.icon-angle-right + %i.fa.fa-angle-right   %small.light - = link_to @commit.short_id, project_commit_path(@project, @commit) + = link_to @commit.short_id, namespace_project_commit_path(@project.namespace, @project, @commit) – = truncate(@commit.title, length: 50) - = link_to "history", project_commits_path(@project, @id), class: "pull-right" + = link_to 'History', namespace_project_commits_path(@project.namespace, @project, @id), class: 'pull-right' - if @path.present? %tr.tree-item %td.tree-item-file-name - = link_to "..", project_tree_path(@project, up_dir_path(tree)), class: 'prepend-left-10' + = link_to "..", namespace_project_tree_path(@project.namespace, @project, up_dir_path), class: 'prepend-left-10' %td %td.hidden-xs diff --git a/app/views/projects/tree/_tree_commit_column.html.haml b/app/views/projects/tree/_tree_commit_column.html.haml index bd50dd4d9a..50521264a6 100644 --- a/app/views/projects/tree/_tree_commit_column.html.haml +++ b/app/views/projects/tree/_tree_commit_column.html.haml @@ -1,3 +1,3 @@ %span.str-truncated %span.tree_author= commit_author_link(commit, avatar: true, size: 16) - = link_to_gfm commit.title, project_commit_path(@project, commit.id), class: "tree-commit-link" + = link_to_gfm commit.title, namespace_project_commit_path(@project.namespace, @project, commit.id), class: "tree-commit-link" diff --git a/app/views/projects/tree/_tree_item.html.haml b/app/views/projects/tree/_tree_item.html.haml index f8cecf9be1..e87138bf98 100644 --- a/app/views/projects/tree/_tree_item.html.haml +++ b/app/views/projects/tree/_tree_item.html.haml @@ -1,8 +1,9 @@ %tr{ class: "tree-item #{tree_hex_class(tree_item)}" } %td.tree-item-file-name - = tree_icon(type) + = tree_icon(type, tree_item.mode, tree_item.name) %span.str-truncated - = link_to tree_item.name, project_tree_path(@project, tree_join(@id || @commit.id, tree_item.name)) + - path = flatten_tree(tree_item) + = link_to path, namespace_project_tree_path(@project.namespace, @project, tree_join(@id || @commit.id, path)) %td.tree_time_ago.cgray = render 'spinner' %td.hidden-xs.tree_commit diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml index fc4616da6e..feca145369 100644 --- a/app/views/projects/tree/show.html.haml +++ b/app/views/projects/tree/show.html.haml @@ -3,7 +3,7 @@ - if can? current_user, :download_code, @project .tree-download-holder - = render 'projects/repositories/download_archive', ref: @ref, btn_class: 'btn-group-small pull-right hidden-xs hidden-sm', split_button: true + = render 'projects/repositories/download_archive', ref: @ref, btn_class: 'btn-group-sm pull-right hidden-xs hidden-sm', split_button: true #tree-holder.tree-holder.clearfix = render "tree", tree: @tree diff --git a/app/views/projects/update.js.haml b/app/views/projects/update.js.haml index cbb21f2b9f..4f3f4cab8d 100644 --- a/app/views/projects/update.js.haml +++ b/app/views/projects/update.js.haml @@ -1,6 +1,6 @@ - if @project.valid? :plain - location.href = "#{edit_project_path(@project)}"; + location.href = "#{edit_namespace_project_path(@project.namespace, @project)}"; - else :plain $(".project-edit-errors").html("#{escape_javascript(render('errors'))}"); diff --git a/app/views/projects/wikis/_form.html.haml b/app/views/projects/wikis/_form.html.haml index 0a24e36ae8..9fbfa0b1ae 100644 --- a/app/views/projects/wikis/_form.html.haml +++ b/app/views/projects/wikis/_form.html.haml @@ -1,4 +1,4 @@ -= form_for [@project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal' } do |f| += form_for [@project.namespace.becomes(Namespace), @project, @page], method: @page.persisted? ? :put : :post, html: { class: 'form-horizontal wiki-form gfm-form' } do |f| -if @page.errors.any? #error_explanation .alert.alert-danger @@ -19,13 +19,15 @@ %code [Link Title](page-slug) \. - .form-group + .form-group.wiki-content = f.label :content, class: 'control-label' .col-sm-10 - = f.text_area :content, class: 'form-control js-gfm-input markdown-area', rows: 18 - .col-sm-12.hint - .pull-left Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'} - .pull-right Attach images (JPG, PNG, GIF) by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. + = render layout: 'projects/md_preview', locals: { preview_class: "wiki" } do + = render 'projects/zen', f: f, attr: :content, classes: 'description form-control' + .col-sm-12.hint + .pull-left Wiki content is parsed with #{link_to "GitLab Flavored Markdown", help_page_path("markdown", "markdown"), target: '_blank'} + .pull-right Attach files by dragging & dropping or #{link_to "selecting them", '#', class: 'markdown-selector' }. + .clearfix .error-alert .form-group @@ -35,11 +37,10 @@ .form-actions - if @page && @page.persisted? = f.submit 'Save changes', class: "btn-save btn" - = link_to "Cancel", project_wiki_path(@project, @page), class: "btn btn-cancel" + = link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-cancel" - else = f.submit 'Create page', class: "btn-create btn" - = link_to "Cancel", project_wiki_path(@project, :home), class: "btn btn-cancel" + = link_to "Cancel", namespace_project_wiki_path(@project.namespace, @project, :home), class: "btn btn-cancel" :javascript - window.project_image_path_upload = "#{upload_image_project_path @project}"; - + window.project_uploads_path = "#{namespace_project_uploads_path @project.namespace, @project}"; diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml index 68d7087323..633214a4e8 100644 --- a/app/views/projects/wikis/_main_links.html.haml +++ b/app/views/projects/wikis/_main_links.html.haml @@ -1,8 +1,8 @@ %span.pull-right - if (@page && @page.persisted?) - = link_to history_project_wiki_path(@project, @page), class: "btn btn-grouped" do + = link_to history_namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-grouped" do Page History - if can?(current_user, :write_wiki, @project) - = link_to edit_project_wiki_path(@project, @page), class: "btn btn-grouped" do - %i.icon-edit + = link_to edit_namespace_project_wiki_path(@project.namespace, @project, @page), class: "btn btn-grouped" do + %i.fa.fa-pencil-square-o Edit diff --git a/app/views/projects/wikis/_nav.html.haml b/app/views/projects/wikis/_nav.html.haml index 0a7e51e974..693c3facb3 100644 --- a/app/views/projects/wikis/_nav.html.haml +++ b/app/views/projects/wikis/_nav.html.haml @@ -1,19 +1,19 @@ %ul.nav.nav-tabs = nav_link(html_options: {class: params[:id] == 'home' ? 'active' : '' }) do - = link_to 'Home', project_wiki_path(@project, :home) + = link_to 'Home', namespace_project_wiki_path(@project.namespace, @project, :home) = nav_link(path: 'wikis#pages') do - = link_to 'Pages', pages_project_wikis_path(@project) + = link_to 'Pages', pages_namespace_project_wikis_path(@project.namespace, @project) = nav_link(path: 'wikis#git_access') do - = link_to git_access_project_wikis_path(@project) do - %i.icon-download-alt + = link_to git_access_namespace_project_wikis_path(@project.namespace, @project) do + %i.fa.fa-download Git Access - if can?(current_user, :write_wiki, @project) .pull-right = link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do - %i.icon-plus + %i.fa.fa-plus New Page = render 'projects/wikis/new' diff --git a/app/views/projects/wikis/_new.html.haml b/app/views/projects/wikis/_new.html.haml index 1ce292a02d..6834969de8 100644 --- a/app/views/projects/wikis/_new.html.haml +++ b/app/views/projects/wikis/_new.html.haml @@ -7,7 +7,7 @@ .modal-body = label_tag :new_wiki_path do %span Page slug - = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => project_wikis_path(@project) + = text_field_tag :new_wiki_path, nil, placeholder: 'how-to-setup', class: 'form-control', required: true, :'data-wikis-path' => namespace_project_wikis_path(@project.namespace, @project) %p.hint Please don't use spaces. .modal-footer diff --git a/app/views/projects/wikis/edit.html.haml b/app/views/projects/wikis/edit.html.haml index 5347caf000..566850cb78 100644 --- a/app/views/projects/wikis/edit.html.haml +++ b/app/views/projects/wikis/edit.html.haml @@ -9,5 +9,5 @@ .pull-right - if @page.persisted? && can?(current_user, :admin_wiki, @project) - = link_to project_wiki_path(@project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-small btn-remove" do + = link_to namespace_project_wiki_path(@project.namespace, @project, @page), data: { confirm: "Are you sure you want to delete this page?"}, method: :delete, class: "btn btn-sm btn-remove" do Delete this page diff --git a/app/views/projects/wikis/history.html.haml b/app/views/projects/wikis/history.html.haml index 7bc566cf7f..91291f753f 100644 --- a/app/views/projects/wikis/history.html.haml +++ b/app/views/projects/wikis/history.html.haml @@ -1,7 +1,7 @@ = render 'nav' %h3.page-title %span.light History for - = link_to @page.title, project_wiki_path(@project, @page) + = link_to @page.title, namespace_project_wiki_path(@project.namespace, @project, @page) %table.table %thead @@ -12,18 +12,19 @@ %th Last updated %th Format %tbody - - @page.versions.each do |version| + - @page.versions.each_with_index do |version, index| - commit = version %tr %td - = link_to project_wiki_path(@project, @page, version_id: commit.id) do - = commit.short_id + = link_to project_wiki_path_with_version(@project, @page, + commit.id, index == 0) do + = truncate_sha(commit.id) %td - = commit_author_link(commit, avatar: true, size: 24) + = commit.author.name %td - = commit.title + = commit.message %td - #{time_ago_with_tooltip(version.date)} + #{time_ago_with_tooltip(version.authored_date)} %td %strong = @page.page.wiki.page(@page.page.name, commit.id).try(:format) diff --git a/app/views/projects/wikis/pages.html.haml b/app/views/projects/wikis/pages.html.haml index 74317faf9d..ee233d9086 100644 --- a/app/views/projects/wikis/pages.html.haml +++ b/app/views/projects/wikis/pages.html.haml @@ -5,8 +5,8 @@ - @wiki_pages.each do |wiki_page| %li %h4 - = link_to wiki_page.title, project_wiki_path(@project, wiki_page) + = link_to wiki_page.title, namespace_project_wiki_path(@project.namespace, @project, wiki_page) %small (#{wiki_page.format}) .pull-right - %small Last edited #{time_ago_with_tooltip(wiki_page.commit.created_at)} + %small Last edited #{time_ago_with_tooltip(wiki_page.commit.authored_date)} = paginate @wiki_pages, theme: 'gitlab' diff --git a/app/views/projects/wikis/show.html.haml b/app/views/projects/wikis/show.html.haml index cb923e4ca3..a6263e93f6 100644 --- a/app/views/projects/wikis/show.html.haml +++ b/app/views/projects/wikis/show.html.haml @@ -5,7 +5,7 @@ - if @page.historical? .warning_message This is an old version of this page. - You can view the #{link_to "most recent version", project_wiki_path(@project, @page)} or browse the #{link_to "history", history_project_wiki_path(@project, @page)}. + You can view the #{link_to "most recent version", namespace_project_wiki_path(@project.namespace, @project, @page)} or browse the #{link_to "history", history_namespace_project_wiki_path(@project.namespace, @project, @page)}. %hr @@ -17,4 +17,4 @@ %hr .wiki-last-edit-by - Last edited by #{commit_author_link(@page.commit, avatar: true, size: 16)} #{time_ago_with_tooltip(@page.commit.created_at)} + Last edited by #{@page.commit.author.name} #{time_ago_with_tooltip(@page.commit.authored_date)} diff --git a/app/views/search/_filter.html.haml b/app/views/search/_filter.html.haml index 979b18e385..ffc145497a 100644 --- a/app/views/search/_filter.html.haml +++ b/app/views/search/_filter.html.haml @@ -1,6 +1,6 @@ .dropdown.inline - %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"} - %i.icon-tags + %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'} + %i.fa.fa-tags %span.light Group: - if @group.present? %strong= @group.name @@ -9,16 +9,16 @@ %b.caret %ul.dropdown-menu %li - = link_to search_path(group_id: nil, search: params[:search]) do + = link_to search_filter_path(group_id: nil) do Any - current_user.authorized_groups.sort_by(&:name).each do |group| %li - = link_to search_path(group_id: group.id, search: params[:search]) do + = link_to search_filter_path(group_id: group.id, project_id: nil) do = group.name -.dropdown.inline.prepend-left-10 - %a.dropdown-toggle.btn.btn-small{href: '#', "data-toggle" => "dropdown"} - %i.icon-tags +.dropdown.inline.prepend-left-10.project-filter + %button.dropdown-toggle.btn.btn-sm{type: 'button', 'data-toggle' => 'dropdown'} + %i.fa.fa-tags %span.light Project: - if @project.present? %strong= @project.name_with_namespace @@ -27,9 +27,9 @@ %b.caret %ul.dropdown-menu %li - = link_to search_path(project_id: nil, search: params[:search]) do + = link_to search_filter_path(project_id: nil) do Any - current_user.authorized_projects.sort_by(&:name_with_namespace).each do |project| %li - = link_to search_path(project_id: project.id, search: params[:search]) do + = link_to search_filter_path(project_id: project.id, group_id: nil) do = project.name_with_namespace diff --git a/app/views/search/_global_filter.html.haml b/app/views/search/_global_filter.html.haml new file mode 100644 index 0000000000..442bd84f93 --- /dev/null +++ b/app/views/search/_global_filter.html.haml @@ -0,0 +1,16 @@ +%ul.nav.nav-pills.nav-stacked.search-filter + %li{class: ("active" if @scope == 'projects')} + = link_to search_filter_path(scope: 'projects') do + Projects + .pull-right + = @search_results.projects_count + %li{class: ("active" if @scope == 'issues')} + = link_to search_filter_path(scope: 'issues') do + Issues + .pull-right + = @search_results.issues_count + %li{class: ("active" if @scope == 'merge_requests')} + = link_to search_filter_path(scope: 'merge_requests') do + Merge requests + .pull-right + = @search_results.merge_requests_count diff --git a/app/views/search/_global_results.html.haml b/app/views/search/_global_results.html.haml deleted file mode 100644 index 7f4f0e5e00..0000000000 --- a/app/views/search/_global_results.html.haml +++ /dev/null @@ -1,5 +0,0 @@ -.search_results - %ul.bordered-list - = render partial: "search/results/project", collection: @search_results[:projects] - = render partial: "search/results/merge_request", collection: @search_results[:merge_requests] - = render partial: "search/results/issue", collection: @search_results[:issues] diff --git a/app/views/search/_project_filter.html.haml b/app/views/search/_project_filter.html.haml new file mode 100644 index 0000000000..ad933502a2 --- /dev/null +++ b/app/views/search/_project_filter.html.haml @@ -0,0 +1,32 @@ +%ul.nav.nav-pills.nav-stacked.search-filter + %li{class: ("active" if @scope == 'blobs')} + = link_to search_filter_path(scope: 'blobs') do + %i.fa.fa-code + Code + .pull-right + = @search_results.blobs_count + %li{class: ("active" if @scope == 'issues')} + = link_to search_filter_path(scope: 'issues') do + %i.fa.fa-exclamation-circle + Issues + .pull-right + = @search_results.issues_count + %li{class: ("active" if @scope == 'merge_requests')} + = link_to search_filter_path(scope: 'merge_requests') do + %i.fa.fa-code-fork + Merge requests + .pull-right + = @search_results.merge_requests_count + %li{class: ("active" if @scope == 'notes')} + = link_to search_filter_path(scope: 'notes') do + %i.fa.fa-comments + Comments + .pull-right + = @search_results.notes_count + %li{class: ("active" if @scope == 'wiki_blobs')} + = link_to search_filter_path(scope: 'wiki_blobs') do + %i.fa.fa-book + Wiki + .pull-right + = @search_results.wiki_blobs_count + diff --git a/app/views/search/_project_results.html.haml b/app/views/search/_project_results.html.haml deleted file mode 100644 index 5e8346a826..0000000000 --- a/app/views/search/_project_results.html.haml +++ /dev/null @@ -1,24 +0,0 @@ -%ul.nav.nav-tabs - %li{class: ("active" if params[:search_code].present?)} - = link_to search_path(params.merge(search_code: true)) do - Repository Code - %li{class: ("active" if params[:search_code].blank?)} - = link_to search_path(params.merge(search_code: nil)) do - Issues and Merge requests - -.search_results - - if params[:search_code].present? - .blob-results - - if !@search_results[:blobs].empty? - = render partial: "search/results/blob", collection: @search_results[:blobs] - = paginate @search_results[:blobs], theme: 'gitlab' - - else - = render partial: "search/results/empty", :locals => { message: "We couldn't find any matching code" } - - else - - if @search_results[:merge_requests].present? || @search_results[:issues].present? || @search_results[:notes].present? - %ul.bordered-list - = render partial: "search/results/merge_request", collection: @search_results[:merge_requests] - = render partial: "search/results/issue", collection: @search_results[:issues] - = render partial: "search/results/note", collection: @search_results[:notes] - - else - = render partial: "search/results/empty", locals: { message: "We couldn't find any issues, merge requests or notes" } diff --git a/app/views/search/_results.html.haml b/app/views/search/_results.html.haml index 2336d0f71d..796dd752a4 100644 --- a/app/views/search/_results.html.haml +++ b/app/views/search/_results.html.haml @@ -1,17 +1,28 @@ %h4 - #{@search_results[:total_results]} results found - - if @project - for #{link_to @project.name_with_namespace, @project} - - elsif @group - for #{link_to @group.name, @group} + #{@search_results.total_count} results found + - unless @show_snippets + - if @project + for #{link_to @project.name_with_namespace, [@project.namespace.becomes(Namespace), @project]} + - elsif @group + for #{link_to @group.name, @group} %hr -- if @project - = render "project_results" -- else - = render "global_results" +.row + .col-sm-3 + - if @project + = render "project_filter" + - elsif @show_snippets + = render 'snippet_filter' + - else + = render "global_filter" + .col-sm-9 + .search-results + - if @search_results.empty? + = render partial: "search/results/empty", locals: { message: "We couldn't find any matching results" } + - else + = render partial: "search/results/#{@scope.singularize}", collection: @objects + = paginate @objects, theme: 'gitlab' :javascript - $(".search_results .term").highlight("#{escape_javascript(params[:search])}"); - + $(".search-results .term").highlight("#{escape_javascript(params[:search])}"); diff --git a/app/views/search/_snippet_filter.html.haml b/app/views/search/_snippet_filter.html.haml new file mode 100644 index 0000000000..95d23fa9f4 --- /dev/null +++ b/app/views/search/_snippet_filter.html.haml @@ -0,0 +1,13 @@ +%ul.nav.nav-pills.nav-stacked.search-filter + %li{class: ("active" if @scope == 'snippet_blobs')} + = link_to search_filter_path(scope: 'snippet_blobs', snippets: true, group_id: nil, project_id: nil) do + %i.fa.fa-code + Snippet Contents + .pull-right + = @search_results.snippet_blobs_count + %li{class: ("active" if @scope == 'snippet_titles')} + = link_to search_filter_path(scope: 'snippet_titles', snippets: true, group_id: nil, project_id: nil) do + %i.fa.fa-book + Titles and Filenames + .pull-right + = @search_results.snippet_titles_count diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml index f9d217e840..84e9be82c4 100644 --- a/app/views/search/results/_blob.html.haml +++ b/app/views/search/results/_blob.html.haml @@ -1,9 +1,9 @@ .blob-result .file-holder .file-title - = link_to project_blob_path(@project, tree_join(blob.ref, blob.filename), :anchor => "L" + blob.startline.to_s) do - %i.icon-file + = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(blob.ref, blob.filename), :anchor => "L" + blob.startline.to_s) do + %i.fa.fa-file %strong = blob.filename .file-content.code.term - = render 'shared/file_hljs', blob: blob, first_line_number: blob.startline + = render 'shared/file_highlight', blob: blob, first_line_number: blob.startline, user_color_scheme_class: 'white' diff --git a/app/views/search/results/_empty.html.haml b/app/views/search/results/_empty.html.haml index 3615f6ae52..01fb8cd9b8 100644 --- a/app/views/search/results/_empty.html.haml +++ b/app/views/search/results/_empty.html.haml @@ -1,4 +1,4 @@ .search_box .search_glyph - %span.icon-search + %span.fa.fa-search %h4 #{message} diff --git a/app/views/search/results/_issue.html.haml b/app/views/search/results/_issue.html.haml index 8147cf272f..ce8ddff955 100644 --- a/app/views/search/results/_issue.html.haml +++ b/app/views/search/results/_issue.html.haml @@ -1,9 +1,14 @@ -%li - issue: - = link_to [issue.project, issue] do - %span ##{issue.iid} - %strong.term - = truncate issue.title, length: 50 - %span.light (#{issue.project.name_with_namespace}) +.search-result-row + %h4 + = link_to [issue.project.namespace.becomes(Namespace), issue.project, issue] do + %span.term.str-truncated= issue.title + .pull-right ##{issue.iid} + - if issue.description.present? + .description.term + = preserve do + = search_md_sanitize(markdown(issue.description)) + %span.light + #{issue.project.name_with_namespace} - if issue.closed? - %span.label.label-danger Closed + .pull-right + %span.label.label-danger Closed diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml index de2a79970c..2efa616d66 100644 --- a/app/views/search/results/_merge_request.html.haml +++ b/app/views/search/results/_merge_request.html.haml @@ -1,14 +1,16 @@ -%li - merge request: - = link_to [merge_request.target_project, merge_request] do - %span ##{merge_request.iid} - %strong.term - = truncate merge_request.title, length: 50 - - if merge_request.for_fork? - %span.light (#{merge_request.source_project.name_with_namespace}:#{merge_request.source_branch} → #{merge_request.target_project.name_with_namespace}:#{merge_request.target_branch}) - - else - %span.light (#{merge_request.source_branch} → #{merge_request.target_branch}) - - if merge_request.merged? - %span.label.label-primary Merged - - elsif merge_request.closed? - %span.label.label-danger Closed +.search-result-row + %h4 + = link_to [merge_request.target_project.namespace.becomes(Namespace), merge_request.target_project, merge_request] do + %span.term.str-truncated= merge_request.title + .pull-right ##{merge_request.iid} + - if merge_request.description.present? + .description.term + = preserve do + = search_md_sanitize(markdown(merge_request.description)) + %span.light + #{merge_request.project.name_with_namespace} + .pull-right + - if merge_request.merged? + %span.label.label-primary Merged + - elsif merge_request.closed? + %span.label.label-danger Closed diff --git a/app/views/search/results/_note.html.haml b/app/views/search/results/_note.html.haml index 97e892bdd4..5fcba2b7e9 100644 --- a/app/views/search/results/_note.html.haml +++ b/app/views/search/results/_note.html.haml @@ -1,9 +1,26 @@ -%li - note on issue: - = link_to [note.project, note.noteable] do - %span ##{note.noteable.iid} - %strong.term - = truncate note.noteable.title, length: 50 - %span.light (#{note.project.name_with_namespace}) - - if note.noteable.closed? - %span.label Closed +- project = note.project +.search-result-row + %h5.note-search-caption.str-truncated + %i.fa.fa-comment + = link_to_member(project, note.author, avatar: false) + commented on + + - if note.for_commit? + = link_to project do + = project.name_with_namespace + · + = link_to namespace_project_commit_path(project.namespace, project, note.commit_id, anchor: dom_id(note)) do + Commit #{truncate_sha(note.commit_id)} + - else + = link_to project do + = project.name_with_namespace + · + %span #{note.noteable_type.titleize} ##{note.noteable.iid} + · + = link_to [project.namespace.becomes(Namespace), project, note.noteable, anchor: dom_id(note)] do + = note.noteable.title + + .note-search-result + .term + = preserve do + = search_md_sanitize(markdown(note.note, {no_header_anchors: true})) diff --git a/app/views/search/results/_project.html.haml b/app/views/search/results/_project.html.haml index abc86c72be..195cf06c8e 100644 --- a/app/views/search/results/_project.html.haml +++ b/app/views/search/results/_project.html.haml @@ -1,7 +1,6 @@ -%li - project: - = link_to project do - %strong.term= project.name_with_namespace +.search-result-row + %h4 + = link_to [project.namespace.becomes(Namespace), project] do + %span.term= project.name_with_namespace - if project.description.present? - – %span.light.term= project.description diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml new file mode 100644 index 0000000000..8af393777f --- /dev/null +++ b/app/views/search/results/_snippet_blob.html.haml @@ -0,0 +1,59 @@ +.search-result-row + %span + = snippet_blob[:snippet_object].title + by + = link_to user_snippets_path(snippet_blob[:snippet_object].author) do + = image_tag avatar_icon(snippet_blob[:snippet_object].author_email), class: "avatar avatar-inline s16", alt: '' + = snippet_blob[:snippet_object].author_name + %span.light #{time_ago_with_tooltip(snippet_blob[:snippet_object].created_at)} + %h4.snippet-title + - snippet_path = reliable_snippet_path(snippet_blob[:snippet_object]) + = link_to snippet_path do + .file-holder + .file-title + %i.fa.fa-file + %strong= snippet_blob[:snippet_object].file_name + - if gitlab_markdown?(snippet_blob[:snippet_object].file_name) + .file-content.wiki + - snippet_blob[:snippet_chunks].each do |snippet| + - unless snippet[:data].empty? + = preserve do + = markdown(snippet[:data]) + - else + .file-content.code + .nothing-here-block Empty file + - elsif markup?(snippet_blob[:snippet_object].file_name) + .file-content.wiki + - snippet_blob[:snippet_chunks].each do |snippet| + - unless snippet[:data].empty? + = render_markup(snippet_blob[:snippet_object].file_name, snippet[:data]) + - else + .file-content.code + .nothing-here-block Empty file + - else + .file-content.code + %div.highlighted-data{class: user_color_scheme_class} + .line-numbers + - snippet_blob[:snippet_chunks].each do |snippet| + - unless snippet[:data].empty? + - snippet[:data].lines.to_a.size.times do |index| + - offset = defined?(snippet[:start_line]) ? snippet[:start_line] : 1 + - i = index + offset + = link_to snippet_path+"#L#{i}", id: "L#{i}", rel: "#L#{i}" do + %i.fa.fa-link + = i + - unless snippet == snippet_blob[:snippet_chunks].last + %a + = "." + .highlight.term + %pre + %code + - snippet_blob[:snippet_chunks].each do |snippet| + - unless snippet[:data].empty? + = snippet[:data] + - unless snippet == snippet_blob[:snippet_chunks].last + %a + = "..." + - else + .file-content.code + .nothing-here-block Empty file diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml new file mode 100644 index 0000000000..c414acb6a1 --- /dev/null +++ b/app/views/search/results/_snippet_title.html.haml @@ -0,0 +1,23 @@ +.search-result-row + %h4.snippet-title.term + = link_to reliable_snippet_path(snippet_title) do + = truncate(snippet_title.title, length: 60) + - if snippet_title.private? + %span.label.label-gray + %i.fa.fa-lock + private + %span.cgray.monospace.tiny.pull-right.term + = snippet_title.file_name + + %small.pull-right.cgray + - if snippet_title.project_id? + = link_to snippet_title.project.name_with_namespace, namespace_project_path(snippet_title.project.namespace, snippet_title.project) + + .snippet-info + = "##{snippet_title.id}" + %span + by + = link_to user_snippets_path(snippet_title.author) do + = image_tag avatar_icon(snippet_title.author_email), class: "avatar avatar-inline s16", alt: '' + = snippet_title.author_name + %span.light #{time_ago_with_tooltip(snippet_title.created_at)} diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml new file mode 100644 index 0000000000..f9c5810e3d --- /dev/null +++ b/app/views/search/results/_wiki_blob.html.haml @@ -0,0 +1,9 @@ +.blob-result + .file-holder + .file-title + = link_to namespace_project_wiki_path(@project.namespace, @project, wiki_blob.filename) do + %i.fa.fa-file + %strong + = wiki_blob.filename + .file-content.code.term + = render 'shared/file_highlight', blob: wiki_blob, first_line_number: wiki_blob.startline, user_color_scheme_class: 'white' diff --git a/app/views/search/show.html.haml b/app/views/search/show.html.haml index 3b6f10d4d9..5b4816e4c4 100644 --- a/app/views/search/show.html.haml +++ b/app/views/search/show.html.haml @@ -6,14 +6,16 @@ .col-sm-6 = search_field_tag :search, params[:search], placeholder: "issue 143", class: "form-control search-text-input", id: "dashboard_search" .col-sm-4 - = submit_tag 'Search', class: "btn btn-create" + = button_tag 'Search', class: "btn btn-create" .form-group .col-sm-2 - .col-sm-10 - = render 'filter', f: f + - unless params[:snippets].eql? 'true' + .col-sm-10 + = render 'filter', f: f = hidden_field_tag :project_id, params[:project_id] = hidden_field_tag :group_id, params[:group_id] - = hidden_field_tag :search_code, params[:search_code] + = hidden_field_tag :snippets, params[:snippets] + = hidden_field_tag :scope, params[:scope] .results.prepend-top-10 - if params[:search].present? diff --git a/app/views/shared/_choose_group_avatar_button.html.haml b/app/views/shared/_choose_group_avatar_button.html.haml new file mode 100644 index 0000000000..000532b1c9 --- /dev/null +++ b/app/views/shared/_choose_group_avatar_button.html.haml @@ -0,0 +1,7 @@ +%a.choose-btn.btn.btn-sm.js-choose-group-avatar-button + %i.fa.fa-paperclip + %span Choose File ... +  +%span.file_name.js-avatar-filename File name... += f.file_field :avatar, class: 'js-group-avatar-input hidden' +.light The maximum file size allowed is 200KB. diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml index 8cd426c71e..a1121750ca 100644 --- a/app/views/shared/_clone_panel.html.haml +++ b/app/views/shared/_clone_panel.html.haml @@ -1,6 +1,23 @@ - project = project || @project .git-clone-holder.input-group .input-group-btn - %button{class: "btn #{ 'active' if default_clone_protocol == 'ssh' }", :"data-clone" => project.ssh_url_to_repo} SSH - %button{class: "btn #{ 'active' if default_clone_protocol == 'http' }", :"data-clone" => project.http_url_to_repo}= gitlab_config.protocol.upcase + %button{ | + class: "btn #{ 'active' if default_clone_protocol == 'ssh' }#{ ' has_tooltip' if current_user && current_user.require_ssh_key? }", | + :"data-clone" => project.ssh_url_to_repo, | + :"data-title" => "Add an SSH key to your profile
    to pull or push via SSH", + :"data-html" => "true", + :"data-container" => "body"} + SSH + %button{ | + class: "btn #{ 'active' if default_clone_protocol == 'http' }#{ ' has_tooltip' if current_user && current_user.require_password? }", | + :"data-clone" => project.http_url_to_repo, | + :"data-title" => "Set a password on your account
    to pull or push via #{gitlab_config.protocol.upcase}", + :"data-html" => "true", + :"data-container" => "body"} + = gitlab_config.protocol.upcase = text_field_tag :project_clone, default_url_to_repo(project), class: "one_click_select form-control", readonly: true + - if project.kind_of?(Project) + .input-group-addon + .visibility-level-label.has_tooltip{'data-title' => "#{visibility_level_label(project.visibility_level)} project" } + = visibility_level_icon(project.visibility_level) + = visibility_level_label(project.visibility_level).downcase diff --git a/app/views/shared/_commit_message_container.html.haml b/app/views/shared/_commit_message_container.html.haml index 4365947e70..5071ff640f 100644 --- a/app/views/shared/_commit_message_container.html.haml +++ b/app/views/shared/_commit_message_container.html.haml @@ -1,3 +1,14 @@ -.commit-message-container - .max-width-marker - = textarea +.form-group.commit_message-group + = label_tag 'commit_message', class: 'control-label' do + Commit message + .col-sm-10 + .commit-message-container + .max-width-marker + = text_area_tag 'commit_message', + (params[:commit_message] || local_assigns[:text]), + class: 'form-control', placeholder: local_assigns[:placeholder], + required: true, rows: (local_assigns[:rows] || 3) + - if local_assigns[:hint] + %p.hint + Try to keep the first line under 52 characters + and the others under 72. diff --git a/app/views/shared/_confirm_modal.html.haml b/app/views/shared/_confirm_modal.html.haml new file mode 100644 index 0000000000..30ba361c86 --- /dev/null +++ b/app/views/shared/_confirm_modal.html.haml @@ -0,0 +1,22 @@ +#modal-confirm-danger.modal.hide{tabindex: -1} + .modal-dialog + .modal-content + .modal-header + %a.close{href: "#", "data-dismiss" => "modal"} × + %h4 Confirmation required + + .modal-body + %p.cred.lead.js-confirm-text + + %p + This action can lead to data loss. + To prevent accidental actions we ask you to confirm your intention. + %br + Please type + %code.js-confirm-danger-match #{phrase} + to proceed or close this modal to cancel + + .form-group + = text_field_tag 'confirm_name_input', '', class: 'form-control js-confirm-danger-input' + .form-group + = submit_tag 'Confirm', class: "btn btn-danger js-confirm-danger-submit" diff --git a/app/views/shared/_event_filter.html.haml b/app/views/shared/_event_filter.html.haml index ee0b57fbe5..d07a9e2b92 100644 --- a/app/views/shared/_event_filter.html.haml +++ b/app/views/shared/_event_filter.html.haml @@ -1,5 +1,19 @@ -.event_filter +%ul.nav.nav-pills.event_filter = 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' + + - if current_user + - if current_controller?(:dashboard) + %li.pull-right + = link_to dashboard_path(:atom, { private_token: current_user.private_token }), class: 'rss-btn' do + %i.fa.fa-rss + News Feed + + - if current_controller?(:groups) + %li.pull-right + = link_to group_path(@group, { format: :atom, private_token: current_user.private_token }), title: "Feed", class: 'rss-btn' do + %i.fa.fa-rss + News Feed +%hr diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml new file mode 100644 index 0000000000..fba69dd0f3 --- /dev/null +++ b/app/views/shared/_file_highlight.html.haml @@ -0,0 +1,11 @@ +.file-content.code{class: user_color_scheme_class} + .line-numbers + - if blob.data.present? + - blob.data.lines.to_a.size.times do |index| + - offset = defined?(first_line_number) ? first_line_number : 1 + - i = index + offset + = link_to "#L#{i}", id: "L#{i}", rel: "#L#{i}" do + %i.fa.fa-link + = i + :preserve + #{highlight(blob.name, blob.data)} diff --git a/app/views/shared/_file_hljs.html.haml b/app/views/shared/_file_hljs.html.haml deleted file mode 100644 index ceee2c7527..0000000000 --- a/app/views/shared/_file_hljs.html.haml +++ /dev/null @@ -1,12 +0,0 @@ -%div.highlighted-data{class: user_color_scheme_class} - .line-numbers - - blob.data.lines.to_a.size.times do |index| - - offset = defined?(first_line_number) ? first_line_number : 1 - - i = index + offset - = link_to "#L#{i}", id: "L#{i}", rel: "#L#{i}" do - %i.icon-link - = i - .highlight - %pre - %code{ class: highlightjs_class(blob.name) } - = blob.data diff --git a/app/views/shared/_filter.html.haml b/app/views/shared/_filter.html.haml deleted file mode 100644 index 9e65ce11ad..0000000000 --- a/app/views/shared/_filter.html.haml +++ /dev/null @@ -1,50 +0,0 @@ -.side-filters - = form_tag filter_path(entity), method: 'get' do - - if current_user - %fieldset.scope-filter - %ul.nav.nav-pills.nav-stacked - %li{class: ("active" if params[:scope] == 'assigned-to-me')} - = link_to filter_path(entity, scope: 'assigned-to-me') do - Assigned to me - %span.pull-right - = assigned_entities_count(current_user, entity, @group) - %li{class: ("active" if params[:scope] == 'authored')} - = link_to filter_path(entity, scope: 'authored') do - Created by me - %span.pull-right - = authored_entities_count(current_user, entity, @group) - %li{class: ("active" if params[:scope] == 'all')} - = link_to filter_path(entity, scope: 'all') do - Everyone's - %span.pull-right - = authorized_entities_count(current_user, entity, @group) - - %fieldset.status-filter - %legend State - %ul.nav.nav-pills - %li{class: ("active" if params[:state] == 'opened')} - = link_to filter_path(entity, state: 'opened') do - Open - %li{class: ("active" if params[:state] == 'closed')} - = link_to filter_path(entity, state: 'closed') do - Closed - %li{class: ("active" if params[:state] == 'all')} - = link_to filter_path(entity, state: 'all') do - All - - %fieldset - %legend Projects - %ul.nav.nav-pills.nav-stacked.nav-small - - @projects.each do |project| - - unless entities_per_project(project, entity).zero? - %li{class: ("active" if params[:project_id] == project.id.to_s)} - = link_to filter_path(entity, project_id: project.id) do - = project.name_with_namespace - %small.pull-right= entities_per_project(project, entity) - - %fieldset - - if params[:state].present? || params[:project_id].present? - = link_to filter_path(entity, state: nil, project_id: nil), class: 'pull-right cgray' do - %i.icon-remove - %strong Clear filter - diff --git a/app/views/shared/_group_form.html.haml b/app/views/shared/_group_form.html.haml new file mode 100644 index 0000000000..c0a9923348 --- /dev/null +++ b/app/views/shared/_group_form.html.haml @@ -0,0 +1,29 @@ +- if @group.persisted? + .form-group + = f.label :name, class: 'control-label' do + Group name + .col-sm-10 + = f.text_field :name, placeholder: 'open-source', class: 'form-control' + +.form-group + = f.label :path, class: 'control-label' do + Group path + .col-sm-10 + .input-group + .input-group-addon + = root_url + = f.text_field :path, placeholder: 'open-source', class: 'form-control', + autofocus: local_assigns[:autofocus] || false + - if @group.persisted? + .alert.alert-warning.prepend-top-10 + %ul + %li Changing group path can have unintended side effects. + %li Renaming group path will rename directory for all related projects + %li It will change web url for access group and group projects. + %li It will change the git path to repositories under this group. + +.form-group.group-description-holder + = f.label :description, 'Details', class: 'control-label' + .col-sm-10 + = f.text_area :description, maxlength: 250, + class: 'form-control js-gfm-input', rows: 4 diff --git a/app/views/shared/_group_tips.html.haml b/app/views/shared/_group_tips.html.haml new file mode 100644 index 0000000000..e5cf783beb --- /dev/null +++ b/app/views/shared/_group_tips.html.haml @@ -0,0 +1,6 @@ +%ul + %li A group is a collection of several projects + %li Groups are private by default + %li Members of a group may only view projects they have permission to access + %li Group project URLs are prefixed with the group namespace + %li Existing projects may be moved into a group diff --git a/app/views/shared/_issuable_filter.html.haml b/app/views/shared/_issuable_filter.html.haml new file mode 100644 index 0000000000..83f5a3a801 --- /dev/null +++ b/app/views/shared/_issuable_filter.html.haml @@ -0,0 +1,58 @@ +.issues-filters + .issues-state-filters + %ul.nav.nav-tabs + %li{class: ("active" if params[:state] == 'opened')} + = link_to page_filter_path(state: 'opened') do + %i.fa.fa-exclamation-circle + Open + %li{class: ("active" if params[:state] == 'closed')} + = link_to page_filter_path(state: 'closed') do + %i.fa.fa-check-circle + Closed + %li{class: ("active" if params[:state] == 'all')} + = link_to page_filter_path(state: 'all') do + %i.fa.fa-compass + All + + .issues-details-filters + = form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_id, :label_name]), method: :get, class: 'filter-form' do + - if controller.controller_name == 'issues' + .check-all-holder + = check_box_tag "check_all_issues", nil, false, + class: "check_all_issues left", + disabled: !can?(current_user, :modify_issue, @project) + .issues-other-filters + .filter-item.inline + = users_select_tag(:assignee_id, selected: params[:assignee_id], + placeholder: 'Assignee', class: 'trigger-submit', any_user: true, null_user: true, first_user: true) + + .filter-item.inline + = users_select_tag(:author_id, selected: params[:author_id], + placeholder: 'Author', class: 'trigger-submit', any_user: true, first_user: true) + + .filter-item.inline.milestone-filter + = select_tag('milestone_id', projects_milestones_options, class: "select2 trigger-submit", prompt: 'Milestone') + + - if @project + .filter-item.inline.labels-filter + = select_tag('label_name', project_labels_options(@project), class: "select2 trigger-submit", prompt: 'Label') + + .pull-right + = render 'shared/sort_dropdown' + + - if controller.controller_name == 'issues' + .issues_bulk_update.hide + = form_tag bulk_update_namespace_project_issues_path(@project.namespace, @project), method: :post do + = select_tag('update[state_event]', options_for_select([['Open', 'reopen'], ['Closed', 'close']]), prompt: "Status", class: 'form-control') + = users_select_tag('update[assignee_id]', placeholder: 'Assignee', null_user: true) + = select_tag('update[milestone_id]', bulk_update_milestone_options, prompt: "Milestone") + = hidden_field_tag 'update[issues_ids]', [] + = hidden_field_tag :state_event, params[:state_event] + = button_tag "Update issues", class: "btn update_selected_issues btn-save" + +:coffeescript + new UsersSelect() + + $('form.filter-form').on 'submit', (event) -> + event.preventDefault() + Turbolinks.visit @.action + '&' + $(@).serialize() diff --git a/app/views/shared/_issuable_search_form.html.haml b/app/views/shared/_issuable_search_form.html.haml new file mode 100644 index 0000000000..639d203dcd --- /dev/null +++ b/app/views/shared/_issuable_search_form.html.haml @@ -0,0 +1,9 @@ += form_tag(path, method: :get, id: "issue_search_form", class: 'pull-left issue-search-form') do + .append-right-10.hidden-xs.hidden-sm + = search_field_tag :issue_search, params[:issue_search], { placeholder: 'Filter by title or description', class: 'form-control issue_search search-text-input input-mn-300' } + = 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/_issues.html.haml b/app/views/shared/_issues.html.haml index e976f897dc..0dbb6a0439 100644 --- a/app/views/shared/_issues.html.haml +++ b/app/views/shared/_issues.html.haml @@ -4,7 +4,7 @@ - project = group[0] .panel-heading = link_to_project project - = link_to 'show all', project_issues_path(project), class: 'pull-right' + = link_to 'show all', namespace_project_issues_path(project.namespace, project), class: 'pull-right' %ul.well-list.issues-list - group[1].each do |issue| diff --git a/app/views/shared/_merge_requests.html.haml b/app/views/shared/_merge_requests.html.haml index 39a1ee38f8..c02c5af008 100644 --- a/app/views/shared/_merge_requests.html.haml +++ b/app/views/shared/_merge_requests.html.haml @@ -4,7 +4,7 @@ - project = group[0] .panel-heading = link_to_project project - = link_to 'show all', project_merge_requests_path(project), class: 'pull-right' + = link_to 'show all', namespace_project_merge_requests_path(project.namespace, project), class: 'pull-right' %ul.well-list.mr-list - group[1].each do |merge_request| = render 'projects/merge_requests/merge_request', merge_request: merge_request diff --git a/app/views/shared/_milestones_filter.html.haml b/app/views/shared/_milestones_filter.html.haml new file mode 100644 index 0000000000..f685ae7726 --- /dev/null +++ b/app/views/shared/_milestones_filter.html.haml @@ -0,0 +1,14 @@ +.milestones-filters.append-bottom-10 + %ul.nav.nav-tabs + %li{class: ("active" if params[:state].blank? || params[:state] == 'opened')} + = link_to milestones_filter_path(state: 'opened') do + %i.fa.fa-exclamation-circle + Open + %li{class: ("active" if params[:state] == 'closed')} + = link_to milestones_filter_path(state: 'closed') do + %i.fa.fa-check-circle + Closed + %li{class: ("active" if params[:state] == 'all')} + = link_to milestones_filter_path(state: 'all') do + %i.fa.fa-compass + All diff --git a/app/views/shared/_no_password.html.haml b/app/views/shared/_no_password.html.haml new file mode 100644 index 0000000000..a43bf33751 --- /dev/null +++ b/app/views/shared/_no_password.html.haml @@ -0,0 +1,8 @@ +- if cookies[:hide_no_password_message].blank? && !current_user.hide_no_password && current_user.require_password? + .no-password-message.alert.alert-warning.hidden-xs + You won't be able to pull or push project code via #{gitlab_config.protocol.upcase} until you #{link_to 'set a password', edit_profile_password_path} on your account + + .pull-right + = link_to "Don't show again", profile_path(user: {hide_no_password: true}), method: :put + | + = link_to 'Remind later', '#', class: 'hide-no-password-message' diff --git a/app/views/shared/_no_ssh.html.haml b/app/views/shared/_no_ssh.html.haml index e70eb4d01b..089179e677 100644 --- a/app/views/shared/_no_ssh.html.haml +++ b/app/views/shared/_no_ssh.html.haml @@ -1,14 +1,8 @@ -- if cookies[:hide_no_ssh_message].blank? && current_user.require_ssh_key? && !current_user.hide_no_ssh_key - .no-ssh-key-message - .container - You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_profile_key_path} to your profile - .pull-right.hidden-xs - = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'hide-no-ssh-message', remote: true - | - = link_to 'Remind later', '#', class: 'hide-no-ssh-message' - .links-xs.visible-xs - = link_to "Add key", new_profile_key_path - | - = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'hide-no-ssh-message', remote: true - | - = link_to 'Later', '#', class: 'hide-no-ssh-message' +- if cookies[:hide_no_ssh_message].blank? && !current_user.hide_no_ssh_key && current_user.require_ssh_key? + .no-ssh-key-message.alert.alert-warning.hidden-xs + You won't be able to pull or push project code via SSH until you #{link_to 'add an SSH key', new_profile_key_path, class: 'alert-link'} to your profile + + .pull-right + = link_to "Don't show again", profile_path(user: {hide_no_ssh_key: true}), method: :put, class: 'alert-link' + | + = link_to 'Remind later', '#', class: 'hide-no-ssh-message alert-link' diff --git a/app/views/shared/_outdated_browser.html.haml b/app/views/shared/_outdated_browser.html.haml new file mode 100644 index 0000000000..0eba1fe075 --- /dev/null +++ b/app/views/shared/_outdated_browser.html.haml @@ -0,0 +1,8 @@ +- if outdated_browser? + - link = "https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/requirements.md#supported-web-browsers" + .browser-alert + GitLab may not work properly because you are using an outdated web browser. + %br + Please install a + = link_to 'supported web browser', link + for a better experience. diff --git a/app/views/shared/_project.html.haml b/app/views/shared/_project.html.haml new file mode 100644 index 0000000000..722a7f7ce0 --- /dev/null +++ b/app/views/shared/_project.html.haml @@ -0,0 +1,21 @@ += cache [project.namespace, project, controller.controller_name, controller.action_name] do + = link_to project_path(project), class: dom_class(project) do + - if avatar + .dash-project-avatar + = project_icon(project, alt: '', class: 'avatar project-avatar s40') + .dash-project-access-icon + = visibility_level_icon(project.visibility_level) + %span.str-truncated + %span.namespace-name + - if project.namespace + = project.namespace.human_name + \/ + %span.project-name.filter-title + = project.name + - if stars + %span.pull-right.light + %i.fa.fa-star + = project.star_count + - else + %span.arrow + %i.fa.fa-angle-right diff --git a/app/views/shared/_project_filter.html.haml b/app/views/shared/_project_filter.html.haml deleted file mode 100644 index 5e7d1c2c88..0000000000 --- a/app/views/shared/_project_filter.html.haml +++ /dev/null @@ -1,64 +0,0 @@ -.side-filters - = form_tag project_entities_path, method: 'get' do - - if current_user - %fieldset - %ul.nav.nav-pills.nav-stacked - %li{class: ("active" if params[:scope] == 'all')} - = link_to project_filter_path(scope: 'all') do - Everyone's - %span.pull-right - = authorized_entities_count(current_user, entity, @project) - %li{class: ("active" if params[:scope] == 'assigned-to-me')} - = link_to project_filter_path(scope: 'assigned-to-me') do - Assigned to me - %span.pull-right - = assigned_entities_count(current_user, entity, @project) - %li{class: ("active" if params[:scope] == 'created-by-me')} - = link_to project_filter_path(scope: 'created-by-me') do - Created by me - %span.pull-right - = authored_entities_count(current_user, entity, @project) - - %fieldset - %legend State - %ul.nav.nav-pills - %li{class: ("active" if params[:state] == 'opened')} - = link_to project_filter_path(state: 'opened') do - Open - %li{class: ("active" if params[:state] == 'closed')} - = link_to project_filter_path(state: 'closed') do - Closed - %li{class: ("active" if params[:state] == 'all')} - = link_to project_filter_path(state: 'all') do - All - - - if defined?(labels) - %fieldset - %legend - Labels - %small.pull-right - = link_to project_labels_path(@project), class: 'light' do - %i.icon-edit - %ul.nav.nav-pills.nav-stacked.nav-small.labels-filter - - @project.labels.order_by_name.each do |label| - %li{class: label_filter_class(label.name)} - = link_to labels_filter_path(label.name) do - = render_colored_label(label) - - if selected_label?(label.name) - .pull-right - %i.icon-remove - - - if @project.labels.empty? - .light-well - Create first label at - = link_to 'labels page', project_labels_path(@project) - %br - or #{link_to 'generate', generate_project_labels_path(@project, redirect: redirect), method: :post} default set of labels - - %fieldset - - if %w(state scope milestone_id assignee_id label_name).select { |k| params[k].present? }.any? - = link_to project_entities_path, class: 'cgray pull-right' do - %i.icon-remove - %strong Clear filter - - diff --git a/app/views/shared/_projects_list.html.haml b/app/views/shared/_projects_list.html.haml new file mode 100644 index 0000000000..4c58092af4 --- /dev/null +++ b/app/views/shared/_projects_list.html.haml @@ -0,0 +1,17 @@ +- projects_limit = 20 unless local_assigns[:projects_limit] +- avatar = true unless local_assigns[:avatar] == false +- stars = false unless local_assigns[:stars] == true +%ul.well-list.projects-list + - projects.each_with_index do |project, i| + %li{class: (i >= projects_limit) ? 'project-row hide' : 'project-row'} + = render "shared/project", project: project, avatar: avatar, stars: stars + - if projects.blank? + %li + .nothing-here-block There are no projects here. + - if projects.count > projects_limit + %li.bottom + %span.light + #{projects_limit} of #{pluralize(projects.count, 'project')} displayed. + %span + = link_to '#', class: 'js-expand' do + Show all diff --git a/app/views/shared/_promo.html.haml b/app/views/shared/_promo.html.haml index 7dec48e658..3596aabe30 100644 --- a/app/views/shared/_promo.html.haml +++ b/app/views/shared/_promo.html.haml @@ -1,4 +1,5 @@ .gitlab-promo - = link_to "Homepage", "https://www.gitlab.com/" - = link_to "Blog", "https://www.gitlab.com/blog/" - = link_to "@gitlabhq", "https://twitter.com/gitlabhq" + = link_to 'Homepage', promo_url + = link_to "Blog", promo_url + '/blog/' + = link_to "@gitlab", "https://twitter.com/gitlab" + = link_to "Requests", "http://feedback.gitlab.com/" diff --git a/app/views/shared/_ref_switcher.html.haml b/app/views/shared/_ref_switcher.html.haml index 4d9534f49b..eb2e1919e1 100644 --- a/app/views/shared/_ref_switcher.html.haml +++ b/app/views/shared/_ref_switcher.html.haml @@ -1,4 +1,4 @@ -= form_tag switch_project_refs_path(@project), method: :get, class: "project-refs-form" do += form_tag switch_namespace_project_refs_path(@project.namespace, @project), method: :get, class: "project-refs-form" do = select_tag "ref", grouped_options_refs, class: "project-refs-select select2 select2-sm" = hidden_field_tag :destination, destination - if defined?(path) diff --git a/app/views/shared/_sort_dropdown.html.haml b/app/views/shared/_sort_dropdown.html.haml index 7b37b39780..af3d35de32 100644 --- a/app/views/shared/_sort_dropdown.html.haml +++ b/app/views/shared/_sort_dropdown.html.haml @@ -1,22 +1,22 @@ .dropdown.inline.prepend-left-10 - %a.dropdown-toggle.btn{href: '#', "data-toggle" => "dropdown"} + %button.dropdown-toggle.btn{type: 'button', 'data-toggle' => 'dropdown'} %span.light sort: - if @sort.present? - = @sort + = sort_options_hash[@sort] - else - Newest + = sort_title_recently_created %b.caret - %ul.dropdown-menu + %ul.dropdown-menu.dropdown-menu-align-right %li - = link_to project_filter_path(sort: 'newest') do - Newest - = link_to project_filter_path(sort: 'oldest') do - Oldest - = link_to project_filter_path(sort: 'recently_updated') do - Recently updated - = link_to project_filter_path(sort: 'last_updated') do - Last updated - = link_to project_filter_path(sort: 'milestone_due_soon') do - Milestone due soon - = link_to project_filter_path(sort: 'milestone_due_later') do - Milestone due later + = link_to page_filter_path(sort: sort_value_recently_created) do + = sort_title_recently_created + = link_to page_filter_path(sort: sort_value_oldest_created) do + = sort_title_oldest_created + = link_to page_filter_path(sort: sort_value_recently_updated) do + = sort_title_recently_updated + = link_to page_filter_path(sort: sort_value_oldest_updated) do + = sort_title_oldest_updated + = link_to page_filter_path(sort: sort_value_milestone_soon) do + = sort_title_milestone_soon + = link_to page_filter_path(sort: sort_value_milestone_later) do + = sort_title_milestone_later diff --git a/app/views/shared/snippets/_blob.html.haml b/app/views/shared/snippets/_blob.html.haml index 8cec6168ab..30458793fd 100644 --- a/app/views/shared/snippets/_blob.html.haml +++ b/app/views/shared/snippets/_blob.html.haml @@ -8,7 +8,7 @@ = render_markup(@snippet.file_name, @snippet.data) - else .file-content.code - = render 'shared/file_hljs', blob: @snippet + = render 'shared/file_highlight', blob: @snippet - else .file-content.code .nothing-here-block Empty file diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml index 49ea8460e7..4e0663ea20 100644 --- a/app/views/shared/snippets/_form.html.haml +++ b/app/views/shared/snippets/_form.html.haml @@ -10,22 +10,8 @@ = f.label :title, class: 'control-label' .col-sm-10= f.text_field :title, placeholder: "Example Snippet", class: 'form-control', required: true - - unless @snippet.respond_to?(:project) - .form-group - = f.label "Access", class: 'control-label' - .col-sm-10 - = f.label :private_true, class: 'radio-label' do - = f.radio_button :private, true - %span - %strong Private - (only you can see this snippet) - %br - = f.label :private_false, class: 'radio-label' do - = f.radio_button :private, false - %span - %strong Public - (GitLab users can see this snippet) - + = render "shared/snippets/visibility_level", f: f, visibility_level: gitlab_config.default_projects_features.visibility_level, can_change_visibility_level: true + .form-group .file-editor = f.label :file_name, "File", class: 'control-label' @@ -44,7 +30,7 @@ = f.submit 'Save', class: "btn-save btn" - if @snippet.respond_to?(:project) - = link_to "Cancel", project_snippets_path(@project), class: "btn btn-cancel" + = link_to "Cancel", namespace_project_snippets_path(@project.namespace, @project), class: "btn btn-cancel" - else = link_to "Cancel", snippets_path(@project), class: "btn btn-cancel" diff --git a/app/views/shared/snippets/_visibility_level.html.haml b/app/views/shared/snippets/_visibility_level.html.haml new file mode 100644 index 0000000000..9acff18e45 --- /dev/null +++ b/app/views/shared/snippets/_visibility_level.html.haml @@ -0,0 +1,27 @@ +.form-group.project-visibility-level-holder + = f.label :visibility_level, class: 'control-label' do + Visibility Level + = link_to "(?)", help_page_path("public_access", "public_access") + .col-sm-10 + - if can_change_visibility_level + - Gitlab::VisibilityLevel.values.each do |level| + .radio + - restricted = restricted_visibility_levels.include?(level) + = f.radio_button :visibility_level, level, disabled: restricted + = label "#{dom_class(@snippet)}_visibility_level", level do + = visibility_level_icon(level) + .option-title + = visibility_level_label(level) + .option-descr + = snippet_visibility_level_description(level) + - unless restricted_visibility_levels.empty? + .col-sm-10 + %span.info + Some visibility level settings have been restricted by the administrator. + - else + .col-sm-10 + %span.info + = visibility_level_icon(visibility_level) + %strong + = visibility_level_label(visibility_level) + .light= visibility_level_description(visibility_level) diff --git a/app/views/snippets/_snippet.html.haml b/app/views/snippets/_snippet.html.haml index e6f8316733..5bb2866434 100644 --- a/app/views/snippets/_snippet.html.haml +++ b/app/views/snippets/_snippet.html.haml @@ -4,14 +4,14 @@ = truncate(snippet.title, length: 60) - if snippet.private? %span.label.label-gray - %i.icon-lock + %i.fa.fa-lock private %span.cgray.monospace.tiny.pull-right = snippet.file_name %small.pull-right.cgray - if snippet.project_id? - = link_to snippet.project.name_with_namespace, project_path(snippet.project) + = link_to snippet.project.name_with_namespace, namespace_project_path(snippet.project.namespace, snippet.project) .snippet-info = "##{snippet.id}" diff --git a/app/views/snippets/current_user_index.html.haml b/app/views/snippets/current_user_index.html.haml index e3edd85698..0df5ade500 100644 --- a/app/views/snippets/current_user_index.html.haml +++ b/app/views/snippets/current_user_index.html.haml @@ -1,5 +1,5 @@ %h3.page-title - My Snippets + Your Snippets .pull-right = link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do Add new snippet @@ -23,6 +23,11 @@ Private %span.pull-right = @user.snippets.are_private.count + = nav_tab :scope, 'are_internal' do + = link_to user_snippets_path(@user, scope: 'are_internal') do + Internal + %span.pull-right + = @user.snippets.are_internal.count = nav_tab :scope, 'are_public' do = link_to user_snippets_path(@user, scope: 'are_public') do Public diff --git a/app/views/snippets/index.html.haml b/app/views/snippets/index.html.haml index cea2517a8e..5cd8ae26cf 100644 --- a/app/views/snippets/index.html.haml +++ b/app/views/snippets/index.html.haml @@ -2,10 +2,12 @@ Public snippets .pull-right - = link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do - Add new snippet - = link_to user_snippets_path(current_user), class: "btn btn-grouped" do - My snippets + + - if current_user + = link_to new_snippet_path, class: "btn btn-new btn-grouped", title: "New Snippet" do + Add new snippet + = link_to user_snippets_path(current_user), class: "btn btn-grouped" do + Your snippets %p.light Public snippets created by you and other users are listed here diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml index 1d2e3d5ae4..5204fb9a90 100644 --- a/app/views/snippets/show.html.haml +++ b/app/views/snippets/show.html.haml @@ -3,7 +3,7 @@ - if @snippet.private? %span.label.label-success - %i.icon-lock + %i.fa.fa-lock private .pull-right @@ -17,27 +17,27 @@ %span.light by = link_to user_snippets_path(@snippet.author) do - = image_tag avatar_icon(@snippet.author_email), class: "avatar avatar-inline s16" + = image_tag avatar_icon(@snippet.author_email), class: "avatar avatar-inline s16", alt: '' = @snippet.author_name .back-link - if @snippet.author == current_user = link_to user_snippets_path(current_user) do - ← my snippets + ← your snippets - else = link_to snippets_path do ← discover snippets .file-holder .file-title - %i.icon-file - %span.file_name + %i.fa.fa-file + %strong = @snippet.file_name - .options + .file-actions .btn-group - if can?(current_user, :modify_personal_snippet, @snippet) - = link_to "edit", edit_snippet_path(@snippet), class: "btn btn-small", title: 'Edit Snippet' - = link_to "raw", raw_snippet_path(@snippet), class: "btn btn-small", target: "_blank" + = link_to "edit", edit_snippet_path(@snippet), class: "btn btn-sm", title: 'Edit Snippet' + = link_to "raw", raw_snippet_path(@snippet), class: "btn btn-sm", target: "_blank" - if can?(current_user, :admin_personal_snippet, @snippet) - = link_to "remove", snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-small btn-remove", title: 'Delete Snippet' + = link_to "remove", snippet_path(@snippet), method: :delete, data: { confirm: "Are you sure?" }, class: "btn btn-sm btn-remove", title: 'Delete Snippet' = render 'shared/snippets/blob' diff --git a/app/views/snippets/user_index.html.haml b/app/views/snippets/user_index.html.haml index 1cb53ec6a2..df524cd18b 100644 --- a/app/views/snippets/user_index.html.haml +++ b/app/views/snippets/user_index.html.haml @@ -4,8 +4,9 @@ %span \/ Snippets - = link_to new_snippet_path, class: "btn btn-small add_new pull-right", title: "New Snippet" do - Add new snippet + - if current_user + = link_to new_snippet_path, class: "btn btn-sm add_new pull-right", title: "New Snippet" do + Add new snippet %hr diff --git a/app/views/users/_groups.html.haml b/app/views/users/_groups.html.haml index 09b2985d49..f360fbb3d5 100644 --- a/app/views/users/_groups.html.haml +++ b/app/views/users/_groups.html.haml @@ -1,3 +1,4 @@ -- groups.each do |group| - = link_to group, class: 'profile-groups-avatars', :title => group.name do - = image_tag group_icon(group.path) +.clearfix + - groups.each do |group| + = link_to group, class: 'profile-groups-avatars inline', title: group.name do + = image_tag group_icon(group), class: 'avatar group-avatar s40' diff --git a/app/views/users/_profile.html.haml b/app/views/users/_profile.html.haml index 3b44959baa..90d9980c85 100644 --- a/app/views/users/_profile.html.haml +++ b/app/views/users/_profile.html.haml @@ -5,6 +5,10 @@ %li %span.light Member since %strong= user.created_at.stamp("Aug 21, 2011") + - unless user.public_email.blank? + %li + %span.light E-mail: + %strong= link_to user.public_email, "mailto:#{user.public_email}" - unless user.skype.blank? %li %span.light Skype: @@ -12,7 +16,7 @@ - unless user.linkedin.blank? %li %span.light LinkedIn: - %strong= user.linkedin + %strong= link_to user.linkedin, "http://www.linkedin.com/in/#{user.linkedin}" - unless user.twitter.blank? %li %span.light Twitter: @@ -21,7 +25,7 @@ %li %span.light Website: %strong= link_to user.short_website_url, user.full_website_url - - unless user.bio.blank? + - unless user.location.blank? %li - %span.light Bio: - %span= user.bio + %span.light Location: + %strong= user.location diff --git a/app/views/users/_projects.html.haml b/app/views/users/_projects.html.haml index 1d38f8e8ab..297fa53739 100644 --- a/app/views/users/_projects.html.haml +++ b/app/views/users/_projects.html.haml @@ -1,6 +1,13 @@ -.panel.panel-default - .panel-heading Personal projects - %ul.well-list - - projects.each do |project| - %li - = link_to_project project +- if local_assigns.has_key?(:contributed_projects) && contributed_projects.present? + .panel.panel-default.contributed-projects + .panel-heading Projects contributed to + = render 'shared/projects_list', + projects: contributed_projects.sort_by(&:star_count).reverse, + projects_limit: 5, stars: true, avatar: false + +- if local_assigns.has_key?(:projects) && projects.present? + .panel.panel-default + .panel-heading Personal projects + = render 'shared/projects_list', + projects: projects.sort_by(&:star_count).reverse, + projects_limit: 10, stars: true, avatar: false diff --git a/app/views/users/calendar.html.haml b/app/views/users/calendar.html.haml new file mode 100644 index 0000000000..922b0c6ceb --- /dev/null +++ b/app/views/users/calendar.html.haml @@ -0,0 +1,12 @@ +%h4 + Contributions calendar + .pull-right + %small Issues, merge requests and push events +#cal-heatmap.calendar + :javascript + new Calendar( + #{@timestamps.to_json}, + #{@starting_year}, + #{@starting_month}, + '#{user_calendar_activities_path}' + ); diff --git a/app/views/users/calendar_activities.html.haml b/app/views/users/calendar_activities.html.haml new file mode 100644 index 0000000000..027a93a75f --- /dev/null +++ b/app/views/users/calendar_activities.html.haml @@ -0,0 +1,23 @@ +%h4.prepend-top-20 + %span.light 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 + - else + = event.project_name diff --git a/app/views/users/show.atom.builder b/app/views/users/show.atom.builder new file mode 100644 index 0000000000..8fe30b2363 --- /dev/null +++ b/app/views/users/show.atom.builder @@ -0,0 +1,12 @@ +xml.instruct! +xml.feed "xmlns" => "http://www.w3.org/2005/Atom", "xmlns:media" => "http://search.yahoo.com/mrss/" do + xml.title "Activity feed for #{@user.name}" + xml.link href: user_url(@user, :atom), rel: "self", type: "application/atom+xml" + xml.link href: user_url(@user), rel: "alternate", type: "text/html" + xml.id projects_url + xml.updated @events.maximum(:updated_at).strftime("%Y-%m-%dT%H:%M:%SZ") if @events.any? + + @events.each do |event| + event_to_atom(xml, event) + end +end diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml index 60159a29b9..9dd8cb0738 100644 --- a/app/views/users/show.html.haml +++ b/app/views/users/show.html.haml @@ -1,26 +1,50 @@ .row - .col-md-8 - %h3.page-title - = image_tag avatar_icon(@user.email, 90), class: "avatar s90", alt: '' - = @user.name - - if @user == current_user - .pull-right - = link_to profile_path, class: 'btn' do - %i.icon-edit - Edit Profile settings - %br - %span.user-show-username #{@user.username} - %br - %small member since #{@user.created_at.stamp("Nov 12, 2031")} + = link_to '#aside', class: 'show-aside' do + %i.fa.fa-angle-left + %section.col-md-8 + .header-with-avatar + = image_tag avatar_icon(@user.email, 90), class: "avatar avatar-tile s90", alt: '' + %h3 + = @user.name + - if @user == current_user + .pull-right + = link_to profile_path, class: 'btn btn-sm' do + %i.fa.fa-pencil-square-o + Edit Profile settings + .username + @#{@user.username} + .description + - if @user.bio.present? + = @user.bio + .clearfix - if @groups.any? - %h4 Groups: - = render 'groups', groups: @groups + .prepend-top-20 + %h4 Groups + = render 'groups', groups: @groups + %hr + + .hidden-xs + .user-calendar + %h4.center.light + %i.fa.fa-spinner.fa-spin + .user-calendar-activities %hr - %h4 User Activity: - = render @events - .col-md-4 + %h4 + User Activity + + - if current_user + %span.rss-icon.pull-right + = link_to user_path(@user, :atom, { private_token: current_user.private_token }) do + %strong + %i.fa.fa-rss + + .content_list + = spinner + %aside.col-md-4 = render 'profile', user: @user - - if @projects.present? - = render 'projects', projects: @projects + = render 'projects', projects: @projects, contributed_projects: @contributed_projects + +:coffeescript + $(".user-calendar").load("#{user_calendar_path}") diff --git a/app/views/users_groups/_users_group.html.haml b/app/views/users_groups/_users_group.html.haml deleted file mode 100644 index ad363eaba2..0000000000 --- a/app/views/users_groups/_users_group.html.haml +++ /dev/null @@ -1,31 +0,0 @@ -- user = member.user -- return unless user -- show_roles = true if show_roles.nil? -%li{class: "#{dom_class(member)} js-toggle-container", id: dom_id(member)} - %span{class: ("list-item-name" if show_controls)} - = image_tag avatar_icon(user.email, 16), class: "avatar s16" - %strong= user.name - %span.cgray= user.username - - if user == current_user - %span.label.label-success It's you - - - if show_roles - %span.pull-right - %strong= member.human_access - - if show_controls - - if can?(current_user, :modify, member) - = link_to '#', class: "btn-tiny btn js-toggle-button", title: 'Edit access level' do - %i.icon-edit - - if can?(current_user, :destroy, member) - - if current_user == member.user - = link_to leave_profile_group_path(@group), data: { confirm: leave_group_message(@group.name)}, method: :delete, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do - %i.icon-minus.icon-white - - else - = link_to group_users_group_path(@group, member), data: { confirm: remove_user_from_group_message(@group, user) }, method: :delete, remote: true, class: "btn-tiny btn btn-remove", title: 'Remove user from group' do - %i.icon-minus.icon-white - - .edit-member.hide.js-toggle-content - = form_for [@group, member], remote: true do |f| - .alert.prepend-top-20 - = f.select :group_access, options_for_select(UsersGroup.group_access_roles, member.group_access) - = f.submit 'Save', class: 'btn btn-save btn-small' diff --git a/app/views/votes/_votes_block.html.haml b/app/views/votes/_votes_block.html.haml index 788d9065a7..36ea674206 100644 --- a/app/views/votes/_votes_block.html.haml +++ b/app/views/votes/_votes_block.html.haml @@ -1,6 +1,10 @@ .votes.votes-block - .progress - .progress-bar.progress-bar-success{style: "width: #{votable.upvotes_in_percent}%;"} - .progress-bar.progress-bar-danger{style: "width: #{votable.downvotes_in_percent}%;"} - .upvotes= "#{votable.upvotes} up" - .downvotes= "#{votable.downvotes} down" + .btn-group + - unless votable.upvotes.zero? + .btn.btn-sm.disabled.cgreen + %i.fa.fa-thumbs-up + = votable.upvotes + - unless votable.downvotes.zero? + .btn.btn-sm.disabled.cred + %i.fa.fa-thumbs-down + = votable.downvotes diff --git a/app/views/votes/_votes_inline.html.haml b/app/views/votes/_votes_inline.html.haml index ee80547483..2cb3ae04e1 100644 --- a/app/views/votes/_votes_inline.html.haml +++ b/app/views/votes/_votes_inline.html.haml @@ -1,9 +1,9 @@ .votes.votes-inline - unless votable.upvotes.zero? - .upvotes + %span.upvotes.cgreen + #{votable.upvotes} - unless votable.downvotes.zero? \/ - unless votable.downvotes.zero? - .downvotes + %span.downvotes.cred \- #{votable.downvotes} diff --git a/app/workers/auto_merge_worker.rb b/app/workers/auto_merge_worker.rb new file mode 100644 index 0000000000..a6dd73eee5 --- /dev/null +++ b/app/workers/auto_merge_worker.rb @@ -0,0 +1,13 @@ +class AutoMergeWorker + include Sidekiq::Worker + + sidekiq_options queue: :default + + def perform(merge_request_id, current_user_id, params) + params = params.with_indifferent_access + current_user = User.find(current_user_id) + merge_request = MergeRequest.find(merge_request_id) + merge_request.should_remove_source_branch = params[:should_remove_source_branch] + merge_request.automerge!(current_user, params[:commit_message]) + end +end diff --git a/app/workers/emails_on_push_worker.rb b/app/workers/emails_on_push_worker.rb index 2947c8e3ec..1d21addece 100644 --- a/app/workers/emails_on_push_worker.rb +++ b/app/workers/emails_on_push_worker.rb @@ -1,25 +1,61 @@ class EmailsOnPushWorker include Sidekiq::Worker - def perform(project_id, recipients, push_data) + def perform(project_id, recipients, push_data, options = {}) + options.symbolize_keys! + options.reverse_merge!( + send_from_committer_email: false, + disable_diffs: false + ) + send_from_committer_email = options[:send_from_committer_email] + disable_diffs = options[:disable_diffs] + project = Project.find(project_id) before_sha = push_data["before"] after_sha = push_data["after"] - branch = push_data["ref"] + ref = push_data["ref"] author_id = push_data["user_id"] - if before_sha =~ /^000000/ || after_sha =~ /^000000/ - # skip if new branch was pushed or branch was removed - return true + action = + if Gitlab::Git.blank_ref?(before_sha) + :create + elsif Gitlab::Git.blank_ref?(after_sha) + :delete + else + :push + end + + compare = nil + reverse_compare = false + if action == :push + compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha) + + return false if compare.same + + if compare.commits.empty? + compare = Gitlab::Git::Compare.new(project.repository.raw_repository, after_sha, before_sha) + + reverse_compare = true + + return false if compare.commits.empty? + end end - compare = Gitlab::Git::Compare.new(project.repository.raw_repository, before_sha, after_sha) - - # Do not send emails if git compare failed - return false unless compare && compare.commits.present? - recipients.split(" ").each do |recipient| - Notify.repository_push_email(project_id, recipient, author_id, branch, compare).deliver + Notify.repository_push_email( + project_id, + 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 end + ensure + compare = nil + GC.start end end diff --git a/app/workers/fork_registration_worker.rb b/app/workers/fork_registration_worker.rb new file mode 100644 index 0000000000..fffa8b3a65 --- /dev/null +++ b/app/workers/fork_registration_worker.rb @@ -0,0 +1,12 @@ +class ForkRegistrationWorker + include Sidekiq::Worker + + sidekiq_options queue: :default + + def perform(from_project_id, to_project_id, private_token) + from_project = Project.find(from_project_id) + to_project = Project.find(to_project_id) + + from_project.gitlab_ci_service.fork_registration(to_project, private_token) + end +end diff --git a/app/workers/irker_worker.rb b/app/workers/irker_worker.rb new file mode 100644 index 0000000000..8b50f42398 --- /dev/null +++ b/app/workers/irker_worker.rb @@ -0,0 +1,169 @@ +require 'json' +require 'socket' + +class IrkerWorker + include Sidekiq::Worker + + def perform(project_id, chans, colors, push_data, settings) + project = Project.find(project_id) + + # Get config parameters + return false unless init_perform settings, chans, colors + + repo_name = push_data['repository']['name'] + committer = push_data['user_name'] + branch = push_data['ref'].gsub(%r'refs/[^/]*/', '') + + if @colors + repo_name = "\x0304#{repo_name}\x0f" + branch = "\x0305#{branch}\x0f" + end + + # Firsts messages are for branch creation/deletion + send_branch_updates push_data, project, repo_name, committer, branch + + # Next messages are for commits + send_commits push_data, project, repo_name, committer, branch + + close_connection + true + end + + private + + def init_perform(set, chans, colors) + @colors = colors + @channels = chans + start_connection set['server_ip'], set['server_port'] + end + + def start_connection(irker_server, irker_port) + begin + @socket = TCPSocket.new irker_server, irker_port + rescue Errno::ECONNREFUSED => e + logger.fatal "Can't connect to Irker daemon: #{e}" + return false + end + true + end + + def sendtoirker(privmsg) + to_send = { to: @channels, privmsg: privmsg } + @socket.puts JSON.dump(to_send) + end + + def close_connection + @socket.close + end + + def send_branch_updates(push_data, project, repo_name, committer, branch) + if Gitlab::Git.blank_ref?(push_data['before']) + send_new_branch project, repo_name, committer, branch + elsif Gitlab::Git.blank_ref?(push_data['after']) + send_del_branch repo_name, committer, branch + end + end + + def send_new_branch(project, repo_name, committer, branch) + repo_path = project.path_with_namespace + newbranch = "#{Gitlab.config.gitlab.url}/#{repo_path}/branches" + newbranch = "\x0302\x1f#{newbranch}\x0f" if @colors + + privmsg = "[#{repo_name}] #{committer} has created a new branch " + privmsg += "#{branch}: #{newbranch}" + sendtoirker privmsg + end + + def send_del_branch(repo_name, committer, branch) + privmsg = "[#{repo_name}] #{committer} has deleted the branch #{branch}" + sendtoirker privmsg + end + + def send_commits(push_data, project, repo_name, committer, branch) + return if push_data['total_commits_count'] == 0 + + # Next message is for number of commit pushed, if any + if Gitlab::Git.blank_ref?(push_data['before']) + # Tweak on push_data["before"] in order to have a nice compare URL + push_data['before'] = before_on_new_branch push_data, project + end + + send_commits_count(push_data, project, repo_name, committer, branch) + + # One message per commit, limited by 3 messages (same limit as the + # github irc hook) + commits = push_data['commits'].first(3) + commits.each do |hook_attrs| + send_one_commit project, hook_attrs, repo_name, branch + end + end + + def before_on_new_branch(push_data, project) + commit = commit_from_id project, push_data['commits'][0]['id'] + parents = commit.parents + # Return old value if there's no new one + return push_data['before'] if parents.empty? + # Or return the first parent-commit + parents[0].id + end + + def send_commits_count(data, project, repo, committer, branch) + url = compare_url data, project.path_with_namespace + commits = colorize_commits data['total_commits_count'] + + new_commits = 'new commit' + new_commits += 's' if data['total_commits_count'] > 1 + + sendtoirker "[#{repo}] #{committer} pushed #{commits} #{new_commits} " \ + "to #{branch}: #{url}" + end + + def compare_url(data, repo_path) + sha1 = Commit::truncate_sha(data['before']) + sha2 = Commit::truncate_sha(data['after']) + compare_url = "#{Gitlab.config.gitlab.url}/#{repo_path}/compare" + compare_url += "/#{sha1}...#{sha2}" + colorize_url compare_url + end + + def send_one_commit(project, hook_attrs, repo_name, branch) + commit = commit_from_id project, hook_attrs['id'] + sha = colorize_sha Commit::truncate_sha(hook_attrs['id']) + author = hook_attrs['author']['name'] + files = colorize_nb_files(files_count commit) + title = commit.title + + sendtoirker "#{repo_name}/#{branch} #{sha} #{author} (#{files}): #{title}" + end + + def commit_from_id(project, id) + commit = Gitlab::Git::Commit.find(project.repository, id) + Commit.new(commit) + end + + def files_count(commit) + files = "#{commit.diffs.count} file" + files += 's' if commit.diffs.count > 1 + files + end + + def colorize_sha(sha) + sha = "\x0314#{sha}\x0f" if @colors + sha + end + + def colorize_nb_files(nb_files) + nb_files = "\x0312#{nb_files}\x0f" if @colors + nb_files + end + + def colorize_url(url) + url = "\x0302\x1f#{url}\x0f" if @colors + url + end + + def colorize_commits(commits) + commits = "\x02#{commits}\x0f" if @colors + commits + end +end diff --git a/app/workers/post_receive.rb b/app/workers/post_receive.rb index f110e20bf0..33d8cc8861 100644 --- a/app/workers/post_receive.rb +++ b/app/workers/post_receive.rb @@ -4,16 +4,15 @@ class PostReceive sidekiq_options queue: :post_receive - def perform(repo_path, oldrev, newrev, ref, identifier) - + def perform(repo_path, identifier, changes) if repo_path.start_with?(Gitlab.config.gitlab_shell.repos_path.to_s) repo_path.gsub!(Gitlab.config.gitlab_shell.repos_path.to_s, "") else log("Check gitlab.yml config for correct gitlab_shell.repos_path variable. \"#{Gitlab.config.gitlab_shell.repos_path}\" does not match \"#{repo_path}\"") end - repo_path.gsub!(/\.git$/, "") - repo_path.gsub!(/^\//, "") + repo_path.gsub!(/\.git\z/, "") + repo_path.gsub!(/\A\//, "") project = Project.find_with_namespace(repo_path) @@ -22,27 +21,42 @@ class PostReceive return false end - user = identify(identifier, project, newrev) + changes = Base64.decode64(changes) unless changes.include?(" ") + changes = utf8_encode_changes(changes) + changes = changes.lines - unless user - log("Triggered hook for non-existing user \"#{identifier} \"") - return false - end + changes.each do |change| + oldrev, newrev, ref = change.strip.split(' ') - if tag?(ref) - GitTagPushService.new.execute(project, user, oldrev, newrev, ref) - else - GitPushService.new.execute(project, user, oldrev, newrev, ref) + @user ||= identify(identifier, project, newrev) + + unless @user + log("Triggered hook for non-existing user \"#{identifier} \"") + return false + end + + if Gitlab::Git.tag_ref?(ref) + GitTagPushService.new.execute(project, @user, oldrev, newrev, ref) + else + GitPushService.new.execute(project, @user, oldrev, newrev, ref) + end end end + def utf8_encode_changes(changes) + changes = changes.dup + + changes.force_encoding("UTF-8") + return changes if changes.valid_encoding? + + # Convert non-UTF-8 branch/tag names to UTF-8 so they can be dumped as JSON. + detection = CharlockHolmes::EncodingDetector.detect(changes) + return changes unless detection && detection[:encoding] + + CharlockHolmes::Converter.convert(changes, detection[:encoding], 'UTF-8') + end + def log(message) Gitlab::GitLogger.error("POST-RECEIVE: #{message}") end - - private - - def tag?(ref) - !!(/refs\/tags\/(.*)/.match(ref)) - end end diff --git a/app/workers/project_service_worker.rb b/app/workers/project_service_worker.rb new file mode 100644 index 0000000000..64d39c4d3f --- /dev/null +++ b/app/workers/project_service_worker.rb @@ -0,0 +1,10 @@ +class ProjectServiceWorker + include Sidekiq::Worker + + sidekiq_options queue: :project_web_hook + + def perform(hook_id, data) + data = data.with_indifferent_access + Service.find(hook_id).execute(data) + end +end diff --git a/app/workers/project_web_hook_worker.rb b/app/workers/project_web_hook_worker.rb index 9f9b9b1df5..73085c046b 100644 --- a/app/workers/project_web_hook_worker.rb +++ b/app/workers/project_web_hook_worker.rb @@ -4,6 +4,7 @@ class ProjectWebHookWorker sidekiq_options queue: :project_web_hook def perform(hook_id, data) - WebHook.find(hook_id).execute data + data = data.with_indifferent_access + WebHook.find(hook_id).execute(data) end end diff --git a/app/workers/repository_archive_worker.rb b/app/workers/repository_archive_worker.rb new file mode 100644 index 0000000000..021c113956 --- /dev/null +++ b/app/workers/repository_archive_worker.rb @@ -0,0 +1,43 @@ +class RepositoryArchiveWorker + include Sidekiq::Worker + + sidekiq_options queue: :archive_repo + + attr_accessor :project, :ref, :format + + def perform(project_id, ref, format) + @project = Project.find(project_id) + @ref, @format = ref, format.downcase + + repository = project.repository + + repository.clean_old_archives + + return unless file_path + return if archived? || archiving? + + repository.archive_repo(ref, storage_path, format) + end + + private + + def storage_path + Gitlab.config.gitlab.repository_downloads_path + end + + def file_path + @file_path ||= project.repository.archive_file_path(ref, storage_path, format) + end + + def pid_file_path + @pid_file_path ||= project.repository.archive_pid_file_path(ref, storage_path, format) + end + + def archived? + File.exist?(file_path) + end + + def archiving? + File.exist?(pid_file_path) + end +end diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb index 43ef54631a..e6a50afedb 100644 --- a/app/workers/repository_import_worker.rb +++ b/app/workers/repository_import_worker.rb @@ -6,17 +6,29 @@ class RepositoryImportWorker def perform(project_id) project = Project.find(project_id) - result = gitlab_shell.send(:import_repository, + + import_result = gitlab_shell.send(:import_repository, project.path_with_namespace, project.import_url) + return project.import_fail unless import_result - if result - project.import_finish - project.save - project.satellite.create unless project.satellite.exists? - project.update_repository_size - else - project.import_fail - end + data_import_result = if project.import_type == 'github' + Gitlab::GithubImport::Importer.new(project).execute + elsif project.import_type == 'gitlab' + Gitlab::GitlabImport::Importer.new(project).execute + elsif project.import_type == 'bitbucket' + Gitlab::BitbucketImport::Importer.new(project).execute + elsif project.import_type == 'google_code' + Gitlab::GoogleCodeImport::Importer.new(project).execute + else + true + end + return project.import_fail unless data_import_result + + project.import_finish + project.save + project.satellite.create unless project.satellite.exists? + project.update_repository_size + Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket' end -end \ No newline at end of file +end diff --git a/bin/background_jobs b/bin/background_jobs index c7ba4398cf..a041a4b043 100755 --- a/bin/background_jobs +++ b/bin/background_jobs @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/bin/sh cd $(dirname $0)/.. app_root=$(pwd) @@ -6,22 +6,22 @@ sidekiq_pidfile="$app_root/tmp/pids/sidekiq.pid" sidekiq_logfile="$app_root/log/sidekiq.log" gitlab_user=$(ls -l config.ru | awk '{print $3}') -function warn +warn() { echo "$@" 1>&2 } -function stop +stop() { bundle exec sidekiqctl stop $sidekiq_pidfile >> $sidekiq_logfile 2>&1 } -function killall +killall() { pkill -u $gitlab_user -f 'sidekiq [0-9]' } -function restart +restart() { if [ -f $sidekiq_pidfile ]; then stop @@ -30,20 +30,20 @@ function restart start_sidekiq -d -L $sidekiq_logfile } -function start_no_deamonize +start_no_deamonize() { start_sidekiq } -function start_sidekiq +start_sidekiq() { - bundle exec sidekiq -q post_receive -q mailer -q system_hook -q project_web_hook -q gitlab_shell -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1 + bundle exec sidekiq -q post_receive -q mailer -q archive_repo -q system_hook -q project_web_hook -q gitlab_shell -q common -q default -e $RAILS_ENV -P $sidekiq_pidfile $@ >> $sidekiq_logfile 2>&1 } -function load_ok +load_ok() { sidekiq_pid=$(cat $sidekiq_pidfile) - if [[ -z $sidekiq_pid ]] ; then + if [ -z "$sidekiq_pid" ] ; then warn "Could not find a PID in $sidekiq_pidfile" exit 0 fi diff --git a/bin/guard b/bin/guard new file mode 100755 index 0000000000..0c1a532bd0 --- /dev/null +++ b/bin/guard @@ -0,0 +1,16 @@ +#!/usr/bin/env ruby +# +# This file was generated by Bundler. +# +# The application 'guard' is installed as part of a gem, and +# this file is here to facilitate running it. +# + +require 'pathname' +ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile", + Pathname.new(__FILE__).realpath) + +require 'rubygems' +require 'bundler/setup' + +load Gem.bin_path('guard', 'guard') diff --git a/bin/pkgr_before_precompile.sh b/bin/pkgr_before_precompile.sh index 283abb6a0c..5a2007f4ab 100755 --- a/bin/pkgr_before_precompile.sh +++ b/bin/pkgr_before_precompile.sh @@ -18,6 +18,3 @@ rm config/resque.yml # Set default unicorn.rb file echo "" > config/unicorn.rb - -# Required for assets precompilation -sudo service postgresql start diff --git a/bin/rspec b/bin/rspec index 41e37089ac..20060ebd79 100755 --- a/bin/rspec +++ b/bin/rspec @@ -4,4 +4,4 @@ begin rescue LoadError end require 'bundler/setup' -load Gem.bin_path('rspec', 'rspec') +load Gem.bin_path('rspec-core', 'rspec') diff --git a/bin/web b/bin/web index 1ad3b5d24b..67f236eb0b 100755 --- a/bin/web +++ b/bin/web @@ -1,4 +1,4 @@ -#!/usr/bin/env bash +#!/bin/sh cd $(dirname $0)/.. app_root=$(pwd) @@ -6,28 +6,28 @@ app_root=$(pwd) unicorn_pidfile="$app_root/tmp/pids/unicorn.pid" unicorn_config="$app_root/config/unicorn.rb" -function get_unicorn_pid +get_unicorn_pid() { local pid=$(cat $unicorn_pidfile) - if [ -z $pid ] ; then + if [ -z "$pid" ] ; then echo "Could not find a PID in $unicorn_pidfile" exit 1 fi unicorn_pid=$pid } -function start +start() { bundle exec unicorn_rails -D -c $unicorn_config -E $RAILS_ENV } -function stop +stop() { get_unicorn_pid kill -QUIT $unicorn_pid } -function reload +reload() { get_unicorn_pid kill -USR2 $unicorn_pid diff --git a/config/application.rb b/config/application.rb index 58a5949c65..fa399533e5 100644 --- a/config/application.rb +++ b/config/application.rb @@ -2,7 +2,7 @@ require File.expand_path('../boot', __FILE__) require 'rails/all' require 'devise' - +I18n.config.enforce_available_locales = false Bundler.require(:default, Rails.env) module Gitlab @@ -12,16 +12,16 @@ module Gitlab # -- all .rb files in that directory are automatically loaded. # Custom directories with classes and modules you want to be autoloadable. - config.autoload_paths += %W(#{config.root}/lib #{config.root}/app/finders #{config.root}/app/models/concerns #{config.root}/app/models/project_services) + 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)) # 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. # config.plugins = [ :exception_notification, :ssl_requirement, :all ] - # Set Time.zone default to the specified zone and make Active Record auto-convert to this zone. - # Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC. - # config.time_zone = 'Central Time (US & Canada)' - # The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded. # config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s] # config.i18n.default_locale = :de @@ -31,7 +31,7 @@ module Gitlab config.encoding = "utf-8" # Configure sensitive parameters which will be filtered from the log file. - config.filter_parameters += [:password] + config.filter_parameters.push(:password, :password_confirmation, :private_token) # Enable escaping HTML in JSON. config.active_support.escape_html_entities_in_json = true @@ -50,6 +50,8 @@ module Gitlab # Version of your assets, change this if you want to expire all your assets config.assets.version = '1.0' + config.action_view.sanitized_allowed_protocols = %w(smb) + # Relative url support # Uncomment and customize the last line to run in a non-root path # WARNING: We recommend creating a FQDN to host GitLab in a root path instead of this. @@ -70,8 +72,33 @@ module Gitlab config.middleware.use Rack::Cors do allow do origins '*' - resource '/api/*', headers: :any, methods: [:get, :post, :options, :put, :delete] + resource '/api/*', + headers: :any, + methods: [:get, :post, :options, :put, :delete], + expose: ['Link'] end end + + # Use Redis caching across all environments + redis_config_file = Rails.root.join('config', 'resque.yml') + + redis_url_string = if File.exists?(redis_config_file) + YAML.load_file(redis_config_file)[Rails.env] + else + "redis://localhost:6379" + end + + # Redis::Store does not handle Unix sockets well, so let's do it for them + redis_config_hash = Redis::Store::Factory.extract_host_options_from_uri(redis_url_string) + redis_uri = URI.parse(redis_url_string) + if redis_uri.scheme == 'unix' + redis_config_hash[:path] = redis_uri.path + end + + redis_config_hash[:namespace] = 'cache:gitlab' + config.cache_store = :redis_store, redis_config_hash + + # This is needed for gitlab-shell + ENV['GITLAB_PATH_OUTSIDE_HOOK'] = ENV['PATH'] end end diff --git a/config/database.yml.mysql b/config/database.yml.mysql index 55ac088bc1..a99c50706c 100644 --- a/config/database.yml.mysql +++ b/config/database.yml.mysql @@ -4,6 +4,7 @@ production: adapter: mysql2 encoding: utf8 + collation: utf8_general_ci reconnect: false database: gitlabhq_production pool: 10 @@ -18,6 +19,7 @@ production: development: adapter: mysql2 encoding: utf8 + collation: utf8_general_ci reconnect: false database: gitlabhq_development pool: 5 @@ -31,6 +33,7 @@ development: test: &test adapter: mysql2 encoding: utf8 + collation: utf8_general_ci reconnect: false database: gitlabhq_test pool: 5 diff --git a/config/environments/production.rb b/config/environments/production.rb index 2450d5719e..3316ece387 100644 --- a/config/environments/production.rb +++ b/config/environments/production.rb @@ -11,8 +11,9 @@ Gitlab::Application.configure do # Disable Rails's static asset server (Apache or nginx will already do this) config.serve_static_assets = false - # Compress JavaScripts and CSS - config.assets.compress = true + # Compress JavaScripts and CSS. + config.assets.js_compressor = :uglifier + # config.assets.css_compressor = :sass # Don't fallback to assets pipeline if a precompiled asset is missed config.assets.compile = true @@ -45,16 +46,6 @@ Gitlab::Application.configure do # Use a different logger for distributed setups # config.logger = ActiveSupport::TaggedLogging.new(SyslogLogger.new) - # Use a different cache store in production - config_file = Rails.root.join('config', 'resque.yml') - - resque_url = if File.exists?(config_file) - YAML.load_file(config_file)[Rails.env] - else - "redis://localhost:6379" - end - config.cache_store = :redis_store, resque_url, {namespace: 'cache:gitlab'} - # Enable serving of images, stylesheets, and JavaScripts from an asset server # config.action_controller.asset_host = "http://assets.example.com" @@ -84,7 +75,6 @@ Gitlab::Application.configure do config.action_mailer.raise_delivery_errors = true config.eager_load = true - config.assets.js_compressor = :uglifier config.allow_concurrency = false end diff --git a/config/environments/test.rb b/config/environments/test.rb index 25b082b98d..2d5e7addcd 100644 --- a/config/environments/test.rb +++ b/config/environments/test.rb @@ -5,7 +5,7 @@ Gitlab::Application.configure do # test suite. You never need to work with it otherwise. Remember that # your test database is "scratch space" for the test suite and is wiped # and recreated between test runs. Don't rely on the data there! - config.cache_classes = true + config.cache_classes = false # Configure static asset server for tests with Cache-Control for performance config.serve_static_assets = true diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index d897eb4c02..ba40671b16 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -2,10 +2,17 @@ # GitLab application config file # # # # # # # # # # # # # # # # # # # # +########################### NOTE ##################################### +# This file should not receive new settings. All configuration options # +# are being moved to ApplicationSetting model! # +######################################################################## +# # How to use: -# 1. copy file as gitlab.yml -# 2. Replace gitlab -> host with your domain -# 3. Replace gitlab -> email_from +# 1. Copy file as gitlab.yml +# 2. Update gitlab -> host with your fully qualified domain name +# 3. Update gitlab -> email_from +# 4. If you installed Git from source, change git -> bin_path to /usr/local/bin/git +# 5. Review this configuration file for other settings you may want to adjust production: &base # @@ -16,8 +23,8 @@ production: &base gitlab: ## Web server settings (note: host is the FQDN, do not include http://) host: localhost - port: 80 - https: false + port: 80 # Set to 443 if using HTTPS, see installation.md#using-https for additional HTTPS configuration details + https: false # Set to true if using HTTPS, see installation.md#using-https for additional HTTPS configuration details # Uncommment this line below if your ssh host is different from HTTP/HTTPS one # (you'd obviously need to replace ssh.host_example.com with your own host). @@ -31,14 +38,21 @@ production: &base # Uncomment and customize if you can't use the default user to run GitLab (default: 'git') # user: git + ## Date & Time settings + # Uncomment and customize if you want to change the default time zone of GitLab application. + # To see all available zones, run `bundle exec rake time:zones:all RAILS_ENV=production` + # time_zone: 'UTC' + ## Email settings + # Uncomment and set to false if you need to disable email sending from GitLab (default: true) + # email_enabled: true # Email address used in the "From" field in mails sent by GitLab email_from: example@example.com + email_display_name: GitLab + email_reply_to: noreply@example.com - # Email server smtp settings are in [a separate file](initializers/smtp_settings.rb.sample). + # Email server smtp settings are in config/initializers/smtp_settings.rb.sample - ## User settings - default_projects_limit: 10 # default_can_create_group: false # default: true # username_changing_enabled: false # default: true - User can change her username/namespace ## Default theme @@ -49,26 +63,12 @@ production: &base ## COLOR = 5 # default_theme: 2 # default: 2 - ## Users can create accounts - # This also allows normal users to sign up for accounts themselves - # default: false - By default GitLab administrators must create all new accounts - # signup_enabled: true - - ## Standard login settings - # The standard login can be disabled to force login via LDAP - # default: true - If set to false the standard login form won't be shown on the sign-in page - # signin_enabled: false - - # Restrict setting visibility levels for non-admin users. - # The default is to allow all levels. - #restricted_visibility_levels: [ "public" ] - ## Automatic issue closing # If a commit message matches this regular expression, all issues referenced from the matched text will be closed. # This happens when the commit is pushed or merged into the default branch of a project. # When not specified the default issue_closing_pattern as specified below will be used. - # Tip: you can test your closing pattern at http://rubular.com - # issue_closing_pattern: '([Cc]lose[sd]|[Ff]ixe[sd]) #(\d+)' + # Tip: you can test your closing pattern at http://rubular.com. + # issue_closing_pattern: '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)' ## Default project features settings default_projects_features: @@ -78,41 +78,17 @@ production: &base snippets: false visibility_level: "private" # can be "private" | "internal" | "public" + ## Webhook settings + # Number of seconds to wait for HTTP response after sending webhook HTTP POST request (default: 10) + # webhook_timeout: 10 + ## Repository downloads directory # When a user clicks e.g. 'Download zip' on a project, a temporary zip file is created in the following directory. # The default is 'tmp/repositories' relative to the root of the Rails app. # repository_downloads_path: tmp/repositories - ## External issues trackers - issues_tracker: - # redmine: - # title: "Redmine" - # ## If not nil, link 'Issues' on project page will be replaced with this - # ## Use placeholders: - # ## :project_id - GitLab project identifier - # ## :issues_tracker_id - Project Name or Id in external issue tracker - # project_url: "http://redmine.sample/projects/:issues_tracker_id" - # - # ## If not nil, links from /#\d/ entities from commit messages will replaced with this - # ## Use placeholders: - # ## :project_id - GitLab project identifier - # ## :issues_tracker_id - Project Name or Id in external issue tracker - # ## :id - Issue id (from commit messages) - # issues_url: "http://redmine.sample/issues/:id" - # - # ## If not nil, links to creating new issues will be replaced with this - # ## Use placeholders: - # ## :project_id - GitLab project identifier - # ## :issues_tracker_id - Project Name or Id in external issue tracker - # new_issue_url: "http://redmine.sample/projects/:issues_tracker_id/issues/new" - # - # jira: - # title: "Atlassian Jira" - # project_url: "http://jira.sample/issues/?jql=project=:issues_tracker_id" - # issues_url: "http://jira.sample/browse/:id" - # new_issue_url: "http://jira.sample/secure/CreateIssue.jspa" - ## Gravatar + ## For Libravatar see: http://doc.gitlab.com/ce/customization/libravatar.html gravatar: enabled: true # Use user avatar image from Gravatar.com (default: true) # gravatar urls: possible placeholders: %{hash} %{size} %{email} @@ -128,35 +104,75 @@ production: &base # bundle exec rake gitlab:ldap:check RAILS_ENV=production ldap: enabled: false - host: '_your_ldap_server' - port: 636 - uid: 'sAMAccountName' - method: 'ssl' # "tls" or "ssl" or "plain" - bind_dn: '_the_full_dn_of_the_user_you_will_bind_with' - password: '_the_password_of_the_bind_user' - # If allow_username_or_email_login is enabled, GitLab will ignore everything - # after the first '@' in the LDAP username submitted by the user on login. - # - # Example: - # - the user enters 'jane.doe@example.com' and 'p@ssw0rd' as LDAP credentials; - # - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'. - # - # If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to - # disable this setting, because the userPrincipalName contains an '@'. - allow_username_or_email_login: true + servers: + ########################################################################## + # + # Since GitLab 7.4, LDAP servers get ID's (below the ID is 'main'). GitLab + # Enterprise Edition now supports connecting to multiple LDAP servers. + # + # If you are updating from the old (pre-7.4) syntax, you MUST give your + # old server the ID 'main'. + # + ########################################################################## + main: # 'main' is the GitLab 'provider ID' of this LDAP server + ## label + # + # A human-friendly name for your LDAP server. It is OK to change the label later, + # for instance if you find out it is too large to fit on the web page. + # + # Example: 'Paris' or 'Acme, Ltd.' + label: 'LDAP' - # Base where we can search for users - # - # Ex. ou=People,dc=gitlab,dc=example - # - base: '' + host: '_your_ldap_server' + port: 389 + uid: 'sAMAccountName' + method: 'plain' # "tls" or "ssl" or "plain" + bind_dn: '_the_full_dn_of_the_user_you_will_bind_with' + password: '_the_password_of_the_bind_user' - # Filter LDAP users - # - # Format: RFC 4515 - # Ex. (employeeType=developer) - # - user_filter: '' + # This setting specifies if LDAP server is Active Directory LDAP server. + # For non AD servers it skips the AD specific queries. + # If your LDAP server is not AD, set this to false. + active_directory: true + + # If allow_username_or_email_login is enabled, GitLab will ignore everything + # after the first '@' in the LDAP username submitted by the user on login. + # + # Example: + # - the user enters 'jane.doe@example.com' and 'p@ssw0rd' as LDAP credentials; + # - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'. + # + # If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to + # disable this setting, because the userPrincipalName contains an '@'. + allow_username_or_email_login: false + + # To maintain tight control over the number of active users on your GitLab installation, + # enable this setting to keep new users blocked until they have been cleared by the admin + # (default: false). + block_auto_created_users: false + + # Base where we can search for users + # + # Ex. ou=People,dc=gitlab,dc=example + # + base: '' + + # Filter LDAP users + # + # Format: RFC 4515 http://tools.ietf.org/search/rfc4515 + # Ex. (employeeType=developer) + # + # Note: GitLab does not support omniauth-ldap's custom filter syntax. + # + user_filter: '' + + # GitLab EE only: add more LDAP servers + # Choose an ID made of a-z and 0-9 . This ID will be stored in the database + # so that GitLab can remember which LDAP server a user belongs to. + # uswest2: + # label: + # host: + # .... ## OmniAuth settings @@ -179,14 +195,19 @@ production: &base # arguments, followed by optional 'args' which can be either a hash or an array. # Documentation for this is available at http://doc.gitlab.com/ce/integration/omniauth.html providers: - # - { name: 'google_oauth2', app_id: 'YOUR APP ID', - # app_secret: 'YOUR APP SECRET', + # - { name: 'google_oauth2', app_id: 'YOUR_APP_ID', + # app_secret: 'YOUR_APP_SECRET', # args: { access_type: 'offline', approval_prompt: '' } } - # - { name: 'twitter', app_id: 'YOUR APP ID', - # app_secret: 'YOUR APP SECRET'} - # - { name: 'github', app_id: 'YOUR APP ID', - # app_secret: 'YOUR APP SECRET', + # - { name: 'twitter', app_id: 'YOUR_APP_ID', + # app_secret: 'YOUR_APP_SECRET'} + # - { name: 'github', app_id: 'YOUR_APP_ID', + # app_secret: 'YOUR_APP_SECRET', # args: { scope: 'user:email' } } + # - { name: 'gitlab', app_id: 'YOUR_APP_ID', + # app_secret: 'YOUR_APP_SECRET', + # args: { scope: 'api' } } + # - { name: 'bitbucket', app_id: 'YOUR_APP_ID', + # app_secret: 'YOUR_APP_SECRET'} @@ -204,6 +225,15 @@ production: &base backup: path: "tmp/backups" # Relative paths are relative to Rails.root (default: tmp/backups/) # keep_time: 604800 # default: 0 (forever) (in seconds) + # upload: + # # Fog storage connection settings, see http://fog.io/storage/ . + # connection: + # provider: AWS + # region: eu-west-1 + # aws_access_key_id: AKIAKIAKI + # aws_secret_access_key: 'secret123' + # # The remote 'directory' to store your backups. For S3, this would be the bucket name. + # remote_directory: 'my.s3.bucket' ## GitLab Shell settings gitlab_shell: @@ -244,10 +274,22 @@ production: &base # piwik_url: '_your_piwik_url' # piwik_site_id: '_your_piwik_site_id' - ## Text under sign-in page (Markdown enabled) - # sign_in_text: | - # ![Company Logo](http://www.companydomain.com/logo.png) - # [Learn more about CompanyName](http://www.companydomain.com/) + rack_attack: + git_basic_auth: + # Rack Attack IP banning enabled + # enabled: true + # + # Whitelist requests from 127.0.0.1 for web proxies (NGINX/Apache) with incorrect headers + # ip_whitelist: ["127.0.0.1"] + # + # Limit the number of Git HTTP authentication attempts per IP + # maxretry: 10 + # + # Reset the auth attempt counter per IP after 60 seconds + # findtime: 60 + # + # Ban an IP for one hour (3600s) after too many auth attempts + # bantime: 3600 development: <<: *base @@ -261,9 +303,9 @@ test: port: 80 # When you run tests we clone and setup gitlab-shell - # In order to setup it correctly you need to specify + # In order to setup it correctly you need to specify # your system username you use to run GitLab - # user: YOUR_USERNAME + # user: YOUR_USERNAME satellites: path: tmp/tests/gitlab-satellites/ gitlab_shell: @@ -276,6 +318,20 @@ test: project_url: "http://redmine/projects/:issues_tracker_id" issues_url: "http://redmine/:project_id/:issues_tracker_id/:id" new_issue_url: "http://redmine/projects/:issues_tracker_id/issues/new" + ldap: + enabled: false + servers: + main: + label: ldap + host: 127.0.0.1 + port: 3890 + uid: 'uid' + method: 'plain' # "tls" or "ssl" or "plain" + base: 'dc=example,dc=com' + user_filter: '' + group_base: 'ou=groups,dc=example,dc=com' + admin_group: '' + sync_ssh_keys: false staging: <<: *base diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 136622c65a..0abd34fc3e 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -1,3 +1,5 @@ +require 'gitlab' # Load lib/gitlab.rb as soon as possible + class Settings < Settingslogic source ENV.fetch('GITLAB_CONFIG') { "#{Rails.root}/config/gitlab.yml" } namespace Rails.env @@ -13,7 +15,11 @@ class Settings < Settingslogic if gitlab_shell.ssh_port != 22 "ssh://#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}:#{gitlab_shell.ssh_port}/" else - "#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}:" + if gitlab_shell.ssh_host.include? ':' + "[#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}]:" + else + "#{gitlab_shell.ssh_user}@#{gitlab_shell.ssh_host}:" + end end end @@ -56,7 +62,27 @@ end # Default settings Settings['ldap'] ||= Settingslogic.new({}) Settings.ldap['enabled'] = false if Settings.ldap['enabled'].nil? -Settings.ldap['allow_username_or_email_login'] = false if Settings.ldap['allow_username_or_email_login'].nil? + +# backwards compatibility, we only have one host +if Settings.ldap['enabled'] || Rails.env.test? + if Settings.ldap['host'].present? + # We detected old LDAP configuration syntax. Update the config to make it + # look like it was entered with the new syntax. + server = Settings.ldap.except('sync_time') + Settings.ldap['servers'] = { + 'main' => server + } + end + + Settings.ldap['servers'].each do |key, server| + server['label'] ||= 'LDAP' + server['block_auto_created_users'] = false if server['block_auto_created_users'].nil? + server['allow_username_or_email_login'] = false if server['allow_username_or_email_login'].nil? + server['active_directory'] = true if server['active_directory'].nil? + server['provider_name'] ||= "ldap#{key}".downcase + server['provider_class'] = OmniAuth::Utils.camelize(server['provider_name']) + end +end Settings['omniauth'] ||= Settingslogic.new({}) @@ -70,6 +96,7 @@ Settings['issues_tracker'] ||= {} # Settings['gitlab'] ||= Settingslogic.new({}) Settings.gitlab['default_projects_limit'] ||= 10 +Settings.gitlab['default_branch_protection'] ||= 2 Settings.gitlab['default_can_create_group'] = true if Settings.gitlab['default_can_create_group'].nil? Settings.gitlab['default_theme'] = Gitlab::Theme::MARS if Settings.gitlab['default_theme'].nil? Settings.gitlab['host'] ||= 'localhost' @@ -78,7 +105,10 @@ 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['email_enabled'] ||= true if Settings.gitlab['email_enabled'].nil? Settings.gitlab['email_from'] ||= "gitlab@#{Settings.gitlab.host}" +Settings.gitlab['email_display_name'] ||= "GitLab" +Settings.gitlab['email_reply_to'] ||= "noreply@#{Settings.gitlab.host}" Settings.gitlab['url'] ||= Settings.send(:build_gitlab_url) Settings.gitlab['user'] ||= 'git' Settings.gitlab['user_home'] ||= begin @@ -86,12 +116,16 @@ Settings.gitlab['user_home'] ||= begin rescue ArgumentError # no user configured '/home/' + Settings.gitlab['user'] end -Settings.gitlab['signup_enabled'] ||= false +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['twitter_sharing_enabled'] ||= true if Settings.gitlab['twitter_sharing_enabled'].nil? Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], []) Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil? -Settings.gitlab['issue_closing_pattern'] = '([Cc]lose[sd]|[Ff]ixe[sd]) #(\d+)' if Settings.gitlab['issue_closing_pattern'].nil? +Settings.gitlab['issue_closing_pattern'] = '((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?|[Rr]esolv(?:e[sd]?|ing)) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)' if Settings.gitlab['issue_closing_pattern'].nil? Settings.gitlab['default_projects_features'] ||= {} +Settings.gitlab['webhook_timeout'] ||= 10 +Settings.gitlab['max_attachment_size'] ||= 10 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? @@ -128,6 +162,11 @@ Settings.gitlab_shell['ssh_path_prefix'] ||= Settings.send(:build_gitlab_shell_s Settings['backup'] ||= Settingslogic.new({}) Settings.backup['keep_time'] ||= 0 Settings.backup['path'] = File.expand_path(Settings.backup['path'] || "tmp/backups/", Rails.root) +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'] + Settings.backup['upload']['connection'] = Hash[Settings.backup['upload']['connection'].map { |k, v| [k.to_sym, v] }] +end # # Git @@ -146,6 +185,17 @@ Settings.satellites['timeout'] ||= 30 # Settings['extra'] ||= Settingslogic.new({}) +# +# Rack::Attack settings +# +Settings['rack_attack'] ||= Settingslogic.new({}) +Settings.rack_attack['git_basic_auth'] ||= Settingslogic.new({}) +Settings.rack_attack.git_basic_auth['enabled'] = true if Settings.rack_attack.git_basic_auth['enabled'].nil? +Settings.rack_attack.git_basic_auth['ip_whitelist'] ||= %w{127.0.0.1} +Settings.rack_attack.git_basic_auth['maxretry'] ||= 10 +Settings.rack_attack.git_basic_auth['findtime'] ||= 1.minute +Settings.rack_attack.git_basic_auth['bantime'] ||= 1.hour + # # Testing settings # diff --git a/config/initializers/2_app.rb b/config/initializers/2_app.rb index 655590dff0..688cdf5f4b 100644 --- a/config/initializers/2_app.rb +++ b/config/initializers/2_app.rb @@ -6,8 +6,3 @@ module Gitlab Settings end end - -# -# Load all libs for threadsafety -# -Dir["#{Rails.root}/lib/**/*.rb"].each { |file| require file } diff --git a/config/initializers/4_sidekiq.rb b/config/initializers/4_sidekiq.rb index 228b14cb52..e856499732 100644 --- a/config/initializers/4_sidekiq.rb +++ b/config/initializers/4_sidekiq.rb @@ -14,7 +14,8 @@ Sidekiq.configure_server do |config| } config.server_middleware do |chain| - chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger + chain.add Gitlab::SidekiqMiddleware::ArgumentsLogger if ENV['SIDEKIQ_LOG_ARGUMENTS'] + chain.add Gitlab::SidekiqMiddleware::MemoryKiller if ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS'] end end diff --git a/config/initializers/5_backend.rb b/config/initializers/5_backend.rb index 7c2e7f3900..80d641d73a 100644 --- a/config/initializers/5_backend.rb +++ b/config/initializers/5_backend.rb @@ -6,3 +6,10 @@ require Rails.root.join("lib", "gitlab", "backend", "shell") # GitLab shell adapter require 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) + +unless current_version.valid? && required_version <= current_version + warn "WARNING: This version of GitLab depends on gitlab-shell #{required_version}, but you're running #{current_version}. Please update gitlab-shell." +end diff --git a/config/initializers/6_rack_profiler.rb b/config/initializers/6_rack_profiler.rb index a7ee3c5982..b634028756 100644 --- a/config/initializers/6_rack_profiler.rb +++ b/config/initializers/6_rack_profiler.rb @@ -3,4 +3,6 @@ if Rails.env == 'development' # initialization is skipped so trigger it Rack::MiniProfilerRails.initialize!(Rails.application) + Rack::MiniProfiler.config.position = 'right' + Rack::MiniProfiler.config.start_hidden = true end diff --git a/config/initializers/7_omniauth.rb b/config/initializers/7_omniauth.rb new file mode 100644 index 0000000000..8f6c567310 --- /dev/null +++ b/config/initializers/7_omniauth.rb @@ -0,0 +1,12 @@ +if Gitlab::LDAP::Config.enabled? + module OmniAuth::Strategies + server = Gitlab.config.ldap.servers.values.first + klass = server['provider_class'] + const_set(klass, Class.new(LDAP)) unless klass == 'LDAP' + end + + OmniauthCallbacksController.class_eval do + server = Gitlab.config.ldap.servers.values.first + alias_method server['provider_name'], :ldap + end +end diff --git a/config/initializers/acts_as_taggable_on_patch.rb b/config/initializers/acts_as_taggable_on_patch.rb deleted file mode 100644 index baa77fde39..0000000000 --- a/config/initializers/acts_as_taggable_on_patch.rb +++ /dev/null @@ -1,130 +0,0 @@ -# This is a patch to address the issue in https://github.com/mbleigh/acts-as-taggable-on/issues/427 caused by -# https://github.com/rails/rails/commit/31a43ebc107fbd50e7e62567e5208a05909ec76c -# gem 'acts-as-taggable-on' has the fix included https://github.com/mbleigh/acts-as-taggable-on/commit/89bbed3864a9252276fb8dd7d535fce280454b90 -# but not in the currently used version of gem ('2.4.1') -# With replacement of 'acts-as-taggable-on' gem this file will become obsolete - -module ActsAsTaggableOn::Taggable - module Core - module ClassMethods - def tagged_with(tags, options = {}) - tag_list = ActsAsTaggableOn::TagList.from(tags) - empty_result = where("1 = 0") - - return empty_result if tag_list.empty? - - joins = [] - conditions = [] - having = [] - select_clause = [] - - context = options.delete(:on) - owned_by = options.delete(:owned_by) - alias_base_name = undecorated_table_name.gsub('.','_') - quote = ActsAsTaggableOn::Tag.using_postgresql? ? '"' : '' - - if options.delete(:exclude) - if options.delete(:wild) - tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name #{like_operator} ? ESCAPE '!'", "%#{escape_like(t)}%"]) }.join(" OR ") - else - tags_conditions = tag_list.map { |t| sanitize_sql(["#{ActsAsTaggableOn::Tag.table_name}.name #{like_operator} ?", t]) }.join(" OR ") - end - - conditions << "#{table_name}.#{primary_key} NOT IN (SELECT #{ActsAsTaggableOn::Tagging.table_name}.taggable_id FROM #{ActsAsTaggableOn::Tagging.table_name} JOIN #{ActsAsTaggableOn::Tag.table_name} ON #{ActsAsTaggableOn::Tagging.table_name}.tag_id = #{ActsAsTaggableOn::Tag.table_name}.#{ActsAsTaggableOn::Tag.primary_key} AND (#{tags_conditions}) WHERE #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name, nil)})" - - if owned_by - joins << "JOIN #{ActsAsTaggableOn::Tagging.table_name}" + - " ON #{ActsAsTaggableOn::Tagging.table_name}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" + - " AND #{ActsAsTaggableOn::Tagging.table_name}.taggable_type = #{quote_value(base_class.name, nil)}" + - " AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_id = #{owned_by.id}" + - " AND #{ActsAsTaggableOn::Tagging.table_name}.tagger_type = #{quote_value(owned_by.class.base_class.to_s, nil)}" - end - - elsif options.delete(:any) - # get tags, drop out if nothing returned (we need at least one) - tags = if options.delete(:wild) - ActsAsTaggableOn::Tag.named_like_any(tag_list) - else - ActsAsTaggableOn::Tag.named_any(tag_list) - end - - return empty_result unless tags.length > 0 - - # setup taggings alias so we can chain, ex: items_locations_taggings_awesome_cool_123 - # avoid ambiguous column name - taggings_context = context ? "_#{context}" : '' - - taggings_alias = adjust_taggings_alias( - "#{alias_base_name[0..4]}#{taggings_context[0..6]}_taggings_#{sha_prefix(tags.map(&:name).join('_'))}" - ) - - tagging_join = "JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" + - " ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" + - " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}" - tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context - - # don't need to sanitize sql, map all ids and join with OR logic - conditions << tags.map { |t| "#{taggings_alias}.tag_id = #{t.id}" }.join(" OR ") - select_clause = "DISTINCT #{table_name}.*" unless context and tag_types.one? - - if owned_by - tagging_join << " AND " + - sanitize_sql([ - "#{taggings_alias}.tagger_id = ? AND #{taggings_alias}.tagger_type = ?", - owned_by.id, - owned_by.class.base_class.to_s - ]) - end - - joins << tagging_join - else - tags = ActsAsTaggableOn::Tag.named_any(tag_list) - - return empty_result unless tags.length == tag_list.length - - tags.each do |tag| - taggings_alias = adjust_taggings_alias("#{alias_base_name[0..11]}_taggings_#{sha_prefix(tag.name)}") - tagging_join = "JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" + - " ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" + - " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}" + - " AND #{taggings_alias}.tag_id = #{tag.id}" - - tagging_join << " AND " + sanitize_sql(["#{taggings_alias}.context = ?", context.to_s]) if context - - if owned_by - tagging_join << " AND " + - sanitize_sql([ - "#{taggings_alias}.tagger_id = ? AND #{taggings_alias}.tagger_type = ?", - owned_by.id, - owned_by.class.base_class.to_s - ]) - end - - joins << tagging_join - end - end - - taggings_alias, tags_alias = adjust_taggings_alias("#{alias_base_name}_taggings_group"), "#{alias_base_name}_tags_group" - - if options.delete(:match_all) - joins << "LEFT OUTER JOIN #{ActsAsTaggableOn::Tagging.table_name} #{taggings_alias}" + - " ON #{taggings_alias}.taggable_id = #{quote}#{table_name}#{quote}.#{primary_key}" + - " AND #{taggings_alias}.taggable_type = #{quote_value(base_class.name, nil)}" - - - group_columns = ActsAsTaggableOn::Tag.using_postgresql? ? grouped_column_names_for(self) : "#{table_name}.#{primary_key}" - group = group_columns - having = "COUNT(#{taggings_alias}.taggable_id) = #{tags.size}" - end - - select(select_clause) \ - .joins(joins.join(" ")) \ - .where(conditions.join(" AND ")) \ - .group(group) \ - .having(having) \ - .order(options[:order]) \ - .readonly(false) - end - end - end -end diff --git a/config/initializers/carrierwave.rb b/config/initializers/carrierwave.rb index d0065b63e5..bfb8656df5 100644 --- a/config/initializers/carrierwave.rb +++ b/config/initializers/carrierwave.rb @@ -12,22 +12,30 @@ if File.exists?(aws_file) aws_secret_access_key: AWS_CONFIG['secret_access_key'], # required region: AWS_CONFIG['region'], # optional, defaults to 'us-east-1' } - config.fog_directory = AWS_CONFIG['bucket'] # required - config.fog_public = false # optional, defaults to true - config.fog_attributes = {'Cache-Control'=>'max-age=315576000'} # optional, defaults to {} - config.fog_authenticated_url_expiration = 1 << 29 # optional time (in seconds) that authenticated urls will be valid. - # when fog_public is false and provider is AWS or Google, defaults to 600 + + # required + config.fog_directory = AWS_CONFIG['bucket'] + + # optional, defaults to true + config.fog_public = false + + # optional, defaults to {} + 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 + config.fog_authenticated_url_expiration = 1 << 29 end # Mocking Fog requests, based on: https://github.com/carrierwaveuploader/carrierwave/wiki/How-to%3A-Test-Fog-based-uploaders if Rails.env.test? Fog.mock! connection = ::Fog::Storage.new( - :aws_access_key_id => AWS_CONFIG['access_key_id'], - :aws_secret_access_key => AWS_CONFIG['secret_access_key'], - :provider => 'AWS', - :region => AWS_CONFIG['region'] + aws_access_key_id: AWS_CONFIG['access_key_id'], + aws_secret_access_key: AWS_CONFIG['secret_access_key'], + provider: 'AWS', + region: AWS_CONFIG['region'] ) - connection.directories.create(:key => AWS_CONFIG['bucket']) + connection.directories.create(key: AWS_CONFIG['bucket']) end end diff --git a/config/initializers/devise.rb b/config/initializers/devise.rb index 34f4f38698..9dce495106 100644 --- a/config/initializers/devise.rb +++ b/config/initializers/devise.rb @@ -145,7 +145,8 @@ Devise.setup do |config| # Time interval you can reset your password with a reset password key. # Don't put a too small interval or your users won't have the time to # change their passwords. - config.reset_password_within = 2.hours + # When someone else invites you to GitLab this time is also used so it should be pretty long. + config.reset_password_within = 2.days # ==> Configuration for :encryptable # Allow you to use another encryption algorithm besides bcrypt (default). You can use @@ -204,22 +205,24 @@ Devise.setup do |config| # manager.default_strategies(scope: :user).unshift :some_external_strategy # end - if Gitlab.config.ldap.enabled - if Gitlab.config.ldap.allow_username_or_email_login - email_stripping_proc = ->(name) {name.gsub(/@.*$/,'')} - else - email_stripping_proc = ->(name) {name} - end + if Gitlab::LDAP::Config.enabled? + Gitlab.config.ldap.servers.values.each do |server| + if server['allow_username_or_email_login'] + email_stripping_proc = ->(name) {name.gsub(/@.*\z/,'')} + else + email_stripping_proc = ->(name) {name} + end - config.omniauth :ldap, - host: Gitlab.config.ldap['host'], - base: Gitlab.config.ldap['base'], - uid: Gitlab.config.ldap['uid'], - port: Gitlab.config.ldap['port'], - method: Gitlab.config.ldap['method'], - bind_dn: Gitlab.config.ldap['bind_dn'], - password: Gitlab.config.ldap['password'], - name_proc: email_stripping_proc + config.omniauth server['provider_name'], + host: server['host'], + base: server['base'], + uid: server['uid'], + port: server['port'], + method: server['method'], + bind_dn: server['bind_dn'], + password: server['password'], + name_proc: email_stripping_proc + end end Gitlab.config.omniauth.providers.each do |provider| diff --git a/config/initializers/disable_email_interceptor.rb b/config/initializers/disable_email_interceptor.rb new file mode 100644 index 0000000000..c76a6b8b19 --- /dev/null +++ b/config/initializers/disable_email_interceptor.rb @@ -0,0 +1,2 @@ +# Interceptor in lib/disable_email_interceptor.rb +ActionMailer::Base.register_interceptor(DisableEmailInterceptor) unless Gitlab.config.gitlab.email_enabled diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb new file mode 100644 index 0000000000..d422acb31d --- /dev/null +++ b/config/initializers/doorkeeper.rb @@ -0,0 +1,102 @@ +Doorkeeper.configure do + # Change the ORM that doorkeeper will use. + # Currently supported options are :active_record, :mongoid2, :mongoid3, :mongo_mapper + orm :active_record + + # This block will be called to check whether the resource owner is authenticated or not. + resource_owner_authenticator do + # Put your resource owner authentication logic here. + # Example implementation: + current_user || redirect_to(new_user_session_url) + end + + resource_owner_from_credentials do |routes| + u = User.find_by(email: params[:username]) || User.find_by(username: params[:username]) + u if u && u.valid_password?(params[:password]) + end + + # If you want to restrict access to the web interface for adding oauth authorized applications, you need to declare the block below. + # admin_authenticator do + # # Put your admin authentication logic here. + # # Example implementation: + # Admin.find_by_id(session[:admin_id]) || redirect_to(new_admin_session_url) + # end + + # Authorization Code expiration time (default 10 minutes). + # authorization_code_expires_in 10.minutes + + # Access token expiration time (default 2 hours). + # If you want to disable expiration, set this to nil. + access_token_expires_in nil + + # Reuse access token for the same resource owner within an application (disabled by default) + # Rationale: https://github.com/doorkeeper-gem/doorkeeper/issues/383 + # reuse_access_token + + # Issue access tokens with refresh token (disabled by default) + use_refresh_token + + # Forces the usage of the HTTPS protocol in non-native redirect uris (enabled + # by default in non-development environments). OAuth2 delegates security in + # communication to the HTTPS protocol so it is wise to keep this enabled. + # + force_ssl_in_redirect_uri false + + # Provide support for an owner to be assigned to each registered application (disabled by default) + # Optional parameter confirmation: true (default false) if you want to enforce ownership of + # a registered application + # Note: you must also run the rails g doorkeeper:application_owner generator to provide the necessary support + enable_application_owner confirmation: false + + # Define access token scopes for your provider + # For more information go to + # https://github.com/doorkeeper-gem/doorkeeper/wiki/Using-Scopes + default_scopes :api + #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 + # falls back to the `:client_id` and `:client_secret` params from the `params` object. + # Check out the wiki for more information on customization + # client_credentials :from_basic, :from_params + + # Change the way access token is authenticated from the request object. + # By default it retrieves first from the `HTTP_AUTHORIZATION` header, then + # falls back to the `:access_token` or `:bearer_token` params from the `params` object. + # Check out the wiki for more information on customization + access_token_methods :from_access_token_param, :from_bearer_authorization, :from_bearer_param + + # Change the native redirect uri for client apps + # When clients register with the following redirect uri, they won't be redirected to any server and the authorization code will be displayed within the provider + # 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' + + # Specify what grant flows are enabled in array of Strings. The valid + # strings and the flows they enable are: + # + # "authorization_code" => Authorization Code Grant Flow + # "implicit" => Implicit Grant Flow + # "password" => Resource Owner Password Credentials Grant Flow + # "client_credentials" => Client Credentials Grant Flow + # + # If not specified, Doorkeeper enables all the four grant flows. + # + grant_flows %w(authorization_code password client_credentials) + + # Under some circumstances you might want to have applications auto-approved, + # so that the user skips the authorization step. + # For example if dealing with trusted a application. + # skip_authorization do |resource_owner, client| + # client.superapp? or resource_owner.admin? + # end + + # WWW-Authenticate Realm (default "Doorkeeper"). + # realm "Doorkeeper" + + # Allow dynamic query parameters (disabled by default) + # Some applications require dynamic query parameters on their request_uri + # set to true if you want this to be allowed + # wildcard_redirect_uri false +end diff --git a/config/initializers/gitlab_shell_secret_token.rb b/config/initializers/gitlab_shell_secret_token.rb new file mode 100644 index 0000000000..e7c9f0ba7c --- /dev/null +++ b/config/initializers/gitlab_shell_secret_token.rb @@ -0,0 +1,19 @@ +# Be sure to restart your server when you modify this file. + +require 'securerandom' + +# Your secret key for verifying the gitlab_shell. + + +secret_file = Rails.root.join('.gitlab_shell_secret') +gitlab_shell_symlink = File.join(Gitlab.config.gitlab_shell.path, '.gitlab_shell_secret') + +unless File.exist? secret_file + # Generate a new token of 16 random hexadecimal characters and store it in secret_file. + token = SecureRandom.hex(16) + File.write(secret_file, token) +end + +if File.exist?(Gitlab.config.gitlab_shell.path) && !File.exist?(gitlab_shell_symlink) + FileUtils.symlink(secret_file, gitlab_shell_symlink) +end diff --git a/config/initializers/mime_types.rb b/config/initializers/mime_types.rb index 8f8bef42be..ca58ae92d1 100644 --- a/config/initializers/mime_types.rb +++ b/config/initializers/mime_types.rb @@ -6,3 +6,5 @@ Mime::Type.register_alias "text/plain", :diff Mime::Type.register_alias "text/plain", :patch +Mime::Type.register_alias 'text/html', :markdown +Mime::Type.register_alias 'text/html', :md diff --git a/config/initializers/public_key.rb b/config/initializers/public_key.rb new file mode 100644 index 0000000000..e4f09a2d02 --- /dev/null +++ b/config/initializers/public_key.rb @@ -0,0 +1,2 @@ +path = File.expand_path("~/.ssh/bitbucket_rsa.pub") +Gitlab::BitbucketImport.public_key = File.read(path) if File.exist?(path) diff --git a/config/initializers/rack_attack_git_basic_auth.rb b/config/initializers/rack_attack_git_basic_auth.rb new file mode 100644 index 0000000000..bbbfed6832 --- /dev/null +++ b/config/initializers/rack_attack_git_basic_auth.rb @@ -0,0 +1,12 @@ +unless Rails.env.test? + # Tell the Rack::Attack Rack middleware to maintain an IP blacklist. We will + # update the blacklist from Grack::Auth#authenticate_user. + Rack::Attack.blacklist('Git HTTP Basic Auth') do |req| + Rack::Attack::Allow2Ban.filter(req.ip, Gitlab.config.rack_attack.git_basic_auth) do + # This block only gets run if the IP was not already banned. + # Return false, meaning that we do not see anything wrong with the + # request at this time + false + end + end +end diff --git a/config/initializers/redis-store-fix-expiry.rb b/config/initializers/redis-store-fix-expiry.rb new file mode 100644 index 0000000000..fce0a13533 --- /dev/null +++ b/config/initializers/redis-store-fix-expiry.rb @@ -0,0 +1,44 @@ +# Monkey-patch Redis::Store to make 'setex' and 'expire' work with namespacing + +module Gitlab + class Redis + class Store + module Namespace + # Redis::Store#setex in redis-store 1.1.4 does not respect namespaces; + # this new method does. + def setex(key, expires_in, value, options=nil) + namespace(key) { |key| super(key, expires_in, value) } + end + + # Redis::Store#expire in redis-store 1.1.4 does not respect namespaces; + # this new method does. + def expire(key, expires_in) + namespace(key) { |key| super(key, expires_in) } + end + + private + + # Our new definitions of #setex and #expire above assume that the + # #namespace method exists. Because we cannot be sure of that, we + # re-implement the #namespace method from Redis::Store::Namespace so + # that it is available for all Redis::Store instances, whether they use + # namespacing or not. + # + # Based on lib/redis/store/namespace.rb L49-51 (redis-store 1.1.4) + def namespace(key) + if @namespace + yield interpolate(key) + else + # This Redis::Store instance does not use a namespace so we should + # just pass through the key. + yield key + end + end + end + end + end +end + +Redis::Store.class_eval do + include Gitlab::Redis::Store::Namespace +end diff --git a/config/initializers/session_store.rb b/config/initializers/session_store.rb index 5fe5270236..b2d59f1c4b 100644 --- a/config/initializers/session_store.rb +++ b/config/initializers/session_store.rb @@ -2,9 +2,10 @@ Gitlab::Application.config.session_store( :redis_store, # Using the cookie_store would enable session replay attacks. - servers: Gitlab::Application.config.cache_store[1], # re-use the Redis config from the Rails cache store + servers: Gitlab::Application.config.cache_store[1].merge(namespace: 'session:gitlab'), # re-use the Redis config from the Rails cache store key: '_gitlab_session', secure: Gitlab.config.gitlab.https, httponly: true, + expire_after: 1.week, path: (Rails.application.config.relative_url_root.nil?) ? '/' : Rails.application.config.relative_url_root ) diff --git a/config/initializers/smtp_settings.rb.sample b/config/initializers/smtp_settings.rb.sample index 3711b03796..f0fe2fdfa4 100644 --- a/config/initializers/smtp_settings.rb.sample +++ b/config/initializers/smtp_settings.rb.sample @@ -1,8 +1,11 @@ -# To enable smtp email delivery for your GitLab instance do next: +# To enable smtp email delivery for your GitLab instance do the following: # 1. Rename this file to smtp_settings.rb # 2. Edit settings inside this file # 3. Restart GitLab instance # +# For full list of options and their values see http://api.rubyonrails.org/classes/ActionMailer/Base.html +# + if Rails.env.production? Gitlab::Application.config.action_mailer.delivery_method = :smtp @@ -13,6 +16,7 @@ if Rails.env.production? password: "123456", domain: "gitlab.company.com", authentication: :login, - enable_starttls_auto: true + enable_starttls_auto: true, + openssl_verify_mode: 'peer' # See ActionMailer documentation for other possible options } end diff --git a/config/initializers/static_files.rb b/config/initializers/static_files.rb new file mode 100644 index 0000000000..d9042c652b --- /dev/null +++ b/config/initializers/static_files.rb @@ -0,0 +1,15 @@ +app = Rails.application + +if app.config.serve_static_assets + # The `ActionDispatch::Static` middleware intercepts requests for static files + # by checking if they exist in the `/public` directory. + # We're replacing it with our `Gitlab::Middleware::Static` that does the same, + # except ignoring `/uploads`, letting those go through to the GitLab Rails app. + + app.config.middleware.swap( + ActionDispatch::Static, + Gitlab::Middleware::Static, + app.paths["public"].first, + app.config.static_cache_control + ) +end diff --git a/config/initializers/time_zone.rb b/config/initializers/time_zone.rb new file mode 100644 index 0000000000..ee246e67d6 --- /dev/null +++ b/config/initializers/time_zone.rb @@ -0,0 +1 @@ +Time.zone = Gitlab.config.gitlab.time_zone || Time.zone diff --git a/config/locales/devise.en.yml b/config/locales/devise.en.yml index 1cbcde5b3d..f3db5b7476 100644 --- a/config/locales/devise.en.yml +++ b/config/locales/devise.en.yml @@ -23,8 +23,8 @@ en: timeout: 'Your session expired, please sign in again to continue.' inactive: 'Your account was not activated yet.' sessions: - signed_in: 'Signed in successfully.' - signed_out: 'Signed out successfully.' + signed_in: '' + signed_out: '' users_sessions: user: signed_in: 'Signed in successfully.' @@ -57,4 +57,4 @@ en: reset_password_instructions: subject: 'Reset password instructions' unlock_instructions: - subject: 'Unlock Instructions' + subject: 'Unlock Instructions' \ No newline at end of file diff --git a/config/locales/doorkeeper.en.yml b/config/locales/doorkeeper.en.yml new file mode 100644 index 0000000000..c5b6b75e7f --- /dev/null +++ b/config/locales/doorkeeper.en.yml @@ -0,0 +1,73 @@ +en: + activerecord: + errors: + models: + application: + attributes: + redirect_uri: + fragment_present: 'cannot contain a fragment.' + invalid_uri: 'must be a valid URI.' + relative_uri: 'must be an absolute URI.' + mongoid: + errors: + models: + application: + attributes: + redirect_uri: + fragment_present: 'cannot contain a fragment.' + invalid_uri: 'must be a valid URI.' + relative_uri: 'must be an absolute URI.' + mongo_mapper: + errors: + models: + application: + attributes: + redirect_uri: + fragment_present: 'cannot contain a fragment.' + invalid_uri: 'must be a valid URI.' + relative_uri: 'must be an absolute URI.' + doorkeeper: + errors: + messages: + # Common error messages + invalid_request: 'The request is missing a required parameter, includes an unsupported parameter value, or is otherwise malformed.' + invalid_redirect_uri: 'The redirect uri included is not valid.' + unauthorized_client: 'The client is not authorized to perform this request using this method.' + access_denied: 'The resource owner or authorization server denied the request.' + invalid_scope: 'The requested scope is invalid, unknown, or malformed.' + server_error: 'The authorization server encountered an unexpected condition which prevented it from fulfilling the request.' + temporarily_unavailable: 'The authorization server is currently unable to handle the request due to a temporary overloading or maintenance of the server.' + + #configuration error messages + credential_flow_not_configured: 'Resource Owner Password Credentials flow failed due to Doorkeeper.configure.resource_owner_from_credentials being unconfigured.' + resource_owner_authenticator_not_configured: 'Resource Owner find failed due to Doorkeeper.configure.resource_owner_authenticator being unconfiged.' + + # Access grant errors + unsupported_response_type: 'The authorization server does not support this response type.' + + # Access token errors + invalid_client: 'Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method.' + invalid_grant: 'The provided authorization grant is invalid, expired, revoked, does not match the redirection URI used in the authorization request, or was issued to another client.' + unsupported_grant_type: 'The authorization grant type is not supported by the authorization server.' + + # Password Access token errors + invalid_resource_owner: 'The provided resource owner credentials are not valid, or resource owner cannot be found' + + invalid_token: + revoked: "The access token was revoked" + expired: "The access token expired" + unknown: "The access token is invalid" + scopes: + api: Access your API + + flash: + applications: + create: + notice: 'Application created.' + destroy: + notice: 'Application deleted.' + update: + notice: 'Application updated.' + authorized_applications: + destroy: + notice: 'Application revoked.' diff --git a/config/newrelic.yml b/config/newrelic.yml new file mode 100644 index 0000000000..9ef922a38d --- /dev/null +++ b/config/newrelic.yml @@ -0,0 +1,16 @@ +# New Relic configuration file +# +# This file is here to make sure the New Relic gem stays +# quiet by default. +# +# To enable and configure New Relic, please use +# environment variables, e.g. NEW_RELIC_ENABLED=true + +production: + enabled: false + +development: + enabled: false + +test: + enabled: false diff --git a/config/resque.yml.example b/config/resque.yml.example index 3c7ad0e577..347f3599b2 100644 --- a/config/resque.yml.example +++ b/config/resque.yml.example @@ -1,3 +1,3 @@ development: redis://localhost:6379 test: redis://localhost:6379 -production: redis://redis.example.com:6379 +production: unix:/var/run/redis/redis.sock diff --git a/config/routes.rb b/config/routes.rb index ce66ea9995..744a99fede 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -2,11 +2,20 @@ require 'sidekiq/web' require 'api/api' Gitlab::Application.routes.draw do - # + use_doorkeeper do + controllers applications: 'oauth/applications', + authorized_applications: 'oauth/authorized_applications', + authorizations: 'oauth/authorizations' + end + + # Autocomplete + get '/autocomplete/users' => 'autocomplete#users' + get '/autocomplete/users/:id' => 'autocomplete#user' + + # Search - # - get 'search' => "search#show" - get 'search/autocomplete' => "search#autocomplete", as: :search_autocomplete + get 'search' => 'search#show' + get 'search/autocomplete' => 'search#autocomplete', as: :search_autocomplete # API API::API.logger Rails.logger @@ -15,9 +24,9 @@ Gitlab::Application.routes.draw do # Get all keys of user get ':username.keys' => 'profiles/keys#get_keys' , constraints: { username: /.*/ } - constraint = lambda { |request| request.env["warden"].authenticate? and request.env['warden'].user.admin? } + constraint = lambda { |request| request.env['warden'].authenticate? and request.env['warden'].user.admin? } constraints constraint do - mount Sidekiq::Web, at: "/admin/sidekiq", as: :sidekiq + mount Sidekiq::Web, at: '/admin/sidekiq', as: :sidekiq end # Enable Grack support @@ -28,26 +37,94 @@ Gitlab::Application.routes.draw do receive_pack: Gitlab.config.gitlab_shell.receive_pack }), at: '/', constraints: lambda { |request| /[-\/\w\.]+\.git\//.match(request.path_info) }, via: [:get, :post] - # # Help - # - get 'help' => 'help#index' - get 'help/:category/:file' => 'help#show', as: :help_page + get 'help/:category/:file' => 'help#show', as: :help_page, constraints: { category: /.*/, file: /[^\/\.]+/ } get 'help/shortcuts' + get 'help/ui' => 'help#ui' # # Global snippets # resources :snippets do member do - get "raw" + get 'raw' end end - get "/s/:username" => "snippets#user_index", as: :user_snippets, constraints: { username: /.*/ } + get '/s/:username' => 'snippets#user_index', as: :user_snippets, constraints: { username: /.*/ } # - # Explroe area + # Invites + # + + resources :invites, only: [:show], constraints: { id: /[A-Za-z0-9_-]+/ } do + member do + post :accept + match :decline, via: [:get, :post] + end + end + + # + # Import + # + namespace :import do + resource :github, only: [:create, :new], controller: :github do + get :status + get :callback + get :jobs + end + + resource :gitlab, only: [:create, :new], controller: :gitlab do + get :status + get :callback + get :jobs + end + + resource :bitbucket, only: [:create, :new], controller: :bitbucket do + get :status + get :callback + get :jobs + end + + resource :gitorious, only: [:create, :new], controller: :gitorious do + get :status + get :callback + get :jobs + end + + resource :google_code, only: [:create, :new], controller: :google_code do + get :status + post :callback + get :jobs + + get :new_user_map, path: :user_map + post :create_user_map, path: :user_map + end + end + + # + # Uploads + # + + scope path: :uploads do + # Note attachments and User/Group/Project avatars + get ":model/:mounted_as/:id/:filename", + to: "uploads#show", + constraints: { model: /note|user|group|project/, mounted_as: /avatar|attachment/, filename: /[^\/]+/ } + + # Project markdown uploads + get ":namespace_id/:project_id/:secret/:filename", + to: "projects/uploads#show", + constraints: { namespace_id: /[a-zA-Z.0-9_\-]+/, project_id: /[a-zA-Z.0-9_\-]+/, filename: /[^\/]+/ } + end + + # Redirect old note attachments path to new uploads path. + get "files/note/:id/:filename", + to: redirect("uploads/note/attachment/%{id}/%{filename}"), + constraints: { filename: /[^\/]+/ } + + # + # Explore area # namespace :explore do resources :projects, only: [:index] do @@ -58,23 +135,19 @@ Gitlab::Application.routes.draw do end resources :groups, only: [:index] - root to: "projects#trending" + root to: 'projects#trending' end # Compatibility with old routing - get 'public' => "explore/projects#index" - get 'public/projects' => "explore/projects#index" - - # - # Attachments serving - # - get 'files/:type/:id/:filename' => 'files#download', constraints: { id: /\d+/, type: /[a-z]+/, filename: /.+/ } + get 'public' => 'explore/projects#index' + get 'public/projects' => 'explore/projects#index' # # Admin Area # namespace :admin do resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do + resources :keys, only: [:show, :destroy] member do put :team_update put :block @@ -83,12 +156,16 @@ Gitlab::Application.routes.draw do end end + resources :applications + resources :groups, constraints: { id: /[^\/]+/ } do member do - put :project_teams_update + put :members_update end end + resources :deploy_keys, only: [:index, :show, :new, :create, :destroy] + resources :hooks, only: [:index, :create, :destroy] do get :test end @@ -97,13 +174,26 @@ Gitlab::Application.routes.draw do resource :logs, only: [:show] resource :background_jobs, controller: 'background_jobs', only: [:show] - resources :projects, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ }, only: [:index, :show] do - member do - put :transfer + resources :namespaces, path: '/projects', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do + root to: 'projects#index', as: :projects + + resources(:projects, + path: '/', + constraints: { id: /[a-zA-Z.0-9_\-]+/ }, + only: [:index, :show]) do + root to: 'projects#show' + + member do + put :transfer + end end end - root to: "dashboard#index" + resource :application_settings, only: [:show, :update] do + resources :services + end + + root to: 'dashboard#index' end # @@ -113,13 +203,18 @@ Gitlab::Application.routes.draw do member do get :history get :design + get :applications put :reset_private_token put :update_username end scope module: :profiles do - resource :account, only: [:show, :update] + resource :account, only: [:show, :update] do + member do + delete :unlink + end + end resource :notifications, only: [:show, :update] resource :password, only: [:new, :create, :edit, :update] do member do @@ -128,213 +223,301 @@ Gitlab::Application.routes.draw do end resources :keys resources :emails, only: [:index, :create, :destroy] - resources :groups, only: [:index] do - member do - delete :leave - end - end resource :avatar, only: [:destroy] end end - match "/u/:username" => "users#show", as: :user, constraints: { username: /.*/ }, via: :get + 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' => 'users#show', as: :user, + constraints: { username: /(?:[^.]|\.(?!atom$))+/, format: /atom/ } # # Dashboard Area # - resource :dashboard, controller: "dashboard", only: [:show] do + resource :dashboard, controller: 'dashboard', only: [:show] do member do - get :projects get :issues get :merge_requests end + + scope module: :dashboard do + resources :milestones, only: [:index, :show] + + resources :groups, only: [:index] + + resources :projects, only: [] do + collection do + get :starred + end + end + end end # # Groups Area # - resources :groups, constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/} do + resources :groups, constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ } do member do get :issues get :merge_requests - get :members get :projects end - resources :users_groups, only: [:create, :update, :destroy] - scope module: :groups do + resources :group_members, only: [:index, :create, :update, :destroy] do + post :resend_invite, on: :member + delete :leave, on: :collection + end + resource :avatar, only: [:destroy] - resources :milestones + resources :milestones, only: [:index, :show, :update] end end resources :projects, constraints: { id: /[^\/]+/ }, only: [:new, :create] - devise_for :users, controllers: { omniauth_callbacks: :omniauth_callbacks, registrations: :registrations , passwords: :passwords, sessions: :sessions } + 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 + get '/users/auth/:provider/omniauth_error' => 'omniauth_callbacks#omniauth_error', as: :omniauth_error end + + root to: "dashboard#show" + # # Project Area # - resources :projects, constraints: { id: /[a-zA-Z.0-9_\-]+\/[a-zA-Z.0-9_\-]+/ }, except: [:new, :create, :index], path: "/" do - member do - put :transfer - post :fork - post :archive - post :unarchive - post :upload_image - post :toggle_star - get :autocomplete_sources - get :import - put :retry_import - end - - scope module: :projects do - resources :blob, only: [:show, :destroy], constraints: { id: /.+/ } do - get :diff, on: :member + resources :namespaces, path: '/', constraints: { id: /[a-zA-Z.0-9_\-]+/ }, only: [] do + resources(:projects, constraints: { id: /[a-zA-Z.0-9_\-]+/ }, except: + [:new, :create, :index], path: "/") do + member do + put :transfer + post :archive + post :unarchive + post :toggle_star + post :markdown_preview + get :autocomplete_sources end - resources :raw, only: [:show], constraints: {id: /.+/} - resources :tree, only: [:show], constraints: {id: /.+/, format: /(html|js)/ } - resources :edit_tree, only: [:show, :update], constraints: { id: /.+/ }, path: 'edit' do - post :preview, on: :member - end - resources :new_tree, only: [:show, :update], constraints: {id: /.+/}, path: 'new' - resources :commit, only: [:show], constraints: {id: /[[:alnum:]]{6,40}/} - resources :commits, only: [:show], constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/} - resources :compare, only: [:index, :create] - resources :blame, only: [:show], constraints: {id: /.+/} - resources :network, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/} - resources :graphs, only: [:show], constraints: {id: /(?:[^.]|\.(?!json$))+/, format: /json/} - match "/compare/:from...:to" => "compare#show", as: "compare", via: [:get, :post], constraints: {from: /.+/, to: /.+/} + scope module: :projects do + # Blob routes: + get '/new/*id', to: 'blob#new', constraints: { id: /.+/ }, as: 'new_blob' + post '/create/*id', to: 'blob#create', constraints: { id: /.+/ }, as: 'create_blob' + get '/edit/*id', to: 'blob#edit', constraints: { id: /.+/ }, as: 'edit_blob' + put '/update/*id', to: 'blob#update', constraints: { id: /.+/ }, as: 'update_blob' + post '/preview/*id', to: 'blob#preview', constraints: { id: /.+/ }, as: 'preview_blob' - resources :snippets, constraints: {id: /\d+/} do + scope do + get( + '/blob/*id/diff', + to: 'blob#diff', + constraints: { id: /.+/, format: false }, + as: :blob_diff + ) + get( + '/blob/*id', + to: 'blob#show', + constraints: { id: /.+/, format: false }, + as: :blob + ) + delete( + '/blob/*id', + to: 'blob#destroy', + constraints: { id: /.+/, format: false } + ) + end + + scope do + get( + '/raw/*id', + to: 'raw#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :raw + ) + end + + scope do + get( + '/tree/*id', + to: 'tree#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :tree + ) + end + + scope do + get( + '/blame/*id', + to: 'blame#show', + constraints: { id: /.+/, format: /(html|js)/ }, + as: :blame + ) + end + + scope do + get( + '/commits/*id', + to: 'commits#show', + constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ }, + as: :commits + ) + end + + resource :avatar, only: [:show, :destroy] + resources :commit, only: [:show], constraints: { id: /[[:alnum:]]{6,40}/ } do + get :branches, on: :member + end + + resources :compare, only: [:index, :create] + resources :network, only: [:show], constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ } + + resources :graphs, only: [:show], constraints: { id: /(?:[^.]|\.(?!json$))+/, format: /json/ } do member do - get "raw" + get :commits end end - resources :wikis, only: [:show, :edit, :destroy, :create], constraints: {id: /[a-zA-Z.0-9_\-\/]+/} do - collection do - get :pages - put ':id' => 'wikis#update' - get :git_access + get '/compare/:from...:to' => 'compare#show', :as => 'compare', + :constraints => { from: /.+/, to: /.+/ } + + resources :snippets, constraints: { id: /\d+/ } do + member do + get 'raw' + end end - member do - get "history" - end - end + resources :wikis, only: [:show, :edit, :destroy, :create], constraints: { id: /[a-zA-Z.0-9_\-\/]+/ } do + collection do + get :pages + put ':id' => 'wikis#update' + get :git_access + end - resource :repository, only: [:show] do - member do - get "stats" - get "archive", constraints: { format: Gitlab::Regex.archive_formats_regex } - end - end - - resources :services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do - member do - get :test - end - end - - resources :deploy_keys, constraints: {id: /\d+/} do - member do - put :enable - put :disable - end - end - - resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } - resources :tags, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } - resources :protected_branches, only: [:index, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } - - resources :refs, only: [] do - collection do - get "switch" + member do + get 'history' + end end - member do - # tree viewer logs - get "logs_tree", constraints: { id: Gitlab::Regex.git_reference_regex } - get "logs_tree/:path" => "refs#logs_tree", - as: :logs_file, - constraints: { - id: Gitlab::Regex.git_reference_regex, + resource :repository, only: [:show, :create] do + member do + get 'archive', constraints: { format: Gitlab::Regex.archive_formats_regex } + end + end + + resources :services, constraints: { id: /[^\/]+/ }, only: [:index, :edit, :update] do + member do + get :test + end + end + + resources :deploy_keys, constraints: { id: /\d+/ }, only: [:index, :show, :new, :create] do + member do + put :enable + put :disable + end + end + + resource :fork, only: [:new, :create] + resource :import, only: [:new, :create, :show] + + resources :refs, only: [] do + collection do + get 'switch' + end + + member do + # tree viewer logs + get 'logs_tree', constraints: { id: Gitlab::Regex.git_reference_regex } + get 'logs_tree/:path' => 'refs#logs_tree', as: :logs_file, constraints: { + id: Gitlab::Regex.git_reference_regex, path: /.*/ } + end + end + + resources :merge_requests, constraints: { id: /\d+/ }, except: [:destroy] do + member do + get :diffs + post :automerge + get :automerge_check + get :ci_status + post :toggle_subscription + end + + collection do + get :branch_from + get :branch_to + get :update_branches + end + end + + resources :branches, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } + resources :tags, only: [:index, :new, :create, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } + resources :protected_branches, only: [:index, :create, :update, :destroy], constraints: { id: Gitlab::Regex.git_reference_regex } + + resources :hooks, only: [:index, :create, :destroy], constraints: { id: /\d+/ } do + member do + get :test + end + end + + resources :milestones, except: [:destroy], constraints: { id: /\d+/ } do + member do + put :sort_issues + put :sort_merge_requests + end + end + + resources :labels, constraints: { id: /\d+/ } do + collection do + post :generate + end + end + + resources :issues, constraints: { id: /\d+/ }, except: [:destroy] do + member do + post :toggle_subscription + end + collection do + post :bulk_update + end + end + + resources :project_members, except: [:new, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ } do + collection do + delete :leave + + # Used for import team + # from another project + get :import + post :apply_import + end + + member do + post :resend_invite + end + end + + resources :notes, only: [:index, :create, :destroy, :update], constraints: { id: /\d+/ } do + member do + delete :delete_attachment + end + end + + resources :uploads, only: [:create] do + collection do + get ":secret/:filename", action: :show, as: :show, constraints: { filename: /[^\/]+/ } + end end end - resources :merge_requests, constraints: {id: /\d+/}, except: [:destroy] do - member do - get :diffs - post :automerge - get :automerge_check - get :ci_status - end - - collection do - get :branch_from - get :branch_to - get :update_branches - end - end - - resources :hooks, only: [:index, :create, :destroy], constraints: {id: /\d+/} do - member do - get :test - end - end - - resources :team, controller: 'team_members', only: [:index] - resources :milestones, except: [:destroy], constraints: {id: /\d+/} do - member do - put :sort_issues - put :sort_merge_requests - end - end - - resources :labels, constraints: {id: /\d+/} do - collection do - post :generate - end - end - - resources :issues, constraints: {id: /\d+/}, except: [:destroy] do - collection do - post :bulk_update - end - end - - resources :team_members, except: [:index, :edit], constraints: { id: /[a-zA-Z.\/0-9_\-#%+]+/ } do - collection do - delete :leave - - # Used for import team - # from another project - get :import - post :apply_import - end - end - - resources :notes, only: [:index, :create, :destroy, :update], constraints: {id: /\d+/} do - member do - delete :delete_attachment - end - - collection do - post :preview - end - end end end - get ':id' => "namespaces#show", constraints: {id: /(?:[^.]|\.(?!atom$))+/, format: /atom/} - - root to: "dashboard#show" + get ':id' => 'namespaces#show', constraints: { id: /(?:[^.]|\.(?!atom$))+/, format: /atom/ } end diff --git a/config/unicorn.rb.example b/config/unicorn.rb.example index e88a452233..86a5512e76 100644 --- a/config/unicorn.rb.example +++ b/config/unicorn.rb.example @@ -13,9 +13,10 @@ # # ENV['RAILS_RELATIVE_URL_ROOT'] = "/gitlab" -# Use at least one worker per core if you're on a dedicated server, -# more will usually help for _short_ waits on databases/caches. -worker_processes 2 +# Read about unicorn workers here: +# http://doc.gitlab.com/ee/install/requirements.html#unicorn-workers +# +worker_processes 3 # Since Unicorn is never exposed to outside clients, it does not need to # run on the standard HTTP port (80), there is no reason to start Unicorn @@ -28,17 +29,18 @@ worker_processes 2 # "current" directory that Capistrano sets up. working_directory "/home/git/gitlab" # available in 0.94.0+ -# listen on both a Unix domain socket and a TCP port, -# we use a shorter backlog for quicker failover when busy -listen "/home/git/gitlab/tmp/sockets/gitlab.socket", :backlog => 64 +# Listen on both a Unix domain socket and a TCP port. +# If you are load-balancing multiple Unicorn masters, lower the backlog +# setting to e.g. 64 for faster failover. +listen "/home/git/gitlab/tmp/sockets/gitlab.socket", :backlog => 1024 listen "127.0.0.1:8080", :tcp_nopush => true # nuke workers after 30 seconds instead of 60 seconds (the default) # -# NOTICE: git push over http depends on this value. -# If you want be able to push huge amount of data to git repository over http -# you will have to increase this value too. -# +# NOTICE: git push over http depends on this value. +# If you want be able to push huge amount of data to git repository over http +# you will have to increase this value too. +# # Example of output if you try to push 1GB repo to GitLab over http. # -> git push http://gitlab.... master # @@ -48,7 +50,7 @@ listen "127.0.0.1:8080", :tcp_nopush => true # # For more information see http://stackoverflow.com/a/21682112/752049 # -timeout 30 +timeout 60 # feel free to point this anywhere accessible on the filesystem pid "/home/git/gitlab/tmp/pids/unicorn.pid" diff --git a/config/unicorn.rb.example.development b/config/unicorn.rb.example.development index 94a7061451..3cd00d53a1 100644 --- a/config/unicorn.rb.example.development +++ b/config/unicorn.rb.example.development @@ -1,2 +1,2 @@ worker_processes 2 -timeout 30 +timeout 60 diff --git a/db/fixtures/development/01_admin.rb b/db/fixtures/development/01_admin.rb index 1927a39f38..bba2fc4b18 100644 --- a/db/fixtures/development/01_admin.rb +++ b/db/fixtures/development/01_admin.rb @@ -1,11 +1,13 @@ -User.seed do |s| - s.id = 1 - s.name = "Administrator" - s.email = "admin@example.com" - s.username = 'root' - s.password = "5iveL!fe" - s.password_confirmation = "5iveL!fe" - s.admin = true - s.projects_limit = 100 - s.confirmed_at = DateTime.now +Gitlab::Seeder.quiet do + User.seed do |s| + s.id = 1 + s.name = 'Administrator' + s.email = 'admin@example.com' + s.notification_email = 'admin@example.com' + s.username = 'root' + s.password = '5iveL!fe' + s.admin = true + s.projects_limit = 100 + s.confirmed_at = DateTime.now + end end diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb index b93229a060..ae4c0550a4 100644 --- a/db/fixtures/development/04_project.rb +++ b/db/fixtures/development/04_project.rb @@ -4,10 +4,10 @@ Sidekiq::Testing.inline! do Gitlab::Seeder.quiet do project_urls = [ 'https://github.com/documentcloud/underscore.git', - 'https://github.com/gitlabhq/gitlabhq.git', - 'https://github.com/gitlabhq/gitlab-ci.git', - 'https://github.com/gitlabhq/gitlab-shell.git', - 'https://github.com/gitlabhq/testme.git', + 'https://gitlab.com/gitlab-org/gitlab-ce.git', + 'https://gitlab.com/gitlab-org/gitlab-ci.git', + 'https://gitlab.com/gitlab-org/gitlab-shell.git', + 'https://gitlab.com/gitlab-org/gitlab-test.git', 'https://github.com/twitter/flight.git', 'https://github.com/twitter/typeahead.js.git', 'https://github.com/h5bp/html5-boilerplate.git', diff --git a/db/fixtures/development/05_users.rb b/db/fixtures/development/05_users.rb index f4a5b8631a..24952a1f66 100644 --- a/db/fixtures/development/05_users.rb +++ b/db/fixtures/development/05_users.rb @@ -1,15 +1,31 @@ Gitlab::Seeder.quiet do (2..20).each do |i| begin - User.seed(:id, [{ - id: i, + User.create!( username: Faker::Internet.user_name, name: Faker::Name.name, email: Faker::Internet.email, - confirmed_at: DateTime.now - }]) + confirmed_at: DateTime.now, + password: '12345678' + ) + print '.' - rescue ActiveRecord::RecordNotSaved + rescue ActiveRecord::RecordInvalid + print 'F' + end + end + + (1..5).each do |i| + begin + User.create!( + username: "user#{i}", + name: "User #{i}", + email: "user#{i}@example.com", + confirmed_at: DateTime.now, + password: '12345678' + ) + print '.' + rescue ActiveRecord::RecordInvalid print 'F' end end diff --git a/db/fixtures/development/06_teams.rb b/db/fixtures/development/06_teams.rb index dfbe75fd20..3e8cdcd67b 100644 --- a/db/fixtures/development/06_teams.rb +++ b/db/fixtures/development/06_teams.rb @@ -1,7 +1,7 @@ Gitlab::Seeder.quiet do Group.all.each do |group| User.all.sample(4).each do |user| - if group.add_users([user.id], UsersGroup.group_access_roles.values.sample) + if group.add_users([user.id], Gitlab::Access.values.sample) print '.' else print 'F' @@ -11,7 +11,7 @@ Gitlab::Seeder.quiet do Project.all.each do |project| User.all.sample(4).each do |user| - if project.team << [user, UsersProject.access_roles.values.sample] + if project.team << [user, Gitlab::Access.values.sample] print '.' else print 'F' diff --git a/db/fixtures/development/10_merge_requests.rb b/db/fixtures/development/10_merge_requests.rb index 03f8de1298..f9b2fd8b05 100644 --- a/db/fixtures/development/10_merge_requests.rb +++ b/db/fixtures/development/10_merge_requests.rb @@ -20,4 +20,22 @@ Gitlab::Seeder.quiet do print '.' end end + + project = Project.find_with_namespace('gitlab-org/gitlab-test') + + params = { + source_branch: 'feature', + target_branch: 'master', + title: 'Can be automatically merged' + } + MergeRequests::CreateService.new(project, User.admins.first, params).execute + print '.' + + params = { + source_branch: 'feature_conflict', + target_branch: 'feature', + title: 'Cannot be automatically merged' + } + MergeRequests::CreateService.new(project, User.admins.first, params).execute + print '.' end diff --git a/db/fixtures/development/12_snippets.rb b/db/fixtures/development/12_snippets.rb index ff91e8430a..b3a6f39c7d 100644 --- a/db/fixtures/development/12_snippets.rb +++ b/db/fixtures/development/12_snippets.rb @@ -1,9 +1,26 @@ Gitlab::Seeder.quiet do - contents = [ - `curl https://gist.githubusercontent.com/randx/4275756/raw/da2f262920c96d1a970d48bf2e99147954b1f4bd/glus1204.sh`, - `curl https://gist.githubusercontent.com/randx/3754594/raw/11026a295e6ef3a151c635707a3e1e8e15fc4725/gitlab_setup.sh`, - `curl https://gist.githubusercontent.com/randx/3065552/raw/29fbd09f4605a5ea22a5a9095e35fd1938dea4d6/gistfile1.sh`, - ] + content =< { where(access_level: GUEST) } + scope :reporters, -> { where(access_level: REPORTER) } + scope :developers, -> { where(access_level: DEVELOPER) } + scope :masters, -> { where(access_level: MASTER) } + scope :owners, -> { where(access_level: OWNER) } + + delegate :name, :username, :email, to: :user, prefix: true +end +eos (1..50).each do |i| user = User.all.sample @@ -12,10 +29,11 @@ Gitlab::Seeder.quiet do id: i, author_id: user.id, title: Faker::Lorem.sentence(3), - file_name: Faker::Internet.domain_word + '.sh', - private: [true, false].sample, - content: contents.sample, + file_name: Faker::Internet.domain_word + '.rb', + visibility_level: Gitlab::VisibilityLevel.values.sample, + content: content, }]) + print('.') end end diff --git a/db/fixtures/production/001_admin.rb b/db/fixtures/production/001_admin.rb index c00ba3c10b..8b560ee09e 100644 --- a/db/fixtures/production/001_admin.rb +++ b/db/fixtures/production/001_admin.rb @@ -1,10 +1,17 @@ +if ENV['GITLAB_ROOT_PASSWORD'].blank? + password = '5iveL!fe' + expire_time = Time.now +else + password = ENV['GITLAB_ROOT_PASSWORD'] + expire_time = nil +end + admin = User.create( email: "admin@example.com", name: "Administrator", username: 'root', - password: "5iveL!fe", - password_confirmation: "5iveL!fe", - password_expires_at: Time.now, + password: password, + password_expires_at: expire_time, theme_id: Gitlab::Theme::MARS ) @@ -15,10 +22,10 @@ admin.save! admin.confirm! if admin.valid? -puts %q[ +puts %Q[ Administrator account created: login.........root -password......5iveL!fe +password......#{password} ] end diff --git a/db/migrate/20140125162722_add_avatar_to_projects.rb b/db/migrate/20140125162722_add_avatar_to_projects.rb new file mode 100644 index 0000000000..9523ac722f --- /dev/null +++ b/db/migrate/20140125162722_add_avatar_to_projects.rb @@ -0,0 +1,5 @@ +class AddAvatarToProjects < ActiveRecord::Migration + def change + add_column :projects, :avatar, :string + end +end diff --git a/db/migrate/20140903115954_migrate_to_new_shell.rb b/db/migrate/20140903115954_migrate_to_new_shell.rb new file mode 100644 index 0000000000..2d83210951 --- /dev/null +++ b/db/migrate/20140903115954_migrate_to_new_shell.rb @@ -0,0 +1,10 @@ +class MigrateToNewShell < ActiveRecord::Migration + def change + gitlab_shell_path = Gitlab.config.gitlab_shell.path + if system("#{gitlab_shell_path}/bin/create-hooks") + puts 'Repositories updated with new hooks' + else + raise 'Failed to rewrite gitlab-shell hooks in repositories' + end + end +end diff --git a/db/migrate/20140907220153_serialize_service_properties.rb b/db/migrate/20140907220153_serialize_service_properties.rb new file mode 100644 index 0000000000..d45a10465b --- /dev/null +++ b/db/migrate/20140907220153_serialize_service_properties.rb @@ -0,0 +1,42 @@ +class SerializeServiceProperties < ActiveRecord::Migration + def change + unless column_exists?(:services, :properties) + add_column :services, :properties, :text + end + + Service.reset_column_information + + associations = + { + AssemblaService: [:token, :subdomain], + CampfireService: [:token, :subdomain, :room], + EmailsOnPushService: [:recipients], + FlowdockService: [:token], + GemnasiumService: [:api_key, :token], + GitlabCiService: [:token, :project_url], + HipchatService: [:token, :room], + PivotaltrackerService: [:token], + SlackService: [:subdomain, :token, :room], + JenkinsService: [:project_url], + JiraService: [:project_url, :username, :password, + :api_version, :jira_issue_transition_id], + } + + Service.find_each(batch_size: 500).each do |service| + associations[service.type.to_sym].each do |attribute| + service.send("#{attribute}=", service.attributes[attribute.to_s]) + end + + service.save(validate: false) + end + + if column_exists?(:services, :project_url) + remove_column :services, :project_url, :string + remove_column :services, :subdomain, :string + remove_column :services, :room, :string + remove_column :services, :recipients, :text + remove_column :services, :api_key, :string + remove_column :services, :token, :string + end + end +end diff --git a/db/migrate/20140914113604_add_members_table.rb b/db/migrate/20140914113604_add_members_table.rb new file mode 100644 index 0000000000..d311f3033e --- /dev/null +++ b/db/migrate/20140914113604_add_members_table.rb @@ -0,0 +1,19 @@ +class AddMembersTable < ActiveRecord::Migration + def change + create_table :members do |t| + t.integer :access_level, null: false + t.integer :source_id, null: false + t.string :source_type, null: false + t.integer :user_id, null: false + t.integer :notification_level, null: false + t.string :type + + t.timestamps + end + + add_index :members, :type + add_index :members, :user_id + add_index :members, :access_level + add_index :members, [:source_id, :source_type] + end +end diff --git a/db/migrate/20140914145549_migrate_to_new_members_model.rb b/db/migrate/20140914145549_migrate_to_new_members_model.rb new file mode 100644 index 0000000000..2a5a49c724 --- /dev/null +++ b/db/migrate/20140914145549_migrate_to_new_members_model.rb @@ -0,0 +1,11 @@ +class MigrateToNewMembersModel < ActiveRecord::Migration + def up + execute "INSERT INTO members ( user_id, source_id, source_type, access_level, notification_level, type ) SELECT user_id, group_id, 'Namespace', group_access, notification_level, 'GroupMember' FROM users_groups" + execute "INSERT INTO members ( user_id, source_id, source_type, access_level, notification_level, type ) SELECT user_id, project_id, 'Project', project_access, notification_level, 'ProjectMember' FROM users_projects" + end + + def down + Member.delete_all + end +end + diff --git a/db/migrate/20140914173417_remove_old_member_tables.rb b/db/migrate/20140914173417_remove_old_member_tables.rb new file mode 100644 index 0000000000..408b9551db --- /dev/null +++ b/db/migrate/20140914173417_remove_old_member_tables.rb @@ -0,0 +1,26 @@ +class RemoveOldMemberTables < ActiveRecord::Migration + def up + drop_table :users_groups + drop_table :users_projects + end + + def down + create_table :users_groups do |t| + t.integer :group_access, null: false + t.integer :group_id, null: false + t.integer :user_id, null: false + t.integer :notification_level, null: false, default: 3 + + t.timestamps + end + + create_table :users_projects do |t| + t.integer :project_access, null: false + t.integer :project_id, null: false + t.integer :user_id, null: false + t.integer :notification_level, null: false, default: 3 + + t.timestamps + end + end +end diff --git a/db/migrate/20141006143943_move_slack_service_to_webhook.rb b/db/migrate/20141006143943_move_slack_service_to_webhook.rb new file mode 100644 index 0000000000..5836cd6b8d --- /dev/null +++ b/db/migrate/20141006143943_move_slack_service_to_webhook.rb @@ -0,0 +1,17 @@ +class MoveSlackServiceToWebhook < ActiveRecord::Migration + def change + SlackService.all.each do |slack_service| + if ["token", "subdomain"].all? { |property| slack_service.properties.key? property } + token = slack_service.properties['token'] + subdomain = slack_service.properties['subdomain'] + webhook = "https://#{subdomain}.slack.com/services/hooks/incoming-webhook?token=#{token}" + slack_service.properties['webhook'] = webhook + slack_service.properties.delete('token') + slack_service.properties.delete('subdomain') + # Room is configured on the Slack side + slack_service.properties.delete('room') + slack_service.save(validate: false) + end + end + end +end diff --git a/db/migrate/20141007100818_add_visibility_level_to_snippet.rb b/db/migrate/20141007100818_add_visibility_level_to_snippet.rb new file mode 100644 index 0000000000..7f125acb5d --- /dev/null +++ b/db/migrate/20141007100818_add_visibility_level_to_snippet.rb @@ -0,0 +1,21 @@ +class AddVisibilityLevelToSnippet < ActiveRecord::Migration + def up + add_column :snippets, :visibility_level, :integer, :default => 0, :null => false + + Snippet.where(private: true).update_all(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + Snippet.where(private: false).update_all(visibility_level: Gitlab::VisibilityLevel::INTERNAL) + + add_index :snippets, :visibility_level + + remove_column :snippets, :private + end + + def down + add_column :snippets, :private, :boolean, :default => false, :null => false + + Snippet.where(visibility_level: Gitlab::VisibilityLevel::INTERNAL).update_all(private: false) + Snippet.where(visibility_level: Gitlab::VisibilityLevel::PRIVATE).update_all(private: true) + + remove_column :snippets, :visibility_level + end +end diff --git a/db/migrate/20141121133009_add_timestamps_to_members.rb b/db/migrate/20141121133009_add_timestamps_to_members.rb new file mode 100644 index 0000000000..ef6d4dedf3 --- /dev/null +++ b/db/migrate/20141121133009_add_timestamps_to_members.rb @@ -0,0 +1,15 @@ +# In 20140914145549_migrate_to_new_members_model.rb we forgot to set the +# created_at and updated_at times for new records in the 'members' table. This +# became a problem after commit c8e78d972a5a628870eefca0f2ccea0199c55bda which +# was added in GitLab 7.5. With this migration we ensure that all rows in +# 'members' have at least some created_at and updated_at timestamp. +class AddTimestampsToMembers < ActiveRecord::Migration + def up + execute "UPDATE members SET created_at = NOW() WHERE created_at is NULL" + execute "UPDATE members SET updated_at = NOW() WHERE updated_at is NULL" + end + + def down + # no change + end +end diff --git a/db/migrate/20141121161704_add_identity_table.rb b/db/migrate/20141121161704_add_identity_table.rb new file mode 100644 index 0000000000..a85b0426ce --- /dev/null +++ b/db/migrate/20141121161704_add_identity_table.rb @@ -0,0 +1,46 @@ +class AddIdentityTable < ActiveRecord::Migration + def up + create_table :identities do |t| + t.string :extern_uid + t.string :provider + t.references :user + end + + add_index :identities, :user_id + + execute < 2 + end +end diff --git a/db/migrate/20150205211843_add_timestamps_to_identities.rb b/db/migrate/20150205211843_add_timestamps_to_identities.rb new file mode 100644 index 0000000000..77cddbfec3 --- /dev/null +++ b/db/migrate/20150205211843_add_timestamps_to_identities.rb @@ -0,0 +1,5 @@ +class AddTimestampsToIdentities < ActiveRecord::Migration + def change + add_timestamps(:identities) + end +end diff --git a/db/migrate/20150206181414_add_index_to_created_at.rb b/db/migrate/20150206181414_add_index_to_created_at.rb new file mode 100644 index 0000000000..fc624fca60 --- /dev/null +++ b/db/migrate/20150206181414_add_index_to_created_at.rb @@ -0,0 +1,16 @@ +class AddIndexToCreatedAt < ActiveRecord::Migration + def change + add_index "users", [:created_at, :id] + add_index "members", [:created_at, :id] + add_index "projects", [:created_at, :id] + add_index "issues", [:created_at, :id] + add_index "merge_requests", [:created_at, :id] + add_index "milestones", [:created_at, :id] + add_index "namespaces", [:created_at, :id] + add_index "notes", [:created_at, :id] + add_index "identities", [:created_at, :id] + add_index "keys", [:created_at, :id] + add_index "web_hooks", [:created_at, :id] + add_index "snippets", [:created_at, :id] + end +end diff --git a/db/migrate/20150206222854_add_notification_email_to_user.rb b/db/migrate/20150206222854_add_notification_email_to_user.rb new file mode 100644 index 0000000000..ab80f7e582 --- /dev/null +++ b/db/migrate/20150206222854_add_notification_email_to_user.rb @@ -0,0 +1,11 @@ +class AddNotificationEmailToUser < ActiveRecord::Migration + def up + add_column :users, :notification_email, :string + + execute "UPDATE users SET notification_email = email" + end + + def down + remove_column :users, :notification_email + end +end diff --git a/db/migrate/20150209222013_add_missing_index.rb b/db/migrate/20150209222013_add_missing_index.rb new file mode 100644 index 0000000000..a816c2e9e8 --- /dev/null +++ b/db/migrate/20150209222013_add_missing_index.rb @@ -0,0 +1,5 @@ +class AddMissingIndex < ActiveRecord::Migration + def change + add_index "services", [:created_at, :id] + end +end diff --git a/db/migrate/20150211172122_add_template_to_service.rb b/db/migrate/20150211172122_add_template_to_service.rb new file mode 100644 index 0000000000..b1bfbc45ee --- /dev/null +++ b/db/migrate/20150211172122_add_template_to_service.rb @@ -0,0 +1,5 @@ +class AddTemplateToService < ActiveRecord::Migration + def change + add_column :services, :template, :boolean, default: false + end +end diff --git a/db/migrate/20150211174341_allow_null_in_services_project_id.rb b/db/migrate/20150211174341_allow_null_in_services_project_id.rb new file mode 100644 index 0000000000..68f0281279 --- /dev/null +++ b/db/migrate/20150211174341_allow_null_in_services_project_id.rb @@ -0,0 +1,5 @@ +class AllowNullInServicesProjectId < ActiveRecord::Migration + def change + change_column :services, :project_id, :integer, null: true + end +end diff --git a/db/migrate/20150213104043_add_twitter_sharing_enabled_to_application_settings.rb b/db/migrate/20150213104043_add_twitter_sharing_enabled_to_application_settings.rb new file mode 100644 index 0000000000..a043917239 --- /dev/null +++ b/db/migrate/20150213104043_add_twitter_sharing_enabled_to_application_settings.rb @@ -0,0 +1,5 @@ +class AddTwitterSharingEnabledToApplicationSettings < ActiveRecord::Migration + def change + add_column :application_settings, :twitter_sharing_enabled, :boolean, default: true + end +end diff --git a/db/migrate/20150213114800_add_hide_no_password_to_user.rb b/db/migrate/20150213114800_add_hide_no_password_to_user.rb new file mode 100644 index 0000000000..685f084427 --- /dev/null +++ b/db/migrate/20150213114800_add_hide_no_password_to_user.rb @@ -0,0 +1,5 @@ +class AddHideNoPasswordToUser < ActiveRecord::Migration + def change + add_column :users, :hide_no_password, :boolean, default: false + end +end diff --git a/db/migrate/20150213121042_add_password_automatically_set_to_user.rb b/db/migrate/20150213121042_add_password_automatically_set_to_user.rb new file mode 100644 index 0000000000..c3c7c1ffc7 --- /dev/null +++ b/db/migrate/20150213121042_add_password_automatically_set_to_user.rb @@ -0,0 +1,5 @@ +class AddPasswordAutomaticallySetToUser < ActiveRecord::Migration + def change + add_column :users, :password_automatically_set, :boolean, default: false + end +end diff --git a/db/migrate/20150217123345_add_bitbucket_access_token_and_secret_to_user.rb b/db/migrate/20150217123345_add_bitbucket_access_token_and_secret_to_user.rb new file mode 100644 index 0000000000..23ac1b399e --- /dev/null +++ b/db/migrate/20150217123345_add_bitbucket_access_token_and_secret_to_user.rb @@ -0,0 +1,6 @@ +class AddBitbucketAccessTokenAndSecretToUser < ActiveRecord::Migration + def change + add_column :users, :bitbucket_access_token, :string + add_column :users, :bitbucket_access_token_secret, :string + end +end diff --git a/db/migrate/20150219004514_add_events_to_services.rb b/db/migrate/20150219004514_add_events_to_services.rb new file mode 100644 index 0000000000..cf73a0174f --- /dev/null +++ b/db/migrate/20150219004514_add_events_to_services.rb @@ -0,0 +1,8 @@ +class AddEventsToServices < ActiveRecord::Migration + def change + add_column :services, :push_events, :boolean, :default => true + add_column :services, :issues_events, :boolean, :default => true + add_column :services, :merge_requests_events, :boolean, :default => true + add_column :services, :tag_push_events, :boolean, :default => true + end +end diff --git a/db/migrate/20150223022001_set_missing_last_activity_at.rb b/db/migrate/20150223022001_set_missing_last_activity_at.rb new file mode 100644 index 0000000000..3f6d4d8347 --- /dev/null +++ b/db/migrate/20150223022001_set_missing_last_activity_at.rb @@ -0,0 +1,8 @@ +class SetMissingLastActivityAt < ActiveRecord::Migration + def up + execute "UPDATE projects SET last_activity_at = updated_at WHERE last_activity_at IS NULL" + end + + def down + end +end diff --git a/db/migrate/20150225065047_add_note_events_to_services.rb b/db/migrate/20150225065047_add_note_events_to_services.rb new file mode 100644 index 0000000000..d54ba9e482 --- /dev/null +++ b/db/migrate/20150225065047_add_note_events_to_services.rb @@ -0,0 +1,5 @@ +class AddNoteEventsToServices < ActiveRecord::Migration + def change + add_column :services, :note_events, :boolean, default: true, null: false + end +end diff --git a/db/migrate/20150301014758_add_restricted_visibility_levels_to_application_settings.rb b/db/migrate/20150301014758_add_restricted_visibility_levels_to_application_settings.rb new file mode 100644 index 0000000000..494c3033bf --- /dev/null +++ b/db/migrate/20150301014758_add_restricted_visibility_levels_to_application_settings.rb @@ -0,0 +1,5 @@ +class AddRestrictedVisibilityLevelsToApplicationSettings < ActiveRecord::Migration + def change + add_column :application_settings, :restricted_visibility_levels, :text + end +end diff --git a/db/migrate/20150306023106_fix_namespace_duplication.rb b/db/migrate/20150306023106_fix_namespace_duplication.rb new file mode 100644 index 0000000000..334e557455 --- /dev/null +++ b/db/migrate/20150306023106_fix_namespace_duplication.rb @@ -0,0 +1,21 @@ +class FixNamespaceDuplication < ActiveRecord::Migration + def up + #fixes path duplication + select_all('SELECT MAX(id) max, COUNT(id) cnt, path FROM namespaces GROUP BY path HAVING COUNT(id) > 1').each do |nms| + bad_nms_ids = select_all("SELECT id FROM namespaces WHERE path = '#{nms['path']}' AND id <> #{nms['max']}").map{|x| x["id"]} + execute("UPDATE projects SET namespace_id = #{nms["max"]} WHERE namespace_id IN(#{bad_nms_ids.join(', ')})") + execute("DELETE FROM namespaces WHERE id IN(#{bad_nms_ids.join(', ')})") + end + + #fixes name duplication + select_all('SELECT MAX(id) max, COUNT(id) cnt, name FROM namespaces GROUP BY name HAVING COUNT(id) > 1').each do |nms| + bad_nms_ids = select_all("SELECT id FROM namespaces WHERE name = '#{nms['name']}' AND id <> #{nms['max']}").map{|x| x["id"]} + execute("UPDATE projects SET namespace_id = #{nms["max"]} WHERE namespace_id IN(#{bad_nms_ids.join(', ')})") + execute("DELETE FROM namespaces WHERE id IN(#{bad_nms_ids.join(', ')})") + end + end + + def down + # not implemented + end +end diff --git a/db/migrate/20150306023112_add_unique_index_to_namespace.rb b/db/migrate/20150306023112_add_unique_index_to_namespace.rb new file mode 100644 index 0000000000..6472138e3e --- /dev/null +++ b/db/migrate/20150306023112_add_unique_index_to_namespace.rb @@ -0,0 +1,9 @@ +class AddUniqueIndexToNamespace < ActiveRecord::Migration + def change + remove_index :namespaces, column: :name if index_exists?(:namespaces, :name) + remove_index :namespaces, column: :path if index_exists?(:namespaces, :path) + + add_index :namespaces, :name, unique: true + add_index :namespaces, :path, unique: true + end +end diff --git a/db/migrate/20150313012111_create_subscriptions_table.rb b/db/migrate/20150313012111_create_subscriptions_table.rb new file mode 100644 index 0000000000..a1d4d9dedc --- /dev/null +++ b/db/migrate/20150313012111_create_subscriptions_table.rb @@ -0,0 +1,16 @@ +class CreateSubscriptionsTable < ActiveRecord::Migration + def change + create_table :subscriptions do |t| + t.integer :user_id + t.references :subscribable, polymorphic: true + t.boolean :subscribed + + t.timestamps + end + + add_index :subscriptions, + [:subscribable_id, :subscribable_type, :user_id], + unique: true, + name: 'subscriptions_user_id_and_ref_fields' + end +end diff --git a/db/migrate/20150320234437_add_location_to_user.rb b/db/migrate/20150320234437_add_location_to_user.rb new file mode 100644 index 0000000000..32731d37d7 --- /dev/null +++ b/db/migrate/20150320234437_add_location_to_user.rb @@ -0,0 +1,5 @@ +class AddLocationToUser < ActiveRecord::Migration + def change + add_column :users, :location, :string + end +end diff --git a/db/migrate/20150324155957_set_incorrect_assignee_id_to_null.rb b/db/migrate/20150324155957_set_incorrect_assignee_id_to_null.rb new file mode 100644 index 0000000000..42dc8173e4 --- /dev/null +++ b/db/migrate/20150324155957_set_incorrect_assignee_id_to_null.rb @@ -0,0 +1,6 @@ +class SetIncorrectAssigneeIdToNull < ActiveRecord::Migration + def up + execute "UPDATE issues SET assignee_id = NULL WHERE assignee_id = -1" + execute "UPDATE merge_requests SET assignee_id = NULL WHERE assignee_id = -1" + end +end diff --git a/db/migrate/20150327122227_add_public_to_key.rb b/db/migrate/20150327122227_add_public_to_key.rb new file mode 100644 index 0000000000..6ffbf4cda1 --- /dev/null +++ b/db/migrate/20150327122227_add_public_to_key.rb @@ -0,0 +1,5 @@ +class AddPublicToKey < ActiveRecord::Migration + def change + add_column :keys, :public, :boolean, default: false, null: false + end +end diff --git a/db/migrate/20150327150017_add_import_data_to_project.rb b/db/migrate/20150327150017_add_import_data_to_project.rb new file mode 100644 index 0000000000..12c00339ee --- /dev/null +++ b/db/migrate/20150327150017_add_import_data_to_project.rb @@ -0,0 +1,5 @@ +class AddImportDataToProject < ActiveRecord::Migration + def change + add_column :projects, :import_data, :text + end +end diff --git a/db/migrate/20150328132231_add_max_attachment_size_to_application_settings.rb b/db/migrate/20150328132231_add_max_attachment_size_to_application_settings.rb new file mode 100644 index 0000000000..1d161674a9 --- /dev/null +++ b/db/migrate/20150328132231_add_max_attachment_size_to_application_settings.rb @@ -0,0 +1,5 @@ +class AddMaxAttachmentSizeToApplicationSettings < ActiveRecord::Migration + def change + add_column :application_settings, :max_attachment_size, :integer, default: 10, null: false + end +end diff --git a/db/migrate/20150406133311_add_invite_data_to_member.rb b/db/migrate/20150406133311_add_invite_data_to_member.rb new file mode 100644 index 0000000000..3452fd45c4 --- /dev/null +++ b/db/migrate/20150406133311_add_invite_data_to_member.rb @@ -0,0 +1,12 @@ +class AddInviteDataToMember < ActiveRecord::Migration + def change + add_column :members, :created_by_id, :integer + add_column :members, :invite_email, :string + add_column :members, :invite_token, :string + add_column :members, :invite_accepted_at, :datetime + + change_column :members, :user_id, :integer, null: true + + add_index :members, :invite_token, unique: true + end +end diff --git a/db/migrate/20150411000035_fix_identities.rb b/db/migrate/20150411000035_fix_identities.rb new file mode 100644 index 0000000000..d9051f9fff --- /dev/null +++ b/db/migrate/20150411000035_fix_identities.rb @@ -0,0 +1,45 @@ +class FixIdentities < ActiveRecord::Migration + def up + # Up until now, legacy 'ldap' references in the database were charitably + # interpreted to point to the first LDAP server specified in the GitLab + # configuration. So if the database said 'provider: ldap' but the first + # LDAP server was called 'ldapmain', then we would try to interpret + # 'provider: ldap' as if it said 'provider: ldapmain'. This migration (and + # accompanying changes in the GitLab LDAP code) get rid of this complicated + # behavior. Any database references to 'provider: ldap' get rewritten to + # whatever the code would have interpreted it as, i.e. as a reference to + # the first LDAP server specified in gitlab.yml / gitlab.rb. + new_provider = if Gitlab.config.ldap.enabled + first_ldap_server = Gitlab.config.ldap.servers.values.first + first_ldap_server['provider_name'] + else + 'ldapmain' + end + + # Delete duplicate identities + # We use a sort of self-join to find rows in identities which match on + # user_id but where one has provider 'ldap'. We delete the duplicate row + # with provider 'ldap'. + delete_statement = '' + case adapter_name.downcase + when /^mysql/ + delete_statement << 'DELETE FROM id1 USING identities AS id1, identities AS id2' + when 'postgresql' + delete_statement << 'DELETE FROM identities AS id1 USING identities AS id2' + else + raise "Unknown DB adapter: #{adapter_name}" + end + delete_statement << " WHERE id1.user_id = id2.user_id AND id1.provider = 'ldap' AND id2.provider = '#{new_provider}'" + execute delete_statement + + # Update legacy identities + execute "UPDATE identities SET provider = '#{new_provider}' WHERE provider = 'ldap'" + + if table_exists?('ldap_group_links') + execute "UPDATE ldap_group_links SET provider = '#{new_provider}' WHERE provider IS NULL OR provider = 'ldap'" + end + end + + def down + end +end diff --git a/db/migrate/20150411180045_rename_buildbox_service.rb b/db/migrate/20150411180045_rename_buildbox_service.rb new file mode 100644 index 0000000000..5a0b5d07e5 --- /dev/null +++ b/db/migrate/20150411180045_rename_buildbox_service.rb @@ -0,0 +1,9 @@ +class RenameBuildboxService < ActiveRecord::Migration + def up + execute "UPDATE services SET type = 'BuildkiteService' WHERE type = 'BuildboxService';" + end + + def down + execute "UPDATE services SET type = 'BuildboxService' WHERE type = 'BuildkiteService';" + end +end diff --git a/db/migrate/20150413192223_add_public_email_to_users.rb b/db/migrate/20150413192223_add_public_email_to_users.rb new file mode 100644 index 0000000000..700e9f343a --- /dev/null +++ b/db/migrate/20150413192223_add_public_email_to_users.rb @@ -0,0 +1,5 @@ +class AddPublicEmailToUsers < ActiveRecord::Migration + def change + add_column :users, :public_email, :string, default: "", null: false + end +end diff --git a/db/migrate/20150417121913_create_project_import_data.rb b/db/migrate/20150417121913_create_project_import_data.rb new file mode 100644 index 0000000000..c78f5fde85 --- /dev/null +++ b/db/migrate/20150417121913_create_project_import_data.rb @@ -0,0 +1,8 @@ +class CreateProjectImportData < ActiveRecord::Migration + def change + create_table :project_import_data do |t| + t.references :project + t.text :data + end + end +end diff --git a/db/migrate/20150417122318_remove_import_data_from_project.rb b/db/migrate/20150417122318_remove_import_data_from_project.rb new file mode 100644 index 0000000000..c275b49d22 --- /dev/null +++ b/db/migrate/20150417122318_remove_import_data_from_project.rb @@ -0,0 +1,5 @@ +class RemoveImportDataFromProject < ActiveRecord::Migration + def change + remove_column :projects, :import_data + end +end diff --git a/db/schema.rb b/db/schema.rb index 9159556ac7..1aee37b2e6 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,11 +11,26 @@ # # It's strongly recommended that you check this file into your version control system. -ActiveRecord::Schema.define(version: 20140730111702) do +ActiveRecord::Schema.define(version: 20150417122318) do # These are extensions that must be enabled in order to support this database enable_extension "plpgsql" + create_table "application_settings", force: true do |t| + t.integer "default_projects_limit" + t.boolean "signup_enabled" + t.boolean "signin_enabled" + t.boolean "gravatar_enabled" + t.text "sign_in_text" + t.datetime "created_at" + t.datetime "updated_at" + t.string "home_page_url" + t.integer "default_branch_protection", default: 2 + t.boolean "twitter_sharing_enabled", default: true + t.text "restricted_visibility_levels" + t.integer "max_attachment_size", default: 10, null: false + end + create_table "broadcast_messages", force: true do |t| t.text "message", null: false t.datetime "starts_at" @@ -74,6 +89,17 @@ ActiveRecord::Schema.define(version: 20140730111702) do add_index "forked_project_links", ["forked_to_project_id"], name: "index_forked_project_links_on_forked_to_project_id", unique: true, using: :btree + create_table "identities", force: true do |t| + t.string "extern_uid" + t.string "provider" + t.integer "user_id" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "identities", ["created_at", "id"], name: "index_identities_on_created_at_and_id", using: :btree + add_index "identities", ["user_id"], name: "index_identities_on_user_id", using: :btree + create_table "issues", force: true do |t| t.string "title" t.integer "assignee_id" @@ -91,6 +117,7 @@ ActiveRecord::Schema.define(version: 20140730111702) do add_index "issues", ["assignee_id"], name: "index_issues_on_assignee_id", using: :btree add_index "issues", ["author_id"], name: "index_issues_on_author_id", using: :btree + add_index "issues", ["created_at", "id"], name: "index_issues_on_created_at_and_id", using: :btree add_index "issues", ["created_at"], name: "index_issues_on_created_at", using: :btree add_index "issues", ["milestone_id"], name: "index_issues_on_milestone_id", using: :btree add_index "issues", ["project_id", "iid"], name: "index_issues_on_project_id_and_iid", unique: true, using: :btree @@ -105,8 +132,10 @@ ActiveRecord::Schema.define(version: 20140730111702) do t.string "title" t.string "type" t.string "fingerprint" + t.boolean "public", default: false, null: false end + add_index "keys", ["created_at", "id"], name: "index_keys_on_created_at_and_id", using: :btree add_index "keys", ["user_id"], name: "index_keys_on_user_id", using: :btree create_table "label_links", force: true do |t| @@ -130,6 +159,28 @@ ActiveRecord::Schema.define(version: 20140730111702) do add_index "labels", ["project_id"], name: "index_labels_on_project_id", using: :btree + create_table "members", force: true do |t| + t.integer "access_level", null: false + t.integer "source_id", null: false + t.string "source_type", null: false + t.integer "user_id" + t.integer "notification_level", null: false + t.string "type" + t.datetime "created_at" + t.datetime "updated_at" + t.integer "created_by_id" + t.string "invite_email" + t.string "invite_token" + t.datetime "invite_accepted_at" + end + + add_index "members", ["access_level"], name: "index_members_on_access_level", using: :btree + add_index "members", ["created_at", "id"], name: "index_members_on_created_at_and_id", using: :btree + add_index "members", ["invite_token"], name: "index_members_on_invite_token", unique: true, using: :btree + add_index "members", ["source_id", "source_type"], name: "index_members_on_source_id_and_source_type", using: :btree + add_index "members", ["type"], name: "index_members_on_type", using: :btree + add_index "members", ["user_id"], name: "index_members_on_user_id", using: :btree + create_table "merge_request_diffs", force: true do |t| t.string "state" t.text "st_commits" @@ -157,10 +208,12 @@ ActiveRecord::Schema.define(version: 20140730111702) do t.integer "iid" t.text "description" t.integer "position", default: 0 + t.datetime "locked_at" end add_index "merge_requests", ["assignee_id"], name: "index_merge_requests_on_assignee_id", using: :btree add_index "merge_requests", ["author_id"], name: "index_merge_requests_on_author_id", using: :btree + add_index "merge_requests", ["created_at", "id"], name: "index_merge_requests_on_created_at_and_id", using: :btree add_index "merge_requests", ["created_at"], name: "index_merge_requests_on_created_at", using: :btree add_index "merge_requests", ["milestone_id"], name: "index_merge_requests_on_milestone_id", using: :btree add_index "merge_requests", ["source_branch"], name: "index_merge_requests_on_source_branch", using: :btree @@ -180,6 +233,7 @@ ActiveRecord::Schema.define(version: 20140730111702) do t.integer "iid" end + add_index "milestones", ["created_at", "id"], name: "index_milestones_on_created_at_and_id", using: :btree add_index "milestones", ["due_date"], name: "index_milestones_on_due_date", using: :btree add_index "milestones", ["project_id", "iid"], name: "index_milestones_on_project_id_and_iid", unique: true, using: :btree add_index "milestones", ["project_id"], name: "index_milestones_on_project_id", using: :btree @@ -195,9 +249,10 @@ ActiveRecord::Schema.define(version: 20140730111702) do t.string "avatar" end - add_index "namespaces", ["name"], name: "index_namespaces_on_name", using: :btree + add_index "namespaces", ["created_at", "id"], name: "index_namespaces_on_created_at_and_id", using: :btree + add_index "namespaces", ["name"], name: "index_namespaces_on_name", unique: true, using: :btree add_index "namespaces", ["owner_id"], name: "index_namespaces_on_owner_id", using: :btree - add_index "namespaces", ["path"], name: "index_namespaces_on_path", using: :btree + add_index "namespaces", ["path"], name: "index_namespaces_on_path", unique: true, using: :btree add_index "namespaces", ["type"], name: "index_namespaces_on_type", using: :btree create_table "notes", force: true do |t| @@ -217,6 +272,7 @@ ActiveRecord::Schema.define(version: 20140730111702) do add_index "notes", ["author_id"], name: "index_notes_on_author_id", using: :btree add_index "notes", ["commit_id"], name: "index_notes_on_commit_id", using: :btree + add_index "notes", ["created_at", "id"], name: "index_notes_on_created_at_and_id", using: :btree add_index "notes", ["created_at"], name: "index_notes_on_created_at", using: :btree add_index "notes", ["noteable_id", "noteable_type"], name: "index_notes_on_noteable_id_and_noteable_type", using: :btree add_index "notes", ["noteable_type"], name: "index_notes_on_noteable_type", using: :btree @@ -224,6 +280,54 @@ ActiveRecord::Schema.define(version: 20140730111702) do add_index "notes", ["project_id"], name: "index_notes_on_project_id", using: :btree add_index "notes", ["updated_at"], name: "index_notes_on_updated_at", using: :btree + create_table "oauth_access_grants", force: true do |t| + t.integer "resource_owner_id", null: false + t.integer "application_id", null: false + t.string "token", null: false + t.integer "expires_in", null: false + t.text "redirect_uri", null: false + t.datetime "created_at", null: false + t.datetime "revoked_at" + t.string "scopes" + end + + add_index "oauth_access_grants", ["token"], name: "index_oauth_access_grants_on_token", unique: true, using: :btree + + create_table "oauth_access_tokens", force: true do |t| + t.integer "resource_owner_id" + t.integer "application_id" + t.string "token", null: false + t.string "refresh_token" + t.integer "expires_in" + t.datetime "revoked_at" + t.datetime "created_at", null: false + t.string "scopes" + end + + add_index "oauth_access_tokens", ["refresh_token"], name: "index_oauth_access_tokens_on_refresh_token", unique: true, using: :btree + add_index "oauth_access_tokens", ["resource_owner_id"], name: "index_oauth_access_tokens_on_resource_owner_id", using: :btree + add_index "oauth_access_tokens", ["token"], name: "index_oauth_access_tokens_on_token", unique: true, using: :btree + + create_table "oauth_applications", force: true do |t| + t.string "name", null: false + t.string "uid", null: false + t.string "secret", null: false + t.text "redirect_uri", null: false + t.string "scopes", default: "", null: false + t.datetime "created_at" + t.datetime "updated_at" + t.integer "owner_id" + t.string "owner_type" + end + + add_index "oauth_applications", ["owner_id", "owner_type"], name: "index_oauth_applications_on_owner_id_and_owner_type", using: :btree + add_index "oauth_applications", ["uid"], name: "index_oauth_applications_on_uid", unique: true, using: :btree + + create_table "project_import_data", force: true do |t| + t.integer "project_id" + t.text "data" + end + create_table "projects", force: true do |t| t.string "name" t.string "path" @@ -243,21 +347,26 @@ ActiveRecord::Schema.define(version: 20140730111702) do t.string "import_url" t.integer "visibility_level", default: 0, null: false t.boolean "archived", default: false, null: false + t.string "avatar" t.string "import_status" t.float "repository_size", default: 0.0 t.integer "star_count", default: 0, null: false + t.string "import_type" + t.string "import_source" end + add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree add_index "projects", ["creator_id"], name: "index_projects_on_creator_id", using: :btree add_index "projects", ["last_activity_at"], name: "index_projects_on_last_activity_at", using: :btree add_index "projects", ["namespace_id"], name: "index_projects_on_namespace_id", using: :btree add_index "projects", ["star_count"], name: "index_projects_on_star_count", using: :btree create_table "protected_branches", force: true do |t| - t.integer "project_id", null: false - t.string "name", null: false + t.integer "project_id", null: false + t.string "name", null: false t.datetime "created_at" t.datetime "updated_at" + t.boolean "developers_can_push", default: false, null: false end add_index "protected_branches", ["project_id"], name: "index_protected_branches_on_project_id", using: :btree @@ -265,37 +374,52 @@ ActiveRecord::Schema.define(version: 20140730111702) do create_table "services", force: true do |t| t.string "type" t.string "title" - t.string "token" - t.integer "project_id", null: false + t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" - t.boolean "active", default: false, null: false - t.string "project_url" - t.string "subdomain" - t.string "room" - t.text "recipients" - t.string "api_key" + t.boolean "active", default: false, null: false + t.text "properties" + t.boolean "template", default: false + t.boolean "push_events", default: true + t.boolean "issues_events", default: true + t.boolean "merge_requests_events", default: true + t.boolean "tag_push_events", default: true + t.boolean "note_events", default: true, null: false end + add_index "services", ["created_at", "id"], name: "index_services_on_created_at_and_id", using: :btree add_index "services", ["project_id"], name: "index_services_on_project_id", using: :btree create_table "snippets", force: true do |t| t.string "title" t.text "content" - t.integer "author_id", null: false + t.integer "author_id", null: false t.integer "project_id" t.datetime "created_at" t.datetime "updated_at" t.string "file_name" t.datetime "expires_at" - t.boolean "private", default: true, null: false t.string "type" + t.integer "visibility_level", default: 0, null: false end add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree + add_index "snippets", ["created_at", "id"], name: "index_snippets_on_created_at_and_id", using: :btree add_index "snippets", ["created_at"], name: "index_snippets_on_created_at", using: :btree add_index "snippets", ["expires_at"], name: "index_snippets_on_expires_at", using: :btree add_index "snippets", ["project_id"], name: "index_snippets_on_project_id", using: :btree + add_index "snippets", ["visibility_level"], name: "index_snippets_on_visibility_level", using: :btree + + create_table "subscriptions", force: true do |t| + t.integer "user_id" + t.integer "subscribable_id" + t.string "subscribable_type" + t.boolean "subscribed" + t.datetime "created_at" + t.datetime "updated_at" + end + + add_index "subscriptions", ["subscribable_id", "subscribable_type", "user_id"], name: "subscriptions_user_id_and_ref_fields", unique: true, using: :btree create_table "taggings", force: true do |t| t.integer "tag_id" @@ -315,12 +439,12 @@ ActiveRecord::Schema.define(version: 20140730111702) do end create_table "users", force: true do |t| - t.string "email", default: "", null: false - t.string "encrypted_password", default: "", null: false + t.string "email", default: "", null: false + t.string "encrypted_password", default: "", null: false t.string "reset_password_token" t.datetime "reset_password_sent_at" t.datetime "remember_created_at" - t.integer "sign_in_count", default: 0 + t.integer "sign_in_count", default: 0 t.datetime "current_sign_in_at" t.datetime "last_sign_in_at" t.string "current_sign_in_ip" @@ -328,70 +452,53 @@ ActiveRecord::Schema.define(version: 20140730111702) do t.datetime "created_at" t.datetime "updated_at" t.string "name" - t.boolean "admin", default: false, null: false - t.integer "projects_limit", default: 10 - t.string "skype", default: "", null: false - t.string "linkedin", default: "", null: false - t.string "twitter", default: "", null: false + t.boolean "admin", default: false, null: false + t.integer "projects_limit", default: 10 + t.string "skype", default: "", null: false + t.string "linkedin", default: "", null: false + t.string "twitter", default: "", null: false t.string "authentication_token" - t.integer "theme_id", default: 1, null: false + t.integer "theme_id", default: 1, null: false t.string "bio" - t.integer "failed_attempts", default: 0 + t.integer "failed_attempts", default: 0 t.datetime "locked_at" - t.string "extern_uid" - t.string "provider" t.string "username" - t.boolean "can_create_group", default: true, null: false - t.boolean "can_create_team", default: true, null: false + t.boolean "can_create_group", default: true, null: false + t.boolean "can_create_team", default: true, null: false t.string "state" - t.integer "color_scheme_id", default: 1, null: false - t.integer "notification_level", default: 1, null: false + t.integer "color_scheme_id", default: 1, null: false + t.integer "notification_level", default: 1, null: false t.datetime "password_expires_at" t.integer "created_by_id" + t.datetime "last_credential_check_at" t.string "avatar" t.string "confirmation_token" t.datetime "confirmed_at" t.datetime "confirmation_sent_at" t.string "unconfirmed_email" - t.boolean "hide_no_ssh_key", default: false - t.string "website_url", default: "", null: false - t.datetime "last_credential_check_at" + t.boolean "hide_no_ssh_key", default: false + t.string "website_url", default: "", null: false + t.string "github_access_token" + t.string "gitlab_access_token" + t.string "notification_email" + t.boolean "hide_no_password", default: false + t.boolean "password_automatically_set", default: false + t.string "bitbucket_access_token" + t.string "bitbucket_access_token_secret" + t.string "location" + t.string "public_email", default: "", null: false end add_index "users", ["admin"], name: "index_users_on_admin", using: :btree add_index "users", ["authentication_token"], name: "index_users_on_authentication_token", unique: true, using: :btree add_index "users", ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true, using: :btree + add_index "users", ["created_at", "id"], name: "index_users_on_created_at_and_id", using: :btree add_index "users", ["current_sign_in_at"], name: "index_users_on_current_sign_in_at", using: :btree add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree - add_index "users", ["extern_uid", "provider"], name: "index_users_on_extern_uid_and_provider", unique: true, using: :btree add_index "users", ["name"], name: "index_users_on_name", using: :btree add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true, using: :btree add_index "users", ["username"], name: "index_users_on_username", using: :btree - create_table "users_groups", force: true do |t| - t.integer "group_access", null: false - t.integer "group_id", null: false - t.integer "user_id", null: false - t.datetime "created_at" - t.datetime "updated_at" - t.integer "notification_level", default: 3, null: false - end - - add_index "users_groups", ["user_id"], name: "index_users_groups_on_user_id", using: :btree - - create_table "users_projects", force: true do |t| - t.integer "user_id", null: false - t.integer "project_id", null: false - t.datetime "created_at" - t.datetime "updated_at" - t.integer "project_access", default: 0, null: false - t.integer "notification_level", default: 3, null: false - end - - add_index "users_projects", ["project_access"], name: "index_users_projects_on_project_access", using: :btree - add_index "users_projects", ["project_id"], name: "index_users_projects_on_project_id", using: :btree - add_index "users_projects", ["user_id"], name: "index_users_projects_on_user_id", using: :btree - create_table "users_star_projects", force: true do |t| t.integer "project_id", null: false t.integer "user_id", null: false @@ -416,6 +523,7 @@ ActiveRecord::Schema.define(version: 20140730111702) do t.boolean "tag_push_events", default: false end + add_index "web_hooks", ["created_at", "id"], name: "index_web_hooks_on_created_at_and_id", using: :btree add_index "web_hooks", ["project_id"], name: "index_web_hooks_on_project_id", using: :btree end diff --git a/doc/README.md b/doc/README.md index f05078ee38..4e00dceac2 100644 --- a/doc/README.md +++ b/doc/README.md @@ -2,22 +2,30 @@ ## User documentation -- [API](api/README.md) Explore how you can access GitLab via a simple and powerful API. -- [Markdown](markdown/markdown.md) Learn what you can do with GitLab's advanced formatting system. +- [API](api/README.md) Automate GitLab via a simple and powerful API. +- [Markdown](markdown/markdown.md) GitLab's advanced formatting system. - [Permissions](permissions/permissions.md) Learn what each role in a project (guest/reporter/developer/master/owner) can do. -- [Public access](public_access/public_access.md) Learn how you can allow public and internal access to a project. +- [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. - [SSH](ssh/README.md) Setup your ssh keys and deploy keys for secure access to your projects. - [Web hooks](web_hooks/web_hooks.md) Let GitLab notify you when new code has been pushed to your project. -- [Workflow](workflow/README.md) Learn how to use Git and GitLab together. +- [Workflow](workflow/README.md) Using GitLab functionality and importing projects from GitHub and SVN. +- [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab. ## Administrator documentation -- [Install](install/README.md) Requirements, directory structures and manual installation. +- [Install](install/README.md) Requirements, directory structures and installation from source. - [Integration](integration/README.md) How to integrate with systems such as JIRA, Redmine, LDAP and Twitter. -- [Raketasks](raketasks/README.md) Explore what GitLab has in store for you to make administration easier. -- [System hooks](system_hooks/system_hooks.md) Let GitLab notify you when certain management tasks need to be carried out. +- [Raketasks](raketasks/README.md) Backups, maintenance, automatic web hook setup and the importing of projects. +- [Custom git hooks](hooks/custom_hooks.md) Custom git hooks (on the filesystem) for when web hooks aren't enough. +- [System hooks](system_hooks/system_hooks.md) Notifications when users, projects and keys are changed. - [Security](security/README.md) Learn what you can do to further secure your GitLab instance. - [Update](update/README.md) Update guides to upgrade your installation. +- [Welcome message](customization/welcome_message.md) Add a custom welcome message to the sign-in page. +- [Issue closing](customization/issue_closing.md) Customize how to close an issue from commit messages. +- [Libravatar](customization/libravatar.md) Use Libravatar for user avatars. +- [Operations](operations/README.md) Keeping GitLab up and running +- [Log system](logs/logs.md) Log system ## Contributor documentation diff --git a/doc/api/README.md b/doc/api/README.md index ababb7b699..dec530d0b8 100644 --- a/doc/api/README.md +++ b/doc/api/README.md @@ -21,13 +21,8 @@ ## Clients -- [php-gitlab-api](https://github.com/m4tthumphrey/php-gitlab-api) - PHP -- [Laravel API Wrapper for GitLab CE](https://github.com/adamgoose/gitlab) - PHP / [Laravel](http://laravel.com) -- [Ruby Wrapper](https://github.com/NARKOZ/gitlab) - Ruby -- [python-gitlab](https://github.com/Itxaka/python-gitlab) - Python -- [java-gitlab-api](https://github.com/timols/java-gitlab-api) - Java -- [node-gitlab](https://github.com/moul/node-gitlab) - Node.js -- [NGitLab](https://github.com/Scooletz/NGitLab) - .NET +Find API Clients for GitLab [on our website](https://about.gitlab.com/applications/#api-clients). +You can use [GitLab as an OAuth2 client](oauth2.md) to make API calls. ## Introduction @@ -57,6 +52,24 @@ curl --header "PRIVATE-TOKEN: QVy1PB7sTxfy4pqfZM1U" "http://example.com/api/v3/p The API uses JSON to serialize data. You don't need to specify `.json` at the end of API URL. +## Authentication with OAuth2 token + +Instead of the private_token you can transmit the OAuth2 access token as a header or as a parameter. + +### OAuth2 token (as a parameter) + +``` +curl https://localhost:3000/api/v3/user?access_token=OAUTH-TOKEN +``` + +### OAuth2 token (as a header) + +``` +curl -H "Authorization: Bearer OAUTH-TOKEN" https://localhost:3000/api/v3/user +``` + +Read more about [GitLab as an OAuth2 client](oauth2.md). + ## Status codes The API is designed to return different status codes according to context and action. In this way if a request results in an error the caller is able to get insight into what went wrong, e.g. status code `400 Bad Request` is returned if a required attribute is missing from the request. The following list gives an overview of how the API functions generally behave. @@ -80,11 +93,12 @@ Return values: - `404 Not Found` - A resource could not be accessed, e.g. an ID for a resource could not be found - `405 Method Not Allowed` - The request is not supported - `409 Conflict` - A conflicting resource already exists, e.g. creating a project with a name that already exists +- `422 Unprocessable` - The entity could not be processed - `500 Server Error` - While handling the request something went wrong on the server side ## Sudo -All API requests support performing an api call as if you were another user, if your private token is for an administration account. You need to pass `sudo` parameter by url or header with an id or username of the user you want to perform the operation as. If passed as header, the header name must be "SUDO" (capitals). +All API requests support performing an api call as if you were another user, if your private token is for an administration account. You need to pass `sudo` parameter by URL or header with an id or username of the user you want to perform the operation as. If passed as header, the header name must be "SUDO" (capitals). If a non administrative `private_token` is provided then an error message will be returned with status code 403: @@ -129,7 +143,7 @@ When listing resources you can pass the following parameters: - `page` (default: `1`) - page number - `per_page` (default: `20`, max: `100`) - number of items to list per page -[Link headers](http://www.w3.org/wiki/LinkHeader) are send back with each response. These have `rel` prev/next/first/last and contain the relevant URL. Please use these instead of generating your own urls. +[Link headers](http://www.w3.org/wiki/LinkHeader) are send back with each response. These have `rel` prev/next/first/last and contain the relevant URL. Please use these instead of generating your own URLs. ## id vs iid @@ -144,3 +158,52 @@ Issue: - iid - is unique only in scope of a single project. When you browse issues or merge requests with Web UI, you see iid. So if you want to get issue with api you use `http://host/api/v3/.../issues/:id.json`. But when you want to create a link to web page - use `http:://host/project/issues/:iid.json` + +## Data validation and error reporting + +When working with the API you may encounter validation errors. In such case, the API will answer with an HTTP `400` status. +Such errors appear in two cases: + +* A required attribute of the API request is missing, e.g. the title of an issue is not given +* An attribute did not pass the validation, e.g. user bio is too long + +When an attribute is missing, you will get something like: + + HTTP/1.1 400 Bad Request + Content-Type: application/json + + { + "message":"400 (Bad request) \"title\" not given" + } + +When a validation error occurs, error messages will be different. They will hold all details of validation errors: + + HTTP/1.1 400 Bad Request + Content-Type: application/json + + { + "message": { + "bio": [ + "is too long (maximum is 255 characters)" + ] + } + } + +This makes error messages more machine-readable. The format can be described as follow: + + { + "message": { + "": [ + "", + "", + ... + ], + "": { + "": [ + "", + "", + ... + ], + } + } + } diff --git a/doc/api/branches.md b/doc/api/branches.md index 31469b6fe9..6a9c10c852 100644 --- a/doc/api/branches.md +++ b/doc/api/branches.md @@ -15,27 +15,20 @@ Parameters: ```json [ { - "name": "master", "commit": { - "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c", - "parents": [ - { - "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8" - } - ], - "tree": "46e82de44b1061621357f24c05515327f2795a95", - "message": "add projects API", - "author": { - "name": "John Smith", - "email": "john@example.com" - }, - "committer": { - "name": "John Smith", - "email": "john@example.com" - }, + "author_email": "john@example.com", + "author_name": "John Smith", "authored_date": "2012-06-27T05:51:39-07:00", - "committed_date": "2012-06-28T03:44:20-07:00" + "committed_date": "2012-06-28T03:44:20-07:00", + "committer_email": "john@example.com", + "committer_name": "John Smith", + "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c", + "message": "add projects API", + "parent_ids": [ + "4ad91d3c1144c406e50c7b33bae684bd6837faf8" + ] }, + "name": "master", "protected": true } ] @@ -56,27 +49,20 @@ Parameters: ```json { - "name": "master", "commit": { - "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c", - "parents": [ - { - "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8" - } - ], - "tree": "46e82de44b1061621357f24c05515327f2795a95", - "message": "add projects API", - "author": { - "name": "John Smith", - "email": "john@example.com" - }, - "committer": { - "name": "John Smith", - "email": "john@example.com" - }, + "author_email": "john@example.com", + "author_name": "John Smith", "authored_date": "2012-06-27T05:51:39-07:00", - "committed_date": "2012-06-28T03:44:20-07:00" + "committed_date": "2012-06-28T03:44:20-07:00", + "committer_email": "john@example.com", + "committer_name": "John Smith", + "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c", + "message": "add projects API", + "parent_ids": [ + "4ad91d3c1144c406e50c7b33bae684bd6837faf8" + ] }, + "name": "master", "protected": true } ``` @@ -97,27 +83,20 @@ Parameters: ```json { - "name": "master", "commit": { - "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c", - "parents": [ - { - "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8" - } - ], - "tree": "46e82de44b1061621357f24c05515327f2795a95", - "message": "add projects API", - "author": { - "name": "John Smith", - "email": "john@example.com" - }, - "committer": { - "name": "John Smith", - "email": "john@example.com" - }, + "author_email": "john@example.com", + "author_name": "John Smith", "authored_date": "2012-06-27T05:51:39-07:00", - "committed_date": "2012-06-28T03:44:20-07:00" + "committed_date": "2012-06-28T03:44:20-07:00", + "committer_email": "john@example.com", + "committer_name": "John Smith", + "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c", + "message": "add projects API", + "parent_ids": [ + "4ad91d3c1144c406e50c7b33bae684bd6837faf8" + ] }, + "name": "master", "protected": true } ``` @@ -138,27 +117,20 @@ Parameters: ```json { - "name": "master", "commit": { - "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c", - "parents": [ - { - "id": "4ad91d3c1144c406e50c7b33bae684bd6837faf8" - } - ], - "tree": "46e82de44b1061621357f24c05515327f2795a95", - "message": "add projects API", - "author": { - "name": "John Smith", - "email": "john@example.com" - }, - "committer": { - "name": "John Smith", - "email": "john@example.com" - }, + "author_email": "john@example.com", + "author_name": "John Smith", "authored_date": "2012-06-27T05:51:39-07:00", - "committed_date": "2012-06-28T03:44:20-07:00" + "committed_date": "2012-06-28T03:44:20-07:00", + "committer_email": "john@example.com", + "committer_name": "John Smith", + "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c", + "message": "add projects API", + "parent_ids": [ + "4ad91d3c1144c406e50c7b33bae684bd6837faf8" + ] }, + "name": "master", "protected": false } ``` @@ -177,25 +149,26 @@ Parameters: ```json { - "name": "my-new-branch", "commit": { - "id": "8848c0e90327a0b70f1865b843fb2fbfb9345e57", - "message": "Merge pull request #54 from brightbox/use_fog_brightbox_module\n\nUpdate to use fog-brightbox module", - "parent_ids": [ - "fff449e0bf453576f16c91d6544f00a2664009d8", - "f93a93626fec20fd659f4ed3ab2e64019b6169ae" - ], - "authored_date": "2014-02-20T19:54:55+02:00", - "author_name": "john smith", "author_email": "john@example.com", - "committed_date": "2014-02-20T19:54:55+02:00", - "committer_name": "john smith", - "committer_email": "john@example.com" + "author_name": "John Smith", + "authored_date": "2012-06-27T05:51:39-07:00", + "committed_date": "2012-06-28T03:44:20-07:00", + "committer_email": "john@example.com", + "committer_name": "John Smith", + "id": "7b5c3cc8be40ee161ae89a06bba6229da1032a0c", + "message": "add projects API", + "parent_ids": [ + "4ad91d3c1144c406e50c7b33bae684bd6837faf8" + ] }, + "name": "master", "protected": false } ``` +It return 200 if succeed or 400 if failed with error message explaining reason. + ## Delete repository branch ``` @@ -207,4 +180,13 @@ Parameters: - `id` (required) - The ID of a project - `branch` (required) - The name of the branch -It return 200 if succeed or 405 if failed with error message explaining reason. +It return 200 if succeed, 404 if the branch to be deleted does not exist +or 400 for other reasons. In case of an error, an explaining message is provided. + +Success response: + +```json +{ + "branch_name": "my-removed-branch" +} +``` diff --git a/doc/api/commits.md b/doc/api/commits.md index 9475ecbaa6..eb8d6a4359 100644 --- a/doc/api/commits.md +++ b/doc/api/commits.md @@ -93,3 +93,66 @@ Parameters: } ] ``` + +## Get the comments of a commit + +Get the comments of a commit in a project. + +``` +GET /projects/:id/repository/commits/:sha/comments +``` + +Parameters: + +- `id` (required) - The ID of a project +- `sha` (required) - The name of a repository branch or tag or if not given the default branch + +```json +[ + { + "note": "this code is really nice", + "author": { + "id": 11, + "username": "admin", + "email": "admin@local.host", + "name": "Administrator", + "state": "active", + "created_at": "2014-03-06T08:17:35.000Z" + } + } +] +``` + +## Post comment to commit + +Adds a comment to a commit. Optionally you can post comments on a specific line of a commit. Therefor both `path`, `line_new` and `line_old` are required. + +``` +POST /projects/:id/repository/commits/:sha/comments +``` + +Parameters: + +- `id` (required) - The ID of a project +- `sha` (required) - The name of a repository branch or tag or if not given the default branch +- `note` (required) - Text of comment +- `path` (optional) - The file path +- `line` (optional) - The line number +- `line_type` (optional) - The line type (new or old) + +```json +{ + "author": { + "id": 1, + "username": "admin", + "email": "admin@local.host", + "name": "Administrator", + "blocked": false, + "created_at": "2012-04-29T08:46:00Z" + }, + "note": "text1", + "path": "example.rb", + "line": 5, + "line_type": "new" +} +``` diff --git a/doc/api/groups.md b/doc/api/groups.md index 6b379b02d2..b5a4b05cca 100644 --- a/doc/api/groups.md +++ b/doc/api/groups.md @@ -14,11 +14,13 @@ GET /groups "id": 1, "name": "Foobar Group", "path": "foo-bar", - "owner_id": 18 + "description": "An interesting group" } ] ``` +You can search for groups by name or path, see below. + ## Details of a group Get all details of a group. @@ -29,7 +31,7 @@ GET /groups/:id Parameters: -- `id` (required) - The ID of a group +- `id` (required) - The ID or path of a group ## New group @@ -43,6 +45,7 @@ Parameters: - `name` (required) - The name of the group - `path` (required) - The path of the group +- `description` (optional) - The group's description ## Transfer project to group @@ -54,7 +57,7 @@ POST /groups/:id/projects/:project_id Parameters: -- `id` (required) - The ID of a group +- `id` (required) - The ID or path of a group - `project_id` (required) - The ID of a project ## Remove group @@ -67,7 +70,26 @@ DELETE /groups/:id Parameters: -- `id` (required) - The ID of a user group +- `id` (required) - The ID or path of a user group + +## Search for group + +Get all groups that match your string in their name or path. + +``` +GET /groups?search=foobar +``` + +```json +[ + { + "id": 1, + "name": "Foobar Group", + "path": "foo-bar", + "description": "An interesting group" + } +] +``` ## Group members @@ -124,10 +146,24 @@ POST /groups/:id/members Parameters: -- `id` (required) - The ID of a group +- `id` (required) - The ID or path of a group - `user_id` (required) - The ID of a user to add - `access_level` (required) - Project access level +### Edit group team member + +Updates a group team member to a specified access level. + +``` +PUT /groups/:id/members/:user_id +``` + +Parameters: + +- `id` (required) - The ID of a group +- `user_id` (required) - The ID of a group member +- `access_level` (required) - Project access level + ### Remove user team member Removes user from user team. @@ -138,5 +174,5 @@ DELETE /groups/:id/members/:user_id Parameters: -- `id` (required) - The ID of a user group +- `id` (required) - The ID or path of a user group - `user_id` (required) - The ID of a group member diff --git a/doc/api/issues.md b/doc/api/issues.md index a4b3b3e991..a7dd8b74c3 100644 --- a/doc/api/issues.md +++ b/doc/api/issues.md @@ -7,8 +7,20 @@ Get all issues created by authenticated user. This function takes pagination par ``` GET /issues +GET /issues?state=opened +GET /issues?state=closed +GET /issues?labels=foo +GET /issues?labels=foo,bar +GET /issues?labels=foo,bar&state=opened ``` +Parameters: + +- `state` (optional) - Return `all` issues or just those that are `opened` or `closed` +- `labels` (optional) - Comma-separated list of label names +- `order_by` (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` +- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` + ```json [ { @@ -46,7 +58,7 @@ GET /issues "title": "v1.0", "description": "", "due_date": "2012-07-20", - "state": "reopenend", + "state": "reopened", "updated_at": "2012-07-04T13:42:48Z", "created_at": "2012-07-04T13:42:48Z" }, @@ -80,11 +92,23 @@ to return the list of project issues. ``` GET /projects/:id/issues +GET /projects/:id/issues?state=opened +GET /projects/:id/issues?state=closed +GET /projects/:id/issues?labels=foo +GET /projects/:id/issues?labels=foo,bar +GET /projects/:id/issues?labels=foo,bar&state=opened +GET /projects/:id/issues?milestone=1.0.0 +GET /projects/:id/issues?milestone=1.0.0&state=opened ``` Parameters: - `id` (required) - The ID of a project +- `state` (optional) - Return `all` issues or just those that are `opened` or `closed` +- `labels` (optional) - Comma-separated list of label names +- `milestone` (optional) - Milestone title +- `order_by` (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` +- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` ## Single issue @@ -184,7 +208,7 @@ If an error occurs, an error number and a message explaining the reason is retur ## Delete existing issue (**Deprecated**) -The function is deprecated and returns a `405 Method Not Allowed` error if called. An issue gets now closed and is done by calling `PUT /projects/:id/issues/:issue_id` with parameter `closed` set to 1. +The function is deprecated and returns a `405 Method Not Allowed` error if called. An issue gets now closed and is done by calling `PUT /projects/:id/issues/:issue_id` with parameter `state_event` set to `close`. ``` DELETE /projects/:id/issues/:issue_id diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md index 3616e29ef7..6a272539e4 100644 --- a/doc/api/merge_requests.md +++ b/doc/api/merge_requests.md @@ -2,7 +2,9 @@ ## List merge requests -Get all merge requests for this project. The `state` parameter can be used to get only merge requests with a given state (`opened`, `closed`, or `merged`) or all of them (`all`). The pagination parameters `page` and `per_page` can be used to restrict the list of merge requests. +Get all merge requests for this project. +The `state` parameter can be used to get only merge requests with a given state (`opened`, `closed`, or `merged`) or all of them (`all`). +The pagination parameters `page` and `per_page` can be used to restrict the list of merge requests. ``` GET /projects/:id/merge_requests @@ -14,6 +16,8 @@ Parameters: - `id` (required) - The ID of a project - `state` (optional) - Return `all` requests or just those that are `merged`, `opened` or `closed` +- `order_by` (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` +- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` ```json [ @@ -92,6 +96,76 @@ Parameters: } ``` +## Get single MR changes + +Shows information about the merge request including its files and changes + +``` +GET /projects/:id/merge_request/:merge_request_id/changes +``` + +Parameters: + +- `id` (required) - The ID of a project +- `merge_request_id` (required) - The ID of MR + +```json +{ + "id": 21, + "iid": 1, + "project_id": 4, + "title": "Blanditiis beatae suscipit hic assumenda et molestias nisi asperiores repellat et.", + "description": "Qui voluptatibus placeat ipsa alias quasi. Deleniti rem ut sint. Optio velit qui distinctio.", + "state": "reopened", + "created_at": "2015-02-02T19:49:39.159Z", + "updated_at": "2015-02-02T20:08:49.959Z", + "target_branch": "secret_token", + "source_branch": "version-1-9", + "upvotes": 0, + "downvotes": 0, + "author": { + "name": "Chad Hamill", + "username": "jarrett", + "id": 5, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/b95567800f828948baf5f4160ebb2473?s=40&d=identicon" + }, + "assignee": { + "name": "Administrator", + "username": "root", + "id": 1, + "state": "active", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40&d=identicon" + }, + "source_project_id": 4, + "target_project_id": 4, + "labels": [ ], + "milestone": { + "id": 5, + "iid": 1, + "project_id": 4, + "title": "v2.0", + "description": "Assumenda aut placeat expedita exercitationem labore sunt enim earum.", + "state": "closed", + "created_at": "2015-02-02T19:49:26.013Z", + "updated_at": "2015-02-02T19:49:26.013Z", + "due_date": null + }, + "files": [ + { + "old_path": "VERSION", + "new_path": "VERSION", + "a_mode": "100644", + "b_mode": "100644", + "diff": "--- a/VERSION\ +++ b/VERSION\ @@ -1 +1 @@\ -1.9.7\ +1.9.8", + "new_file": false, + "renamed_file": false, + "deleted_file": false + } + ] +} +``` + ## Create MR Creates a new merge request. @@ -107,6 +181,7 @@ Parameters: - `target_branch` (required) - The target branch - `assignee_id` (optional) - Assignee user ID - `title` (required) - Title of MR +- `description` (optional) - Description of MR - `target_project_id` (optional) - The target project (numeric id) ```json @@ -158,6 +233,7 @@ Parameters: - `target_branch` - The target branch - `assignee_id` - Assignee user ID - `title` - Title of MR +- `description` - Description of MR - `state_event` - New state (close|reopen|merge) ```json @@ -167,6 +243,7 @@ Parameters: "source_branch": "test1", "project_id": 3, "title": "test1", + "description": "description1", "state": "opened", "upvotes": 0, "downvotes": 0, @@ -298,7 +375,7 @@ Parameters: } }, { - "note": "_Status changed to closed_", + "note": "Status changed to closed", "author": { "id": 11, "username": "admin", diff --git a/doc/api/milestones.md b/doc/api/milestones.md index 2f52532750..d48b3bcce8 100644 --- a/doc/api/milestones.md +++ b/doc/api/milestones.md @@ -72,3 +72,16 @@ Parameters: - `description` (optional) - The description of a milestone - `due_date` (optional) - The due date of the milestone - `state_event` (optional) - The state event of the milestone (close|activate) + +## Get all issues assigned to a single milestone + +Gets all issues assigned to a single project milestone. + +``` +GET /projects/:id/milestones/:milestone_id/issues +``` + +Parameters: + +- `id` (required) - The ID of a project +- `milestone_id` (required) - The ID of a project milestone diff --git a/doc/api/notes.md b/doc/api/notes.md index b5256ac803..ee2f9fa0ea 100644 --- a/doc/api/notes.md +++ b/doc/api/notes.md @@ -21,7 +21,7 @@ Parameters: [ { "id": 302, - "body": "_Status changed to closed_", + "body": "Status changed to closed", "attachment": null, "author": { "id": 1, @@ -78,6 +78,21 @@ Parameters: - `issue_id` (required) - The ID of an issue - `body` (required) - The content of a note +### Modify existing issue note + +Modify existing note of an issue. + +``` +PUT /projects/:id/issues/:issue_id/notes/:note_id +``` + +Parameters: + +- `id` (required) - The ID of a project +- `issue_id` (required) - The ID of an issue +- `note_id` (required) - The ID of a note +- `body` (required) - The content of a note + ## Snippets ### List all snippet notes @@ -137,7 +152,22 @@ POST /projects/:id/snippets/:snippet_id/notes Parameters: - `id` (required) - The ID of a project -- `snippet_id` (required) - The ID of an snippet +- `snippet_id` (required) - The ID of a snippet +- `body` (required) - The content of a note + +### Modify existing snippet note + +Modify existing note of a snippet. + +``` +PUT /projects/:id/snippets/:snippet_id/notes/:note_id +``` + +Parameters: + +- `id` (required) - The ID of a project +- `snippet_id` (required) - The ID of a snippet +- `note_id` (required) - The ID of a note - `body` (required) - The content of a note ## Merge Requests @@ -199,3 +229,18 @@ Parameters: - `id` (required) - The ID of a project - `merge_request_id` (required) - The ID of a merge request - `body` (required) - The content of a note + +### Modify existing merge request note + +Modify existing note of a merge request. + +``` +PUT /projects/:id/merge_requests/:merge_request_id/notes/:note_id +``` + +Parameters: + +- `id` (required) - The ID of a project +- `merge_request_id` (required) - The ID of a merge request +- `note_id` (required) - The ID of a note +- `body` (required) - The content of a note diff --git a/doc/api/oauth2.md b/doc/api/oauth2.md new file mode 100644 index 0000000000..d416a826f7 --- /dev/null +++ b/doc/api/oauth2.md @@ -0,0 +1,102 @@ +# GitLab as an OAuth2 client + +This document is about using other OAuth authentication service providers to sign into GitLab. +If you want GitLab to be an OAuth authentication service provider to sign into other services please see the [Oauth2 provider documentation](../integration/oauth_provider.md). + +OAuth2 is a protocol that enables us to authenticate a user without requiring them to give their password. + +Before using the OAuth2 you should create an application in user's account. Each application gets a unique App ID and App Secret parameters. You should not share these. + +This functionality is based on [doorkeeper gem](https://github.com/doorkeeper-gem/doorkeeper) + +## Web Application Flow + +This flow is using for authentication from third-party web sites and is probably used the most. +It basically consists of an exchange of an authorization token for an access token. For more detailed info, check out the [RFC spec here](http://tools.ietf.org/html/rfc6749#section-4.1) + +This flow consists from 3 steps. + +### 1. Registering the client + +Create an application in user's account profile. + +### 2. Requesting authorization + +To request the authorization token, you should visit the `/oauth/authorize` endpoint. You can do that by visiting manually the URL: + +``` +http://localhost:3000/oauth/authorize?client_id=APP_ID&redirect_uri=REDIRECT_URI&response_type=code +``` + +Where REDIRECT_URI is the URL in your app where users will be sent after authorization. + +### 3. Requesting the access token + +To request the access token, you should use the returned code and exchange it for an access token. To do that you can use any HTTP client. In this case, I used rest-client: + +``` +parameters = 'client_id=APP_ID&client_secret=APP_SECRET&code=RETURNED_CODE&grant_type=AUTHORIZATION_CODE&redirect_uri=REDIRECT_URI' +RestClient.post 'http://localhost:3000/oauth/token', parameters + +# The response will be +{ + "access_token": "de6780bc506a0446309bd9362820ba8aed28aa506c71eedbe1c5c4f9dd350e54", + "token_type": "bearer", + "expires_in": 7200, + "refresh_token": "8257e65c97202ed1726cf9571600918f3bffb2544b26e00a61df9897668c33a1" +} +``` + +You can now make requests to the API with the access token returned. + +### Use the access token to access the API + +The access token allows you to make requests to the API on a behalf of a user. + +``` +GET https://localhost:3000/api/v3/user?access_token=OAUTH-TOKEN +``` + +Or you can put the token to the Authorization header: + +``` +curl -H "Authorization: Bearer OAUTH-TOKEN" https://localhost:3000/api/v3/user +``` + +## Resource Owner Password Credentials + +In this flow, a token is requested in exchange for the resource owner credentials (username and password). +The credentials should only be used when there is a high degree of trust between the resource owner and the client (e.g. the +client is part of the device operating system or a highly privileged application), and when other authorization grant types are not +available (such as an authorization code). + +Even though this grant type requires direct client access to the resource owner credentials, the resource owner credentials are used +for a single request and are exchanged for an access token. This grant type can eliminate the need for the client to store the +resource owner credentials for future use, by exchanging the credentials with a long-lived access token or refresh token. +You can do POST request to `/oauth/token` with parameters: + +``` +{ + "grant_type" : "password", + "username" : "user@example.com", + "password" : "sekret" +} +``` + +Then, you'll receive the access token back in the response: + +``` +{ + "access_token": "1f0af717251950dbd4d73154fdf0a474a5c5119adad999683f5b450c460726aa", + "token_type": "bearer", + "expires_in": 7200 +} +``` + +For testing you can use the oauth2 ruby gem: + +``` +client = OAuth2::Client.new('the_client_id', 'the_client_secret', :site => "http://example.com") +access_token = client.password.get_token('user@example.com', 'sekret') +puts access_token.token +``` diff --git a/doc/api/projects.md b/doc/api/projects.md index 8995551b9e..971fe96fb8 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -1,5 +1,23 @@ # Projects + +### Project visibility level + +Project in GitLab has be either private, internal or public. +You can determine it by `visibility_level` field in project. + +Constants for project visibility levels are next: + +* Private. `visibility_level` is `0`. + Project access must be granted explicitly for each user. + +* Internal. `visibility_level` is `10`. + The project can be cloned by any logged in user. + +* Public. `visibility_level` is `20`. + The project can be cloned without any authentication. + + ## List projects Get a list of projects accessible by the authenticated user. @@ -11,6 +29,9 @@ GET /projects Parameters: - `archived` (optional) - if passed, limit by archived status +- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` +- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` +- `search` (optional) - Return list of authorized projects according to a search criteria ```json [ @@ -23,6 +44,10 @@ Parameters: "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git", "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git", "web_url": "http://example.com/diaspora/diaspora-client", + "tag_list": [ + "example", + "disapora client" + ], "owner": { "id": 3, "name": "Diaspora", @@ -38,6 +63,7 @@ Parameters: "snippets_enabled": false, "created_at": "2013-09-30T13: 46: 02Z", "last_activity_at": "2013-09-30T13: 46: 02Z", + "creator_id": 3, "namespace": { "created_at": "2013-09-30T13: 46: 02Z", "description": "", @@ -47,7 +73,8 @@ Parameters: "path": "diaspora", "updated_at": "2013-09-30T13: 46: 02Z" }, - "archived": false + "archived": false, + "avatar_url": "http://example.com/uploads/project/avatar/4/uploads/avatar.png" }, { "id": 6, @@ -58,6 +85,10 @@ Parameters: "ssh_url_to_repo": "git@example.com:brightbox/puppet.git", "http_url_to_repo": "http://example.com/brightbox/puppet.git", "web_url": "http://example.com/brightbox/puppet", + "tag_list": [ + "example", + "puppet" + ], "owner": { "id": 4, "name": "Brightbox", @@ -73,6 +104,7 @@ Parameters: "snippets_enabled": false, "created_at": "2013-09-30T13:46:02Z", "last_activity_at": "2013-09-30T13:46:02Z", + "creator_id": 3, "namespace": { "created_at": "2013-09-30T13:46:02Z", "description": "", @@ -82,7 +114,8 @@ Parameters: "path": "brightbox", "updated_at": "2013-09-30T13:46:02Z" }, - "archived": false + "archived": false, + "avatar_url": null } ] ``` @@ -95,6 +128,13 @@ Get a list of projects which are owned by the authenticated user. GET /projects/owned ``` +Parameters: + +- `archived` (optional) - if passed, limit by archived status +- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` +- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` +- `search` (optional) - Return list of authorized projects according to a search criteria + ### List ALL projects Get a list of all GitLab projects (admin only). @@ -103,6 +143,13 @@ Get a list of all GitLab projects (admin only). GET /projects/all ``` +Parameters: + +- `archived` (optional) - if passed, limit by archived status +- `order_by` (optional) - Return requests ordered by `id`, `name`, `path`, `created_at`, `updated_at` or `last_activity_at` fields. Default is `created_at` +- `sort` (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` +- `search` (optional) - Return list of authorized projects according to a search criteria + ### Get single project Get a specific project, identified by project ID or NAMESPACE/PROJECT_NAME, which is owned by the authenticated user. @@ -126,6 +173,10 @@ Parameters: "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git", "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git", "web_url": "http://example.com/diaspora/diaspora-project-site", + "tag_list": [ + "example", + "disapora project" + ], "owner": { "id": 3, "name": "Diaspora", @@ -141,6 +192,7 @@ Parameters: "snippets_enabled": false, "created_at": "2013-09-30T13: 46: 02Z", "last_activity_at": "2013-09-30T13: 46: 02Z", + "creator_id": 3, "namespace": { "created_at": "2013-09-30T13: 46: 02Z", "description": "", @@ -160,7 +212,8 @@ Parameters: "notification_level": 3 } }, - "archived": false + "archived": false, + "avatar_url": "http://example.com/uploads/project/avatar/3/uploads/avatar.png" } ``` @@ -186,6 +239,7 @@ Parameters: "target_id": 830, "target_type": "Issue", "author_id": 1, + "author_username": "john", "data": null, "target_title": "Public project search field" }, @@ -196,6 +250,7 @@ Parameters: "target_id": null, "target_type": null, "author_id": 1, + "author_username": "john", "data": { "before": "50d4420237a9de7be1304607147aec22e4a14af7", "after": "c5feabde2d8cd023215af4d2ceeb7a64839fc428", @@ -231,6 +286,7 @@ Parameters: "target_id": 840, "target_type": "Issue", "author_id": 1, + "author_username": "john", "data": null, "target_title": "Finish & merge Code search PR" } @@ -248,6 +304,7 @@ POST /projects Parameters: - `name` (required) - new project name +- `path` (optional) - custom repository name for new project. By default generated based on name - `namespace_id` (optional) - namespace for the new project (defaults to user) - `description` (optional) - short project description - `issues_enabled` (optional) @@ -280,6 +337,43 @@ Parameters: - `visibility_level` (optional) - `import_url` (optional) +### Edit project + +Updates an existing project + +``` +PUT /projects/:id +``` + +Parameters: + +- `id` (required) - The ID of a project +- `name` (optional) - project name +- `path` (optional) - repository name for project +- `description` (optional) - short project description +- `default_branch` (optional) +- `issues_enabled` (optional) +- `merge_requests_enabled` (optional) +- `wiki_enabled` (optional) +- `snippets_enabled` (optional) +- `public` (optional) - if `true` same as setting visibility_level = 20 +- `visibility_level` (optional) + +On success, method returns 200 with the updated project. If parameters are +invalid, 400 is returned. + +### Fork project + +Forks a project into the user namespace of the authenticated user. + +``` +POST /projects/fork/:id +``` + +Parameters: + +- `id` (required) - The ID of the project to be forked + ### Remove project Removes a project including all associated resources (issues, merge requests etc.) @@ -434,6 +528,7 @@ Parameters: - `push_events` - Trigger hook on push events - `issues_events` - Trigger hook on issues events - `merge_requests_events` - Trigger hook on merge_requests events +- `tag_push_events` - Trigger hook on push_tag events ### Edit project hook @@ -451,6 +546,7 @@ Parameters: - `push_events` - Trigger hook on push events - `issues_events` - Trigger hook on issues events - `merge_requests_events` - Trigger hook on merge_requests events +- `tag_push_events` - Trigger hook on push_tag events ### Delete project hook @@ -495,7 +591,7 @@ Parameters: } ], "tree": "c68537c6534a02cc2b176ca1549f4ffa190b58ee", - "message": "give caolan credit where it's due (up top)", + "message": "give Caolan credit where it's due (up top)", "author": { "name": "Jeremy Ashkenas", "email": "jashkenas@example.com" @@ -610,6 +706,8 @@ GET /projects/search/:query Parameters: -- query (required) - A string contained in the project name -- per_page (optional) - number of projects to return per page -- page (optional) - the page to retrieve +- `query` (required) - A string contained in the project name +- `per_page` (optional) - number of projects to return per page +- `page` (optional) - the page to retrieve +- `order_by` (optional) - Return requests ordered by `id`, `name`, `created_at` or `last_activity_at` fields +- `sort` (optional) - Return requests sorted in `asc` or `desc` order diff --git a/doc/api/repositories.md b/doc/api/repositories.md index 1074b78fd7..3316745380 100644 --- a/doc/api/repositories.md +++ b/doc/api/repositories.md @@ -15,24 +15,21 @@ Parameters: ```json [ { - "name": "v1.0.0", "commit": { - "id": "2695effb5807a22ff3d138d593fd856244e155e7", - "parents": [], - "tree": "38017f2f189336fe4497e9d230c5bb1bf873f08d", - "message": "Initial commit", - "author": { - "name": "John Smith", - "email": "john@example.com" - }, - "committer": { - "name": "Jack Smith", - "email": "jack@example.com" - }, + "author_name": "John Smith", + "author_email": "john@example.com", "authored_date": "2012-05-28T04:42:42-07:00", - "committed_date": "2012-05-28T04:42:42-07:00" + "committed_date": "2012-05-28T04:42:42-07:00", + "committer_name": "Jack Smith", + "committer_email": "jack@example.com", + "id": "2695effb5807a22ff3d138d593fd856244e155e7", + "message": "Initial commit", + "parents_ids": [ + "2a4b78934375d7f53875269ffd4f45fd83a84ebe" + ] }, - "protected": null + "name": "v1.0.0", + "message": null } ] ``` @@ -50,26 +47,32 @@ Parameters: - `id` (required) - The ID of a project - `tag_name` (required) - The name of a tag - `ref` (required) - Create tag using commit SHA, another tag name, or branch name. +- `message` (optional) - Creates annotated tag. ```json -[ - { - "name": "v1.0.0", - "commit": { - "id": "2695effb5807a22ff3d138d593fd856244e155e7", - "parents": [], - "message": "Initial commit", - "authored_date": "2012-05-28T04:42:42-07:00", - "author_name": "John Smith", - "author email": "john@example.com", - "committer_name": "Jack Smith", - "committed_date": "2012-05-28T04:42:42-07:00", - "committer_email": "jack@example.com" - }, - "protected": false - } -] +{ + "commit": { + "author_name": "John Smith", + "author_email": "john@example.com", + "authored_date": "2012-05-28T04:42:42-07:00", + "committed_date": "2012-05-28T04:42:42-07:00", + "committer_name": "Jack Smith", + "committer_email": "jack@example.com", + "id": "2695effb5807a22ff3d138d593fd856244e155e7", + "message": "Initial commit", + "parents_ids": [ + "2a4b78934375d7f53875269ffd4f45fd83a84ebe" + ] + }, + "name": "v1.0.0", + "message": null +} ``` +The message will be `nil` when creating a lightweight tag otherwise +it will contain the annotation. + +It returns 200 if the operation succeed. In case of an error, +405 with an explaining error message is returned. ## List repository tree diff --git a/doc/api/services.md b/doc/api/services.md new file mode 100644 index 0000000000..cbf767d1b2 --- /dev/null +++ b/doc/api/services.md @@ -0,0 +1,46 @@ +# Services + +## GitLab CI + +### Edit GitLab CI service + +Set GitLab CI service for a project. + +``` +PUT /projects/:id/services/gitlab-ci +``` + +Parameters: + +- `token` (required) - CI project token +- `project_url` (required) - CI project URL + +### Delete GitLab CI service + +Delete GitLab CI service settings for a project. + +``` +DELETE /projects/:id/services/gitlab-ci +``` + +## HipChat + +### Edit HipChat service + +Set HipChat service for project. + +``` +PUT /projects/:id/services/hipchat +``` +Parameters: + +- `token` (required) - HipChat token +- `room` (required) - HipChat room name + +### Delete HipChat service + +Delete HipChat service for a project. + +``` +DELETE /projects/:id/services/hipchat +``` diff --git a/doc/api/users.md b/doc/api/users.md index 3fdd3a75e8..a8b7685b50 100644 --- a/doc/api/users.md +++ b/doc/api/users.md @@ -78,7 +78,8 @@ GET /users "is_admin": false, "avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg", "can_create_group": true, - "can_create_project": true + "can_create_project": true, + "projects_limit": 100 } ] ``` @@ -140,7 +141,8 @@ Parameters: "color_scheme_id": 2, "is_admin": false, "can_create_group": true, - "can_create_project": true + "can_create_project": true, + "projects_limit": 100 } ``` @@ -168,6 +170,7 @@ Parameters: - `bio` (optional) - User's biography - `admin` (optional) - User is admin - true or false (default) - `can_create_group` (optional) - User can create groups - true or false +- `confirm` (optional) - Require confirmation - true (default) or false ## User modification @@ -240,7 +243,8 @@ GET /user "color_scheme_id": 2, "is_admin": false, "can_create_group": true, - "can_create_project": true + "can_create_project": true, + "projects_limit": 100 } ``` @@ -257,12 +261,14 @@ GET /user/keys { "id": 1, "title": "Public key", - "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", + "created_at": "2014-08-01T14:47:39.080Z" }, { "id": 3, "title": "Another Public key", - "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", + "created_at": "2014-08-01T14:47:39.080Z" } ] ``` @@ -299,7 +305,8 @@ Parameters: { "id": 1, "title": "Public key", - "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=", + "created_at": "2014-08-01T14:47:39.080Z" } ``` @@ -316,6 +323,31 @@ Parameters: - `title` (required) - new SSH Key's title - `key` (required) - new SSH key +```json +{ + "created_at": "2015-01-21T17:44:33.512Z", + "key": "ssh-dss AAAAB3NzaC1kc3MAAACBAMLrhYgI3atfrSD6KDas1b/3n6R/HP+bLaHHX6oh+L1vg31mdUqK0Ac/NjZoQunavoyzqdPYhFz9zzOezCrZKjuJDS3NRK9rspvjgM0xYR4d47oNZbdZbwkI4cTv/gcMlquRy0OvpfIvJtjtaJWMwTLtM5VhRusRuUlpH99UUVeXAAAAFQCVyX+92hBEjInEKL0v13c/egDCTQAAAIEAvFdWGq0ccOPbw4f/F8LpZqvWDydAcpXHV3thwb7WkFfppvm4SZte0zds1FJ+Hr8Xzzc5zMHe6J4Nlay/rP4ewmIW7iFKNBEYb/yWa+ceLrs+TfR672TaAgO6o7iSRofEq5YLdwgrwkMmIawa21FrZ2D9SPao/IwvENzk/xcHu7YAAACAQFXQH6HQnxOrw4dqf0NqeKy1tfIPxYYUZhPJfo9O0AmBW2S36pD2l14kS89fvz6Y1g8gN/FwFnRncMzlLY/hX70FSc/3hKBSbH6C6j8hwlgFKfizav21eS358JJz93leOakJZnGb8XlWvz1UJbwCsnR2VEY8Dz90uIk1l/UqHkA= loic@call", + "title": "ABC", + "id": 4 +} +``` + +Will return created key with status `201 Created` on success. If an +error occurs a `400 Bad Request` is returned with a message explaining the error: + +```json +{ + "message": { + "fingerprint": [ + "has already been taken" + ], + "key": [ + "has already been taken" + ] + } +} +``` + ## Add SSH key for user Create new key owned by specified user. Available only for admin diff --git a/doc/customization/issue_closing.md b/doc/customization/issue_closing.md new file mode 100644 index 0000000000..aa65a082a5 --- /dev/null +++ b/doc/customization/issue_closing.md @@ -0,0 +1,36 @@ +# Issue closing pattern + +If a commit message matches the regular expression below, all issues referenced from +the matched text will be closed. This happens when the commit is pushed or merged +into the default branch of a project. + +When not specified, the default issue_closing_pattern as shown below will be used: + +```bash +((?:[Cc]los(?:e[sd]?|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+) +``` + +For example: + +``` +git commit -m "Awesome commit message (Fix #20, Fixes #21 and Closes #22). This commit is also related to #17 and fixes #18, #19 and #23." +``` + +will close `#20`, `#21`, `#22`, `#18`, `#19` and `#23`, but `#17` won't be closed +as it does not match the pattern. It also works with multiline commit messages. + +Tip: you can test this closing pattern at [http://rubular.com][1]. Use this site +to test your own patterns. + +## Change the pattern + +For Omnibus installs you can change the default pattern in `/etc/gitlab/gitlab.rb`: + +``` +issue_closing_pattern: '((?:[Cc]los(?:e[sd]|ing)|[Ff]ix(?:e[sd]|ing)?) +(?:(?:issues? +)?#\d+(?:(?:, *| +and +)?))+)' +``` + +For manual installs you can customize the pattern in [gitlab.yml][0]. + +[0]: https://gitlab.com/gitlab-org/gitlab-ce/blob/40c3675372320febf5264061c9bcd63db2dfd13c/config/gitlab.yml.example#L65 +[1]: http://rubular.com/r/Xmbexed1OJ diff --git a/doc/customization/libravatar.md b/doc/customization/libravatar.md new file mode 100644 index 0000000000..ee57fdc659 --- /dev/null +++ b/doc/customization/libravatar.md @@ -0,0 +1,69 @@ +# Use Libravatar service with GitLab + +GitLab by default supports [Gravatar](gravatar.com) avatar service. +Libravatar is a service which delivers your avatar (profile picture) to other websites and their API is +[heavily based on gravatar](http://wiki.libravatar.org/api/). + +This means that it is not complicated to switch to Libravatar avatar service or even self hosted Libravatar server. + +# Configuration + +In [gitlab.yml gravatar section](https://gitlab.com/gitlab-org/gitlab-ce/blob/672bd3902d86b78d730cea809fce312ec49d39d7/config/gitlab.yml.example#L122) set +the configuration options as follows: + +## For HTTP + +```yml + gravatar: + enabled: true + # gravatar URLs: possible placeholders: %{hash} %{size} %{email} + plain_url: "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon" +``` + +## For HTTPS + +```yml + gravatar: + enabled: true + # gravatar URLs: possible placeholders: %{hash} %{size} %{email} + ssl_url: "https://seccdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon" +``` + +## Self-hosted + +If you are [running your own libravatar service](http://wiki.libravatar.org/running_your_own/) the URL will be different in the configuration +but the important part is to provide the same placeholders so GitLab can parse the URL correctly. + +For example, you host a service on `http://libravatar.example.com` the `plain_url` you need to supply in `gitlab.yml` is + +`http://libravatar.example.com/avatar/%{hash}?s=%{size}&d=identicon` + + +## Omnibus-gitlab example + +In `/etc/gitlab/gitlab.rb`: + +#### For http + +```ruby +gitlab_rails['gravatar_enabled'] = true +gitlab_rails['gravatar_plain_url'] = "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon" +``` + +#### For https + +```ruby +gitlab_rails['gravatar_enabled'] = true +gitlab_rails['gravatar_ssl_url'] = "https://seccdn.libravatar.org/avatar/%{hash}?s=%{size}&d=identicon" +``` + + +Run `sudo gitlab-ctl reconfigure` for changes to take effect. + + +## Default URL for missing images + +[Libravatar supports different sets](http://wiki.libravatar.org/api/) of `missing images` for emails not found on the Libravatar service. + +In order to use a different set other than `identicon`, replace `&d=identicon` portion of the URL with another supported set. +For example, you can use `retro` set in which case the URL would look like: `plain_url: "http://cdn.libravatar.org/avatar/%{hash}?s=%{size}&d=retro"` diff --git a/doc/customization/welcome_message.md b/doc/customization/welcome_message.md new file mode 100644 index 0000000000..6c141d1fb7 --- /dev/null +++ b/doc/customization/welcome_message.md @@ -0,0 +1,38 @@ +# Customize the complete sign-in page (GitLab Enterprise Edition only) + +Please see [Branded login page](http://doc.gitlab.com/ee/customization/branded_login_page.html) + +# Add a welcome message to the sign-in page (GitLab Community Edition) + +It is possible to add a markdown-formatted welcome message to your GitLab +sign-in page. Users of GitLab Enterprise Edition should use the [branded login +page feature](/ee/customization/branded_login_page.html) instead. + +## Omnibus-gitlab example + +In `/etc/gitlab/gitlab.rb`: + +```ruby +gitlab_rails['extra_sign_in_text'] = <<'EOS' +# ACME GitLab +Welcome to the [ACME](http://www.example.com) GitLab server! +EOS +``` + +Run `sudo gitlab-ctl reconfigure` for changes to take effect. + +## Installation from source + +In `/home/git/gitlab/config/gitlab.yml`: + +```yaml +# snip +production: + # snip + extra: + sign_in_text: | + # ACME GitLab + Welcome to the [ACME](http://www.example.com) GitLab server! +``` + +Run `sudo service gitlab reload` for the change to take effect. diff --git a/doc/development/README.md b/doc/development/README.md index 67ee828db6..d5d264be19 100644 --- a/doc/development/README.md +++ b/doc/development/README.md @@ -3,3 +3,6 @@ - [Architecture](architecture.md) of GitLab - [Shell commands](shell_commands.md) in the GitLab codebase - [Rake tasks](rake_tasks.md) for development +- [CI setup](ci_setup.md) for testing GitLab +- [Sidekiq debugging](sidekiq_debugging.md) +- [UI guide](ui_guide.md) for building GitLab with existing css styles and elements diff --git a/doc/development/architecture.md b/doc/development/architecture.md index 4624d9f60b..541af487bb 100644 --- a/doc/development/architecture.md +++ b/doc/development/architecture.md @@ -2,12 +2,44 @@ ## Software delivery -There are two editions of GitLab: [Enterprise Edition](https://www.gitlab.com/gitlab-ee/) (EE) and [Community Edition](https://www.gitlab.com/gitlab-ce/) (CE). GitLab CE is delivered via git from the [gitlabhq repository](https://gitlab.com/gitlab-org/gitlab-ce/tree/master). New versions of GitLab are released in stable branches and the master branch is for bleeding edge development. +There are two editions of GitLab: [Enterprise Edition](https://about.gitlab.com/gitlab-ee/) (EE) and [Community Edition](https://about.gitlab.com/gitlab-ce/) (CE). GitLab CE is delivered via git from the [gitlabhq repository](https://gitlab.com/gitlab-org/gitlab-ce/tree/master). New versions of GitLab are released in stable branches and the master branch is for bleeding edge development. EE releases are available not long after CE releases. To obtain the GitLab EE there is a [repository at gitlab.com](https://gitlab.com/subscribers/gitlab-ee). For more information about the release process see the section 'New versions and upgrading' in the readme. Both EE and CE require an add-on component called gitlab-shell. It is obtained from the [gitlab-shell repository](https://gitlab.com/gitlab-org/gitlab-shell/tree/master). New versions are usually tags but staying on the master branch will give you the latest stable version. New releases are generally around the same time as GitLab CE releases with exception for informal security updates deemed critical. +## Physical office analogy + +You can imagine GitLab as a physical office. + +**The repositories** are the goods GitLab handling. +They can be stored in a warehouse. +This can be either a hard disk, or something more complex, such as a NFS filesystem; + +**Nginx** acts like the front-desk. +Users come to Nginx and request actions to be done by workers in the office; + +**The database** is a series of metal file cabinets with information on: + - The goods in the warehouse (metadata, issues, merge requests etc); + - The users coming to the front desk (permissions) + +**Redis** is a communication board with “cubby holes” that can contain tasks for office workers; + +**Sidekiq** is a worker that primarily handles sending out emails. +It takes tasks from the Redis communication board; + +**A Unicorn worker** is a worker that handles quick/mundane tasks. +They work with the communication board (Redis). +Their job description: + - check permissions by checking the user session stored in a Redis “cubby hole”; + - make tasks for Sidekiq; + - fetch stuff from the warehouse or move things around in there; + +**Gitlab-shell** is a third kind of worker that takes orders from a fax machine (SSH) instead of the front desk (HTTP). +Gitlab-shell communicates with Sidekiq via the “communication board” (Redis), and asks quick questions of the Unicorn workers either directly or via the front desk. + +**GitLab Enterprise Edition (the application)** is the collection of processes and business practices that the office is run by. + ## System Layout When referring to ~git in the pictures it means the home directory of the git user which is typically /home/git. @@ -22,7 +54,7 @@ To serve repositories over SSH there's an add-on application called gitlab-shell ![GitLab Diagram Overview](gitlab_diagram_overview.png) -A typical install of GitLab will be on Ubuntu Linux or RHEL/CentOS. It uses Nginx or Apache as a web front end to proxypass the Unicorn web server. By default, communication between Unicorn and the front end is via a Unix domain socket but forwarding requests via TCP is also supported. The web front end accesses `/home/git/gitlab/public` bypassing the Unicorn server to serve static pages, uploads (e.g. avatar images or attachments), and precompiled assets. GitLab serves web pages and a [GitLab API](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/api) using the Unicorn web server. It uses Sidekiq as a job queue which, in turn, uses redis as a non-persistent database backend for job information, meta data, and incoming jobs. +A typical install of GitLab will be on GNU/Linux. It uses Nginx or Apache as a web front end to proxypass the Unicorn web server. By default, communication between Unicorn and the front end is via a Unix domain socket but forwarding requests via TCP is also supported. The web front end accesses `/home/git/gitlab/public` bypassing the Unicorn server to serve static pages, uploads (e.g. avatar images or attachments), and precompiled assets. GitLab serves web pages and a [GitLab API](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/api) using the Unicorn web server. It uses Sidekiq as a job queue which, in turn, uses redis as a non-persistent database backend for job information, meta data, and incoming jobs. The GitLab web app uses MySQL or PostgreSQL for persistent database information (e.g. users, permissions, issues, other meta data). GitLab stores the bare git repositories it serves in `/home/git/repositories` by default. It also keeps default branch and hook information with the bare repository. `/home/git/gitlab-satellites` keeps checked out repositories when performing actions such as a merge request, editing files in the web interface, etc. @@ -38,7 +70,7 @@ To summarize here's the [directory structure of the `git` user home directory](. ps aux | grep '^git' -GitLab has several components to operate. As a system user (i.e. any user that is not the `git` user) it requires a persistent database (MySQL/PostreSQL) and redis database. It also uses Apache httpd or nginx to proxypass Unicorn. As the `git` user it starts Sidekiq and Unicorn (a simple ruby HTTP server running on port `8080` by default). Under the GitLab user there are normally 4 processes: `unicorn_rails master` (1 process), `unicorn_rails worker` (2 processes), `sidekiq` (1 process). +GitLab has several components to operate. As a system user (i.e. any user that is not the `git` user) it requires a persistent database (MySQL/PostreSQL) and redis database. It also uses Apache httpd or Nginx to proxypass Unicorn. As the `git` user it starts Sidekiq and Unicorn (a simple ruby HTTP server running on port `8080` by default). Under the GitLab user there are normally 4 processes: `unicorn_rails master` (1 process), `unicorn_rails worker` (2 processes), `sidekiq` (1 process). ### Repository access @@ -114,13 +146,13 @@ nginx Apache httpd -- [Explanation of apache logs](http://httpd.apache.org/docs/2.2/logs.html). +- [Explanation of Apache logs](http://httpd.apache.org/docs/2.2/logs.html). - `/var/log/apache2/` contains error and output logs (on Ubuntu). - `/var/log/httpd/` contains error and output logs (on RHEL). redis -- `/var/log/redis/redis.log` there are also logrotated logs there. +- `/var/log/redis/redis.log` there are also log-rotated logs there. PostgreSQL diff --git a/doc/development/ci_setup.md b/doc/development/ci_setup.md new file mode 100644 index 0000000000..f9b4886818 --- /dev/null +++ b/doc/development/ci_setup.md @@ -0,0 +1,46 @@ +# CI setup + +This document describes what services we use for testing GitLab and GitLab CI. + +We currently use three CI services to test GitLab: + +1. GitLab CI on [GitHost.io](https://gitlab-ce.githost.io/projects/4/) for the [GitLab.com repo](https://gitlab.com/gitlab-org/gitlab-ce) +2. GitLab CI at ci.gitlab.org to test the private GitLab B.V. repo at dev.gitlab.org +3. [Semephore](https://semaphoreapp.com/gitlabhq/gitlabhq/) for [GitHub.com repo](https://github.com/gitlabhq/gitlabhq) + +| Software @ configuration being tested | GitLab CI (ci.gitlab.org) | GitLab CI (GitHost.io) | Semaphore | +|---------------------------------------|---------------------------|---------------------------------------------------------------------------|-----------| +| GitLab CE @ MySQL | ✓ | ✓ [Core team can trigger builds](https://gitlab-ce.githost.io/projects/4) | | +| GitLab CE @ PostgreSQL | | | ✓ [Core team can trigger builds](https://semaphoreapp.com/gitlabhq/gitlabhq/branches/master) | +| GitLab EE @ MySQL | ✓ | | | +| GitLab CI @ MySQL | ✓ | | | +| GitLab CI @ PostgreSQL | | | ✓ | +| GitLab CI Runner | ✓ | | ✓ | +| GitLab Shell | ✓ | | ✓ | +| GitLab Shell | ✓ | | ✓ | + +Core team has access to trigger builds if needed for GitLab CE. + +We use [these build scripts](https://gitlab.com/gitlab-org/gitlab-ci/blob/master/doc/examples/build_script_gitlab_ce.md) for testing with GitLab CI. + +# Build configuration on [Semaphore](https://semaphoreapp.com/gitlabhq/gitlabhq/) for testing the [GitHub.com repo](https://github.com/gitlabhq/gitlabhq) + +- Language: Ruby +- Ruby version: 2.1.2 +- database.yml: pg + +Build commands + +```bash +sudo apt-get install cmake libicu-dev -y (Setup) +bundle install --deployment --path vendor/bundle (Setup) +cp config/gitlab.yml.example config/gitlab.yml (Setup) +bundle exec rake db:create (Setup) +bundle exec rake spinach (Thread #1) +bundle exec rake spec (thread #2) +bundle exec rake rubocop (thread #3) +bundle exec rake brakeman (thread #4) +bundle exec rake jasmine:ci (thread #5) +``` + +Use rubygems mirror. diff --git a/doc/development/omnibus.md b/doc/development/omnibus.md new file mode 100644 index 0000000000..0ba354d28a --- /dev/null +++ b/doc/development/omnibus.md @@ -0,0 +1,32 @@ +# What you should know about omnibus packages + +Most users install GitLab using our omnibus packages. As a developer it can be +good to know how the omnibus packages differ from what you have on your laptop +when you are coding. + +## Files are owned by root by default + +All the files in the Rails tree (`app/`, `config/` etc.) are owned by 'root' in +omnibus installations. This makes the installation simpler and it provides +extra security. The omnibus reconfigure script contains commands that give +write access to the 'git' user only where needed. + +For example, the 'git' user is allowed to write in the `log/` directory, in +`public/uploads`, and they are allowed to rewrite the `db/schema.rb` file. + +In other cases, the reconfigure script tricks GitLab into not trying to write a +file. For instance, GitLab will generate a `.secret` file if it cannot find one +and write it to the Rails root. In the omnibus packages, reconfigure writes the +`.secret` file first, so that GitLab never tries to write it. + +## Code, data and logs are in separate directories + +The omnibus design separates code (read-only, under `/opt/gitlab`) from data +(read/write, under `/var/opt/gitlab`) and logs (read/write, under +`/var/log/gitlab`). To make this happen the reconfigure script sets custom +paths where it can in GitLab config files, and where there are no path +settings, it uses symlinks. + +For example, `config/gitlab.yml` is treated as data so that file is a symlink. +The same goes for `public/uploads`. The `log/` directory is replaced by omnibus +with a symlink to `/var/log/gitlab/gitlab-rails`. diff --git a/doc/development/rake_tasks.md b/doc/development/rake_tasks.md index 6d9ac161e9..53f8095cb1 100644 --- a/doc/development/rake_tasks.md +++ b/doc/development/rake_tasks.md @@ -1,6 +1,6 @@ # Rake tasks for developers -## Setup db with developer seeds: +## Setup db with developer seeds Note that if your db user does not have advanced privileges you must create the db manually before running this command. @@ -8,6 +8,10 @@ Note that if your db user does not have advanced privileges you must create the bundle exec rake setup ``` +The `setup` task is a alias for `gitlab:setup`. +This tasks calls `db:setup` to create the database, calls `add_limits_mysql` that adds limits to the database schema in case of a MySQL database and fianlly it calls `db:seed_fu` to seed the database. +Note: `db:setup` calls `db:seed` but this does nothing. + ## Run tests This runs all test suites present in GitLab. diff --git a/doc/development/shell_commands.md b/doc/development/shell_commands.md index 1f3908f4e2..821027f43f 100644 --- a/doc/development/shell_commands.md +++ b/doc/development/shell_commands.md @@ -1,5 +1,8 @@ # Guidelines for shell commands in the GitLab codebase +This document contains guidelines for working with processes and files in the GitLab codebase. +These guidelines are meant to make your code more reliable _and_ secure. + ## References - [Google Ruby Security Reviewer's Guide](https://code.google.com/p/ruby-security/wiki/Guide) @@ -22,6 +25,12 @@ FileUtils.mkdir_p "tmp/special/directory" contents = `cat #{filename}` # Correct contents = File.read(filename) + +# Sometimes a shell command is just the best solution. The example below has no +# user input, and is hard to implement correctly in Ruby: delete all files and +# directories older than 120 minutes under /some/path, but not /some/path +# itself. +Gitlab::Popen.popen(%W(find /some/path -not -path /some/path -mmin +120 -delete)) ``` This coding style could have prevented CVE-2013-4490. @@ -99,7 +108,72 @@ In other repositories, such as gitlab-shell you can also use `IO.popen`. ```ruby # Safe IO.popen example -logs = IO.popen(%W(git log), chdir: repo_dir).read +logs = IO.popen(%W(git log), chdir: repo_dir) { |p| p.read } ``` Note that unlike `Gitlab::Popen.popen`, `IO.popen` does not capture standard error. + +## Avoid user input at the start of path strings + +Various methods for opening and reading files in Ruby can be used to read the +standard output of a process instead of a file. The following two commands do +roughly the same: + +``` +`touch /tmp/pawned-by-backticks` +File.read('|touch /tmp/pawned-by-file-read') +``` + +The key is to open a 'file' whose name starts with a `|`. +Affected methods include Kernel#open, File::read, File::open, IO::open and IO::read. + +You can protect against this behavior of 'open' and 'read' by ensuring that an +attacker cannot control the start of the filename string you are opening. For +instance, the following is sufficient to protect against accidentally starting +a shell command with `|`: + +``` +# we assume repo_path is not controlled by the attacker (user) +path = File.join(repo_path, user_input) +# path cannot start with '|' now. +File.read(path) +``` + +If you have to use user input a relative path, prefix `./` to the path. + +Prefixing user-supplied paths also offers extra protection against paths +starting with `-` (see the discussion about using `--` above). + +## Guard against path traversal + +Path traversal is a security where the program (GitLab) tries to restrict user +access to a certain directory on disk, but the user manages to open a file +outside that directory by taking advantage of the `../` path notation. + +``` +# Suppose the user gave us a path and they are trying to trick us +user_input = '../other-repo.git/other-file' + +# We look up the repo path somewhere +repo_path = 'repositories/user-repo.git' + +# The intention of the code below is to open a file under repo_path, but +# because the user used '..' she can 'break out' into +# 'repositories/other-repo.git' +full_path = File.join(repo_path, user_input) +File.open(full_path) do # Oops! +``` + +A good way to protect against this is to compare the full path with its +'absolute path' according to Ruby's `File.absolute_path`. + +``` +full_path = File.join(repo_path, user_input) +if full_path != File.absolute_path(full_path) + raise "Invalid path: #{full_path.inspect}" +end + +File.open(full_path) do # Etc. +``` + +A check like this could have avoided CVE-2013-4583. diff --git a/doc/development/sidekiq_debugging.md b/doc/development/sidekiq_debugging.md new file mode 100644 index 0000000000..cea11e5f12 --- /dev/null +++ b/doc/development/sidekiq_debugging.md @@ -0,0 +1,14 @@ +# Sidekiq debugging + +## Log arguments to Sidekiq jobs + +If you want to see what arguments are being passed to Sidekiq jobs you can set +the SIDEKIQ_LOG_ARGUMENTS environment variable. + +``` +SIDEKIQ_LOG_ARGUMENTS=1 bundle exec foreman start +``` + +It is not recommend to enable this setting in production because some Sidekiq +jobs (such as sending a password reset email) take secret arguments (for +example the password reset token). diff --git a/doc/development/ui_guide.md b/doc/development/ui_guide.md new file mode 100644 index 0000000000..2f01defc11 --- /dev/null +++ b/doc/development/ui_guide.md @@ -0,0 +1,12 @@ +# UI Guide for building GitLab + +## Best practices for creating new pages in GitLab + +TODO: write some best practices when develop GitLab features. + +## GitLab UI development kit + +We created a page inside GitLab where you can check commonly used html and css elements. + +When you run GitLab instance locally - just visit http://localhost:3000/help/ui page to see UI examples +you can use during GitLab development. diff --git a/doc/hooks/custom_hooks.md b/doc/hooks/custom_hooks.md new file mode 100644 index 0000000000..f7d4f3de68 --- /dev/null +++ b/doc/hooks/custom_hooks.md @@ -0,0 +1,41 @@ +# Custom Git Hooks + +**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 as an option if you do not have filesystem access.** + +Git natively supports hooks that are executed on different actions. +Examples of server-side git hooks include pre-receive, post-receive, and update. +See +[Git SCM Server-Side Hooks](http://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks#Server-Side-Hooks) +for more information about each hook type. + +As of gitlab-shell version 2.2.0 (which requires GitLab 7.5+), GitLab +administrators can add custom git hooks to any GitLab project. + +## Setup + +Normally, git hooks are placed in the repository or project's `hooks` directory. +GitLab creates a symlink from each project's `hooks` directory to the +gitlab-shell `hooks` directory for ease of maintenance between gitlab-shell +upgrades. As such, custom hooks are implemented a little differently. Behavior +is exactly the same once the hook is created, though. Follow these steps to +set up a custom hook. + +1. Pick a project that needs a custom git hook. +1. On the GitLab server, navigate to the project's repository directory. +For an installation from source the path is usually +`/home/git/repositories//.git`. For Omnibus installs the path is +usually `/var/opt/gitlab/git-data/repositories//.git`. +1. Create a new directory in this location called `custom_hooks`. +1. Inside the new `custom_hooks` directory, create a file with a name matching +the hook type. For a pre-receive hook the file name should be `pre-receive` with +no extension. +1. Make the hook file executable and make sure it's owned by git. +1. Write the code to make the git hook function as expected. Hooks can be +in any language. Ensure the 'shebang' at the top properly reflects the language +type. For example, if the script is in Ruby the shebang will probably be +`#!/usr/bin/env ruby`. + +That's it! Assuming the hook code is properly implemented the hook will fire +as appropriate. diff --git a/doc/install/database_mysql.md b/doc/install/database_mysql.md index 270ad3b0b8..362c492d0a 100644 --- a/doc/install/database_mysql.md +++ b/doc/install/database_mysql.md @@ -1,4 +1,4 @@ -# Database Mysql +# Database MySQL ## Note @@ -12,31 +12,31 @@ We do not recommend using MySQL due to various issues. For example, case [(in)se # Ensure you have MySQL version 5.5.14 or later mysql --version - # Pick a database root password (can be anything), type it and press enter - # Retype the database root password and press enter + # Pick a MySQL root password (can be anything), type it and press enter + # Retype the MySQL root password and press enter - # Secure your installation. + # Secure your installation sudo mysql_secure_installation # Login to MySQL mysql -u root -p - # Type the database root password + # Type the MySQL root password # Create a user for GitLab # do not type the 'mysql>', this is part of the prompt # change $password in the command below to a real password you pick mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password'; - # Ensure you can use the InnoDB engine which is necessary to support long indexes. + # Ensure you can use the InnoDB engine which is necessary to support long indexes # If this fails, check your MySQL config files (e.g. `/etc/mysql/*.cnf`, `/etc/mysql/conf.d/*`) for the setting "innodb = off" mysql> SET storage_engine=INNODB; # Create the GitLab production database mysql> CREATE DATABASE IF NOT EXISTS `gitlabhq_production` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_unicode_ci`; - # Grant the GitLab user necessary permissions on the table. - mysql> GRANT SELECT, LOCK TABLES, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER ON `gitlabhq_production`.* TO 'git'@'localhost'; + # Grant the GitLab user necessary permissions on the database + mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES ON `gitlabhq_production`.* TO 'git'@'localhost'; # Quit the database session mysql> \q diff --git a/doc/install/installation.md b/doc/install/installation.md index 7ac2619259..a61a40ebd1 100644 --- a/doc/install/installation.md +++ b/doc/install/installation.md @@ -1,24 +1,30 @@ -# Installation +# Installation from source + +## Consider the Omnibus package installation + +Since an installation from source is a lot of work and error prone we strongly recommend the fast and reliable [Omnibus package installation](https://about.gitlab.com/downloads/) (deb/rpm). ## Select Version to Install -Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) from the branch (version) of GitLab you would like to install. In most cases this should be the highest numbered stable branch (example shown below). +Make sure you view [this installation guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md) 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 in the top left corner of GitLab (below the menu bar). -![capture](http://i.imgur.com/d2AlIVj.png) +If the highest number stable branch is unclear please check the [GitLab Blog](https://about.gitlab.com/blog/) for installation guide links by version. -If the highest number stable branch is unclear please check the [GitLab Blog](https://www.gitlab.com/blog/) for installation guide links by version. - -## Important notes +## Important Notes This guide is long because it covers many cases and includes all commands you need, this is [one of the few installation scripts that actually works out of the box](https://twitter.com/robinvdvleuten/status/424163226532986880). -This installation guide was created for and tested on **Debian/Ubuntu** operating systems. Please read [doc/install/requirements.md](./requirements.md) for hardware and operating system requirements. If you want to install on RHEL/CentOS we recommend using the [Omnibus packages](https://www.gitlab.com/downloads/). +This installation guide was created for and tested on **Debian/Ubuntu** operating systems. Please read [doc/install/requirements.md](./requirements.md) for hardware and operating system requirements. If you want to install on RHEL/CentOS we recommend using the [Omnibus packages](https://about.gitlab.com/downloads/). This is the official installation guide to set up a production server. To set up a **development installation** or for many other installation options please see [the installation section of the readme](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/README.md#installation). The following steps have been known to work. Please **use caution when you deviate** from this guide. Make sure you don't violate any assumptions GitLab makes about its environment. For example many people run into permission problems because they changed the location of directories or run services as the wrong user. -If you find a bug/error in this guide please **submit a merge request** following the [contributing guide](../../CONTRIBUTING.md). +If you find a bug/error in this guide please **submit a merge request** +following the +[contributing guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md). ## Overview @@ -28,6 +34,7 @@ The GitLab installation consists of setting up the following components: 1. Ruby 1. System Users 1. Database +1. Redis 1. GitLab 1. Nginx @@ -49,7 +56,7 @@ up-to-date and install it. Install the required packages (needed to compile Ruby and native extensions to Ruby gems): - sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server redis-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake + sudo apt-get install -y build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl openssh-server redis-server checkinstall libxml2-dev libxslt-dev libcurl4-openssl-dev libicu-dev logrotate python-docutils pkg-config cmake libkrb5-dev nodejs Make sure you have the right version of Git installed @@ -69,14 +76,15 @@ Is the system packaged Git too old? Remove it and compile from source. # Download and compile from source cd /tmp - curl -L --progress https://www.kernel.org/pub/software/scm/git/git-2.0.0.tar.gz | tar xz - cd git-2.0.0/ + curl -L --progress https://www.kernel.org/pub/software/scm/git/git-2.1.2.tar.gz | tar xz + cd git-2.1.2/ + ./configure make prefix=/usr/local all # Install into /usr/local/bin sudo make prefix=/usr/local install - # When editing config/gitlab.yml (Step 5), change the git bin_path to /usr/local/bin/git + # When editing config/gitlab.yml (Step 5), change the git -> bin_path to /usr/local/bin/git **Note:** In order to receive mail notifications, make sure to install a mail server. By default, Debian is shipped with exim4 but this [has problems](https://github.com/gitlabhq/gitlabhq/issues/4866#issuecomment-32726573) while Ubuntu does not ship with one. The recommended mail server is postfix and you can install it with: @@ -86,7 +94,7 @@ Then select 'Internet Site' and press enter to confirm the hostname. ## 2. Ruby -The use of ruby version managers such as [RVM](http://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we strongly advise everyone to follow the instructions below to use a system ruby. +The use of Ruby version managers such as [RVM](http://rvm.io/), [rbenv](https://github.com/sstephenson/rbenv) or [chruby](https://github.com/postmodern/chruby) with GitLab in production frequently leads to hard to diagnose problems. For example, GitLab Shell is called from OpenSSH and having a version manager can prevent pushing and pulling over SSH. Version managers are not supported and we strongly advise everyone to follow the instructions below to use a system Ruby. Remove the old Ruby 1.8 if present @@ -95,8 +103,8 @@ Remove the old Ruby 1.8 if present Download Ruby and compile it: mkdir /tmp/ruby && cd /tmp/ruby - curl -L --progress ftp://ftp.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz | tar xz - cd ruby-2.1.2 + curl -L --progress http://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.6.tar.gz | tar xz + cd ruby-2.1.6 ./configure --disable-install-rdoc make sudo make install @@ -116,12 +124,13 @@ Create a `git` user for GitLab: We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](database_mysql.md). *Note*: because we need to make use of extensions you need at least pgsql 9.1. # Install the database packages - sudo apt-get install -y postgresql-9.1 postgresql-client libpq-dev + sudo apt-get install -y postgresql postgresql-client libpq-dev # Login to PostgreSQL sudo -u postgres psql -d template1 - # Create a user for GitLab. + # Create a user for GitLab + # Do not type the 'template1=#', this is part of the prompt template1=# CREATE USER git CREATEDB; # Create the GitLab production database & grant all privileges on database @@ -133,7 +142,40 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da # Try connecting to the new database with the new user sudo -u git -H psql -d gitlabhq_production -## 5. GitLab + # Quit the database session + gitlabhq_production> \q + +## 5. Redis + + sudo apt-get install redis-server + + # Configure redis to use sockets + sudo cp /etc/redis/redis.conf /etc/redis/redis.conf.orig + + # Disable Redis listening on TCP by setting 'port' to 0 + sed 's/^port .*/port 0/' /etc/redis/redis.conf.orig | sudo tee /etc/redis/redis.conf + + # Enable Redis socket for default Debian / Ubuntu path + echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf + # Grant permission to the socket to all members of the redis group + echo 'unixsocketperm 770' | sudo tee -a /etc/redis/redis.conf + + # Create the directory which contains the socket + mkdir /var/run/redis + chown redis:redis /var/run/redis + chmod 755 /var/run/redis + # Persist the directory which contains the socket, if applicable + if [ -d /etc/tmpfiles.d ]; then + echo 'd /var/run/redis 0755 redis redis 10d -' | sudo tee -a /etc/tmpfiles.d/redis.conf + fi + + # Activate the changes to redis.conf + sudo service redis-server restart + + # Add git to the redis group + sudo usermod -aG redis git + +## 6. GitLab # We'll install GitLab into home directory of the user "git" cd /home/git @@ -141,32 +183,25 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da ### Clone the Source # Clone GitLab repository - sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-2-stable gitlab + sudo -u git -H git clone https://gitlab.com/gitlab-org/gitlab-ce.git -b 7-10-stable gitlab - # Go to gitlab dir - cd /home/git/gitlab +**Note:** You can change `7-10-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! -**Note:** You can change `7-2-stable` to `master` if you want the *bleeding edge* version, but never install master on a production server! - -### Configure it +### Configure It + # Go to GitLab installation folder cd /home/git/gitlab # Copy the example GitLab config sudo -u git -H cp config/gitlab.yml.example config/gitlab.yml - # Make sure to change "localhost" to the fully-qualified domain name of your - # host serving GitLab where necessary - # - # If you want to use https make sure that you set `https` to `true`. See #using-https for all necessary details. - # - # If you installed Git from source, change the git bin_path to /usr/local/bin/git + # Update GitLab config file, follow the directions at top of file sudo -u git -H editor config/gitlab.yml # Make sure GitLab can write to the log/ and tmp/ directories sudo chown -R git log/ sudo chown -R git tmp/ - sudo chmod -R u+rwX log/ + sudo chmod -R u+rwX,go-w log/ sudo chmod -R u+rwX tmp/ # Create directory for satellites @@ -183,8 +218,12 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da # Copy the example Unicorn config sudo -u git -H cp config/unicorn.rb.example config/unicorn.rb + # Find number of cores + nproc + # Enable cluster mode if you expect to have a high load instance # Ex. change amount of workers to 3 for 2GB RAM server + # Set the number of workers to at least the number of cores sudo -u git -H editor config/unicorn.rb # Copy the example Rack attack config @@ -196,9 +235,17 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da sudo -u git -H git config --global user.email "example@example.com" sudo -u git -H git config --global core.autocrlf input + # Configure Redis connection settings + sudo -u git -H cp config/resque.yml.example config/resque.yml + + # Change the Redis socket path if you are not using the default Debian / Ubuntu configuration + sudo -u git -H editor config/resque.yml + **Important Note:** Make sure to edit both `gitlab.yml` and `unicorn.rb` to match your setup. -### Configure GitLab DB settings +**Note:** If you want to use HTTPS, see [Using HTTPS](#using-https) for the additional steps. + +### Configure GitLab DB Settings # PostgreSQL only: sudo -u git cp config/database.yml.postgresql config/database.yml @@ -222,35 +269,25 @@ We recommend using a PostgreSQL database. For MySQL check [MySQL setup guide](da **Note:** As of bundler 1.5.2, you can invoke `bundle install -jN` (where `N` the number of your processor cores) and enjoy the parallel gems installation with measurable difference in completion time (~60% faster). Check the number of your cores with `nproc`. For more information check this [post](http://robots.thoughtbot.com/parallel-gem-installing-using-bundler). First make sure you have bundler >= 1.5.2 (run `bundle -v`) as it addresses some [issues](https://devcenter.heroku.com/changelog-items/411) that were [fixed](https://github.com/bundler/bundler/pull/2817) in 1.5.2. - cd /home/git/gitlab - # For PostgreSQL (note, the option says "without ... mysql") sudo -u git -H bundle install --deployment --without development test mysql aws # Or if you use MySQL (note, the option says "without ... postgres") sudo -u git -H bundle install --deployment --without development test postgres aws -### Install GitLab shell +### Install GitLab Shell GitLab Shell is an SSH access and repository management software developed specially for GitLab. - # Go to the GitLab installation folder: - cd /home/git/gitlab - # Run the installation task for gitlab-shell (replace `REDIS_URL` if needed): - sudo -u git -H bundle exec rake gitlab:shell:install[v1.9.7] REDIS_URL=redis://localhost:6379 RAILS_ENV=production + sudo -u git -H bundle exec rake gitlab:shell:install[v2.6.2] REDIS_URL=unix:/var/run/redis/redis.sock RAILS_ENV=production - # By default, the gitlab-shell config is generated from your main gitlab config. - # - # Note: When using GitLab with HTTPS please change the following: - # - Provide paths to the certificates under `ca_file` and `ca_path` options. - # - The `gitlab_url` option must point to the https endpoint of GitLab. - # - In case you are using self signed certificate set `self_signed_cert` to `true`. - # See #using-https for all necessary details. - # + # By default, the gitlab-shell config is generated from your main GitLab config. # You can review (and modify) the gitlab-shell config as follows: sudo -u git -H editor /home/git/gitlab-shell/config.yml +**Note:** If you want to use HTTPS, see [Using HTTPS](#using-https) for the additional steps. + ### Initialize Database and Activate Advanced Features sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production @@ -259,9 +296,13 @@ GitLab Shell is an SSH access and repository management software developed speci # When done you see 'Administrator account created:' +**Note:** You can set the Administrator/root password by supplying it in environmental variable `GITLAB_ROOT_PASSWORD` as seen below. If you don't set the password (and it is set to the default one) please wait with exposing GitLab to the public internet until the installation is done and you've logged into the server the first time. During the first login you'll be forced to change the default password. + + sudo -u git -H bundle exec rake gitlab:setup RAILS_ENV=production GITLAB_ROOT_PASSWORD=yourpassword + ### Install Init Script -Download the init script (will be /etc/init.d/gitlab): +Download the init script (will be `/etc/init.d/gitlab`): sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab @@ -269,13 +310,13 @@ And if you are installing with a non-default folder or user copy and edit the de sudo cp lib/support/init.d/gitlab.default.example /etc/default/gitlab -If you installed GitLab in another directory or as a user other than the default you should change these settings in `/etc/default/gitlab`. Do not edit `/etc/init.d/gitlab as it will be changed on upgrade. +If you installed GitLab in another directory or as a user other than the default you should change these settings in `/etc/default/gitlab`. Do not edit `/etc/init.d/gitlab` as it will be changed on upgrade. Make GitLab start on boot: sudo update-rc.d gitlab defaults 21 -### Set up logrotate +### Setup Logrotate sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab @@ -285,7 +326,7 @@ Check if GitLab and its environment are configured correctly: sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production -### Compile assets +### Compile Assets sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production @@ -295,11 +336,12 @@ Check if GitLab and its environment are configured correctly: # or sudo /etc/init.d/gitlab restart -## 6. Nginx +## 7. Nginx **Note:** Nginx is the officially supported web server for GitLab. If you cannot or do not want to use Nginx as your web server, have a look at the [GitLab recipes](https://gitlab.com/gitlab-org/gitlab-recipes/). ### Installation + sudo apt-get install -y nginx ### Site Configuration @@ -315,7 +357,15 @@ Make sure to edit the config file to match your setup: # domain name of your host serving GitLab. sudo editor /etc/nginx/sites-available/gitlab -**Note:** If you want to use https, replace the `gitlab` nginx config with `gitlab-ssl`. See [Using HTTPS](#using-https) for all necessary details. +**Note:** If you want to use HTTPS, replace the `gitlab` Nginx config with `gitlab-ssl`. See [Using HTTPS](#using-https) for HTTPS configuration details. + +### Test Configuration + +Validate your `gitlab` or `gitlab-ssl` Nginx config file with the following command: + + sudo nginx -t + +You should receive `syntax is okay` and `test is successful` messages. If you receive errors check your `gitlab` or `gitlab-ssl` Nginx config file for typos, etc. as indicated in the error message given. ### Restart @@ -335,26 +385,47 @@ NOTE: Supply `SANITIZE=true` environment variable to `gitlab:check` to omit proj ### Initial Login -Visit YOUR_SERVER in your web browser for your first GitLab login. The setup has created an admin account for you. You can use it to log in: +Visit YOUR_SERVER in your web browser for your first GitLab login. The setup has created a default admin account for you. You can use it to log in: root 5iveL!fe -**Important Note:** Please go over to your profile page and immediately change the password, so nobody can access your GitLab by using this login information later on. +**Important Note:** On login you'll be prompted to change the password. **Enjoy!** +You can use `sudo service gitlab start` and `sudo service gitlab stop` to start and stop GitLab. + ## Advanced Setup Tips ### Using HTTPS -To recapitulate what is needed to use GitLab with HTTPS: +To use GitLab with HTTPS: -1. In `gitlab.yml` set the `https` option to `true` -1. In the `config.yml` of gitlab-shell set the relevant options (see the [install GitLab Shell section](#install-gitlab-shell) of this document). -1. Use the `gitlab-ssl` nginx example config instead of the `gitlab` config. +1. In `gitlab.yml`: + 1. Set the `port` option in section 1 to `443`. + 1. Set the `https` option in section 1 to `true`. +1. In the `config.yml` of gitlab-shell: + 1. Set `gitlab_url` option to the HTTPS endpoint of GitLab (e.g. `https://git.example.com`). + 1. Set the certificates using either the `ca_file` or `ca_path` option. +1. Use the `gitlab-ssl` Nginx example config instead of the `gitlab` config. + 1. Update `YOUR_SERVER_FQDN`. + 1. Update `ssl_certificate` and `ssl_certificate_key`. + 1. Review the configuration file and consider applying other security and performance enhancing features. -### Additional markup styles +Using a self-signed certificate is discouraged but if you must use it follow the normal directions then: + +1. Generate a self-signed SSL certificate: + + ``` + mkdir -p /etc/nginx/ssl/ + cd /etc/nginx/ssl/ + sudo openssl req -newkey rsa:2048 -x509 -nodes -days 3560 -out gitlab.crt -keyout gitlab.key + sudo chmod o-r gitlab.key + ``` +1. In the `config.yml` of gitlab-shell set `self_signed_cert` to `true`. + +### Additional Markup Styles Apart from the always supported markdown style there are other rich text files that GitLab can display. But you might have to install a dependency to do so. Please see the [github-markup gem readme](https://github.com/gitlabhq/markup#markups) for more information. @@ -382,44 +453,10 @@ If you are running SSH on a non-standard port, you must change the GitLab user's You also need to change the corresponding options (e.g. `ssh_user`, `ssh_host`, `admin_uri`) in the `config\gitlab.yml` file. -### LDAP authentication +### LDAP Authentication You can configure LDAP authentication in `config/gitlab.yml`. Please restart GitLab after editing this file. ### Using Custom Omniauth Providers -GitLab uses [Omniauth](http://www.omniauth.org/) for authentication and already ships with a few providers preinstalled (e.g. LDAP, GitHub, Twitter). But sometimes that is not enough and you need to integrate with other authentication solutions. For these cases you can use the Omniauth provider. - -#### Steps - -These steps are fairly general and you will need to figure out the exact details from the Omniauth provider's documentation. - -- Stop GitLab: - - sudo service gitlab stop - -- Add the gem to your [Gemfile](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/Gemfile): - - gem "omniauth-your-auth-provider" - -- If you're using MySQL, install the new Omniauth provider gem by running the following command: - - sudo -u git -H bundle install --without development test postgres --path vendor/bundle --no-deployment - -- If you're using PostgreSQL, install the new Omniauth provider gem by running the following command: - - sudo -u git -H bundle install --without development test mysql --path vendor/bundle --no-deployment - - > These are the same commands you used in the [Install Gems section](#install-gems) with `--path vendor/bundle --no-deployment` instead of `--deployment`. - -- Start GitLab: - - `sudo service gitlab start` - -#### Examples - -If you have successfully set up a provider that is not shipped with GitLab itself, please let us know. - -You can help others by reporting successful configurations and probably share a few insights or provide warnings for common errors or pitfalls by sharing your experience [in the public Wiki](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Custom-omniauth-provider-configurations). - -While we can't officially support every possible authentication mechanism out there, we'd like to at least help those with special needs. +See the [omniauth integration document](../integration/omniauth.md) diff --git a/doc/install/requirements.md b/doc/install/requirements.md index 53f6ccc8c3..7a3216dd2d 100644 --- a/doc/install/requirements.md +++ b/doc/install/requirements.md @@ -7,9 +7,9 @@ - Ubuntu - Debian - CentOS -- RedHat Enterprise Linux -- Scientific Linux -- Oracle Linux +- Red Hat Enterprise Linux (please use the CentOS packages and instructions) +- Scientific Linux (please use the CentOS packages and instructions) +- Oracle Linux (please use the CentOS packages and instructions) For the installations options please see [the installation page on the GitLab website](https://about.gitlab.com/installation/). @@ -22,9 +22,9 @@ For the installations options please see [the installation page on the GitLab we - FreeBSD On the above unsupported distributions is still possible to install GitLab yourself. -Please see the [manual installation guide](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md) and the [unofficial installation guides](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Unofficial-Installation-Guides) on the public wiki for more information. +Please see the [installation from source guide](https://github.com/gitlabhq/gitlabhq/blob/master/doc/install/installation.md) and the [unofficial installation guides](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Unofficial-Installation-Guides) on the public wiki for more information. -### Non Unix operating systems such as Windows +### Non-Unix operating systems such as Windows GitLab is developed for Unix operating systems. GitLab does **not** run on Windows and we have no plans of supporting it in the near future. @@ -34,10 +34,20 @@ Please consider using a virtual machine to run GitLab. GitLab requires Ruby (MRI) 2.0 or 2.1 You will have to use the standard MRI implementation of Ruby. -We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/)) but GitLab needs several Gems that have native extensions. +We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/) but GitLab needs several Gems that have native extensions. ## Hardware requirements +### Storage + +The necessary hard drive space largely depends on the size of the repos you want to store in GitLab but as a *rule of thumb* you should have at least twice as much free space as all your repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo. + +If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them. + +Apart from a local hard drive you can also mount a volume that supports the network file system (NFS) protocol. This volume might be located on a file server, a network attached storage (NAS) device, a storage area network (SAN) or on an Amazon Web Services (AWS) Elastic Block Store (EBS) volume. + +If you have enough RAM memory and a recent CPU the speed of GitLab is mainly limited by hard drive seek times. Having a fast drive (7200 RPM and up) or a solid state drive (SSD) will improve the responsiveness of GitLab. + ### CPU - 1 core works supports up to 100 users but the application can be a bit slower due to having all workers and background jobs running on the same core @@ -50,11 +60,10 @@ We love [JRuby](http://jruby.org/) and [Rubinius](http://rubini.us/)) but GitLab ### Memory -- 512MB is the absolute minimum but we do not recommend this amount of memory. -You will either need to configure 512MB or 1.5GB of swap space. -With 512MB of swap space you must configure only one unicorn worker. -With one unicorn worker only git over ssh access will work because the git over http access requires two running workers (one worker to receive the user request and one worker for the authorization check). -If you use SSD storage and configure 1.5GB of swap space you can use two Unicorn workers, this will allow http access but it will still be slow. +You need at least 2GB of addressable memory (RAM + swap) to install and use GitLab! +With less memory GitLab will give strange errors during the reconfigure run and 500 errors during usage. + +- 512MB RAM + 1.5GB of swap is the absolute minimum but we strongly **advise against** this amount of memory. See the unicorn worker section below for more advise. - 1GB RAM + 1GB swap supports up to 100 users - **2GB RAM** is the **recommended** memory size and supports up to 500 users - 4GB RAM supports up to 2,000 users @@ -65,15 +74,19 @@ If you use SSD storage and configure 1.5GB of swap space you can use two Unicorn 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. -### Storage +## Unicorn Workers -The necessary hard drive space largely depends on the size of the repos you want to store in GitLab. But as a *rule of thumb* you should have at least twice as much free space as your all repos combined take up. You need twice the storage because [GitLab satellites](structure.md) contain an extra copy of each repo. +It's possible to increase the amount of unicorn workers and this will usually help for to reduce the response time of the applications and increase the ability to handle parallel requests. -If you want to be flexible about growing your hard drive space in the future consider mounting it using LVM so you can add more hard drives when you need them. +For most instances we recommend using: CPU cores + 1 = unicorn workers. +So for a machine with 2 cores, 3 unicorn workers is ideal. -Apart from a local hard drive you can also mount a volume that supports the network file system (NFS) protocol. This volume might be located on a file server, a network attached storage (NAS) device, a storage area network (SAN) or on an Amazon Web Services (AWS) Elastic Block Store (EBS) volume. +For all machines that have 1GB and up we recommend a minimum of three unicorn workers. +If you have a 512MB machine with a magnetic (non-SSD) swap drive we recommend to configure only one Unicorn worker to prevent excessive swapping. +With one Unicorn worker only git over ssh access will work because the git over HTTP access requires two running workers (one worker to receive the user request and one worker for the authorization check). +If you have a 512MB machine with a SSD drive you can use two Unicorn workers, this will allow HTTP access although it will be slow due to swapping. -If you have enough RAM memory and a recent CPU the speed of GitLab is mainly limited by hard drive seek times. Having a fast drive (7200 RPM and up) or a solid state drive (SSD) will improve the responsiveness of GitLab. +To change the Unicorn workers when you have the Omnibus package please see [the Unicorn settings in the Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/unicorn.md#unicorn-settings). ## Database @@ -85,12 +98,12 @@ Redis stores all user sessions and the background task queue. The storage requirements for Redis are minimal, about 25kB per user. Sidekiq processes the background jobs with a multithreaded process. This process starts with the entire Rails stack (200MB+) but it can grow over time due to memory leaks. -On a very active server (10.000 active users) the Sidekiq process can use 1GB+ of memory. +On a very active server (10,000 active users) the Sidekiq process can use 1GB+ of memory. -## Supported webbrowsers +## Supported web browsers - Chrome (Latest stable version) -- Firefox (Latest released version) +- Firefox (Latest released version and [latest ESR version](https://www.mozilla.org/en-US/firefox/organizations/)) - Safari 7+ (known problem: required fields in html5 do not work) - Opera (Latest released version) -- IE 10+ +- IE 10+ \ No newline at end of file diff --git a/doc/install/structure.md b/doc/install/structure.md index 67ca189537..5c03f073c1 100644 --- a/doc/install/structure.md +++ b/doc/install/structure.md @@ -13,9 +13,9 @@ This is the directory structure you will end up with following the instructions * `/home/git/.ssh` - contains openssh settings. Specifically the `authorized_keys` file managed by gitlab-shell. * `/home/git/gitlab` - GitLab core software. * `/home/git/gitlab-satellites` - checked out repositories for merge requests and file editing from web UI. This can be treated as a temporary files directory. -* `/home/git/gitlab-shell` - Core add-on component of gitlab. Maintains SSH cloning and other functionality. +* `/home/git/gitlab-shell` - Core add-on component of GitLab. Maintains SSH cloning and other functionality. * `/home/git/repositories` - bare repositories for all projects organized by namespace. This is where the git repositories which are pushed/pulled are maintained for all projects. **This area is critical data for projects. [Keep a backup](../raketasks/backup_restore.md)** -*Note: the default locations for gitlab-satellites and repositories can be configured in `config/gitlab.yml` of gitlab and `config.yml` of gitlab-shell.* +*Note: the default locations for gitlab-satellites and repositories can be configured in `config/gitlab.yml` of GitLab and `config.yml` of gitlab-shell.* To see a more in-depth overview see the [GitLab architecture doc](../development/architecture.md). diff --git a/doc/integration/README.md b/doc/integration/README.md index 357ed03831..286bd34a0b 100644 --- a/doc/integration/README.md +++ b/doc/integration/README.md @@ -6,14 +6,16 @@ See the documentation below for details on how to configure these services. - [External issue tracker](external-issue-tracker.md) Redmine, JIRA, etc. - [LDAP](ldap.md) Set up sign in via LDAP -- [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, and Google via OAuth. +- [OmniAuth](omniauth.md) Sign in via Twitter, GitHub, GitLab, and Google via OAuth. - [Slack](slack.md) Integrate with the Slack chat service +- [OAuth2 provider](oauth_provider.md) OAuth2 application creation +- [Gmail](gitlab_buttons_in_gmail.md) Adds GitLab actions to messages -Jenkins support is [available in GitLab EE](http://doc.gitlab.com/ee/integration/jenkins.html). +GitLab Enterprise Edition contains [advanced JIRA support](http://doc.gitlab.com/ee/integration/jira.html) and [advanced Jenkins support](http://doc.gitlab.com/ee/integration/jenkins.html). ## Project services -Integration with services such as Campfire, Flowdock, Gemnasium, HipChat, PivotalTracker and Slack are available in the from of a Project Service. +Integration with services such as Campfire, Flowdock, Gemnasium, HipChat, Pivotal Tracker, and Slack are available in the form of a Project Service. You can find these within GitLab in the Services page under Project Settings if you are at least a master on the project. Project Services are a bit like plugins in that they allow a lot of freedom in adding functionality to GitLab, for example there is also a service that can send an email every time someone pushes new commits. Because GitLab is open source we can ship with the code and tests for all plugins. diff --git a/doc/integration/bitbucket.md b/doc/integration/bitbucket.md new file mode 100644 index 0000000000..d82e1f8b41 --- /dev/null +++ b/doc/integration/bitbucket.md @@ -0,0 +1,122 @@ +# Integrate your server with Bitbucket + +Import projects from Bitbucket and login to your GitLab instance with your Bitbucket account. + +To enable the Bitbucket OmniAuth provider you must register your application with Bitbucket. +Bitbucket will generate an application ID and secret key for you to use. + +1. Sign in to Bitbucket. + +1. Navigate to your individual user settings or a team's settings, depending on how you want the application registered. It does not matter if the application is registered as an individual or a team - that is entirely up to you. + +1. Select "OAuth" in the left menu. + +1. Select "Add consumer". + +1. Provide the required details. + - Name: This can be anything. Consider something like "\'s GitLab" or "\'s GitLab" or something else descriptive. + - Application description: Fill this in if you wish. + - URL: The URL to your GitLab installation. 'https://gitlab.company.com' +1. Select "Save". + +1. You should now see a Key and Secret in the list of OAuth customers. + Keep this page open as you continue configuration. + +1. On your GitLab server, open the configuration file. + + For omnibus package: + + ```sh + sudo editor /etc/gitlab/gitlab.rb + ``` + + For instalations from source: + + ```sh + cd /home/git/gitlab + + sudo -u git -H editor config/gitlab.yml + ``` + +1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings. + +1. Add the provider configuration: + + For omnibus package: + + ```ruby + gitlab_rails['omniauth_providers'] = [ + { + "name" => "bitbucket", + "app_id" => "YOUR_KEY", + "app_secret" => "YOUR_APP_SECRET", + "url" => "https://bitbucket.org/" + } + ] + ``` + + For installation from source: + + ``` + - { name: 'bitbucket', app_id: 'YOUR_KEY', + app_secret: 'YOUR_APP_SECRET' } + ``` + +1. Change 'YOUR_APP_ID' to the key from the Bitbucket application page from step 7. + +1. Change 'YOUR_APP_SECRET' to the secret from the Bitbucket application page from step 7. + +1. Save the configuration file. + +1. Restart GitLab for the changes to take effect. + +On the sign in page there should now be a Bitbucket icon below the regular sign in form. +Click the icon to begin the authentication process. Bitbucket will ask the user to sign in and authorize the GitLab application. +If everything goes well the user will be returned to GitLab and will be signed in. + +## Bitbucket project import + +To allow projects to be imported directly into GitLab, Bitbucket requires two extra setup steps compared to GitHub and GitLab.com. + +Bitbucket doesn't allow OAuth applications to clone repositories over HTTPS, and instead requires GitLab to use SSH and identify itself using your GitLab server's SSH key. + +### Step 1: Known hosts + +To allow GitLab to connect to Bitbucket over SSH, you need to add 'bitbucket.org' to your GitLab server's known SSH hosts. Take the following steps to do so: + +1. Manually connect to 'bitbucket.org' over SSH, while logged in as the `git` account that GitLab will use: + + ```sh + ssh git@bitbucket.org + ``` + +1. Verify the RSA key fingerprint you'll see in the response matches the one in the [Bitbucket documentation](https://confluence.atlassian.com/display/BITBUCKET/Use+the+SSH+protocol+with+Bitbucket#UsetheSSHprotocolwithBitbucket-KnownhostorBitbucket'spublickeyfingerprints) (the specific IP address doesn't matter): + + ```sh + The authenticity of host 'bitbucket.org (207.223.240.182)' can't be established. + RSA key fingerprint is 97:8c:1b:f2:6f:14:6b:5c:3b:ec:aa:46:46:74:7c:40. + Are you sure you want to continue connecting (yes/no)? + ``` + +1. If the fingerprint matches, type `yes` to continue connecting and have 'bitbucket.org' be added to your known hosts. + +1. Your GitLab server is now able to connect to Bitbucket over SSH. Continue to step 2: + +### Step 2: Public key + +To be able to access repositories on Bitbucket, GitLab will automatically register your public key with Bitbucket as a deploy key for the repositories to be imported. Your public key needs to be at `~/.ssh/bitbucket_rsa.pub`, which will expand to `/home/git/.ssh/bitbucket_rsa.pub` in most configurations. + +If you have that file in place, you're all set and should see the "Import projects from Bitbucket" option enabled. If you don't, do the following: + +1. Create a new SSH key: + + ```sh + sudo -u git -H ssh-keygen + ``` + + When asked `Enter file in which to save the key` specify the correct path, eg. `/home/git/.ssh/bitbucket_rsa`. + Make sure to use an **empty passphrase**. + +2. Restart GitLab to allow it to find the new public key. + +You should now see the "Import projects from Bitbucket" option on the New Project page enabled. diff --git a/doc/integration/external-issue-tracker.md b/doc/integration/external-issue-tracker.md index 6245836bef..96755707de 100644 --- a/doc/integration/external-issue-tracker.md +++ b/doc/integration/external-issue-tracker.md @@ -1,13 +1,39 @@ # External issue tracker -GitLab has a great issue tracker but you can also use an external issue tracker such as JIRA or Redmine. This is something that you can turn on per GitLab project. If for example you configure JIRA it provides the following functionality: +GitLab has a great issue tracker but you can also use an external issue tracker such as Jira, Bugzilla or Redmine. You can configure issue trackers per GitLab project. For instance, if you configure Jira it allows you to do the following: -- the 'Issues' link on the GitLab project pages takes you to the appropriate JIRA issue index; -- clicking 'New issue' on the project dashboard creates a new JIRA issue; -- To reference JIRA issue PROJECT-1234 in comments, use syntax PROJECT-1234. Commit messages get turned into HTML links to the corresponding JIRA issue. +- the 'Issues' link on the GitLab project pages takes you to the appropriate Jira issue index; +- clicking 'New issue' on the project dashboard creates a new Jira issue; +- To reference Jira issue PROJECT-1234 in comments, use syntax PROJECT-1234. Commit messages get turned into HTML links to the corresponding Jira issue. -![jira screenshot](jira-integration-points.png) +![Jira screenshot](jira-integration-points.png) -You can configure the integration in the gitlab.yml configuration file. +GitLab Enterprise Edition contains [advanced JIRA support](http://doc.gitlab.com/ee/integration/jira.html). + +## Configuration + +### Project Service + +You can enable an external issue tracker per project. As an example, we will configure `Redmine` for project named gitlab-ci. + +Fill in the required details on the page: + +![redmine configuration](redmine_configuration.png) + +* `description` A name for the issue tracker (to differentiate between instances, for example). +* `project_url` The URL to the project in Redmine which is being linked to this GitLab project. +* `issues_url` The URL to the issue in Redmine project that is linked to this GitLab project. Note that the `issues_url` requires `:id` in the url. This id is used by GitLab as a placeholder to replace the issue number. +* `new_issue_url` This is the URL to create a new issue in Redmine for the project linked to this GitLab project. + +### Service Template + +It is necessary to configure the external issue tracker per project, because project specific details are needed for the integration with GitLab. +The admin can add a service template that sets a default for each project. This makes it much easier to configure individual projects. + +In GitLab Admin section, navigate to `Service Templates` and choose the service template you want to create: + +![redmine service template](redmine_service_template.png) + +After the template is created, the template details will be pre-filled on the project service page. Support to add your commits to the Jira ticket automatically is [available in GitLab EE](http://doc.gitlab.com/ee/integration/jira.html). diff --git a/doc/integration/github.md b/doc/integration/github.md index 714593d826..b64501c2aa 100644 --- a/doc/integration/github.md +++ b/doc/integration/github.md @@ -1,6 +1,9 @@ -# GitHub OAuth2 OmniAuth Provider +# Integrate your server with GitHub -To enable the GitHub OmniAuth provider you must register your application with GitHub. GitHub will generate a client ID and secret key for you to use. +Import projects from GitHub and login to your GitLab instance with your GitHub account. + +To enable the GitHub OmniAuth provider you must register your application with GitHub. +GitHub will generate an application ID and secret key for you to use. 1. Sign in to GitHub. @@ -14,35 +17,63 @@ To enable the GitHub OmniAuth provider you must register your application with G - Application name: This can be anything. Consider something like "\'s GitLab" or "\'s GitLab" or something else descriptive. - Homepage URL: The URL to your GitLab installation. 'https://gitlab.company.com' - Application description: Fill this in if you wish. - - Authorization callback URL: 'https://gitlab.company.com/users/auth/github/callback' + - Authorization callback URL: 'https://gitlab.company.com/' 1. Select "Register application". -1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot). Keep this page open as you continue configuration. ![GitHub app](github_app.png) +1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot). + Keep this page open as you continue configuration. + ![GitHub app](github_app.png) 1. On your GitLab server, open the configuration file. + For omnibus package: + ```sh - cd /home/git/gitlab - - sudo -u git -H editor config/gitlab.yml + sudo editor /etc/gitlab/gitlab.rb ``` -1. Find the section dealing with OmniAuth. See [Initial OmniAuth Configuration](README.md#initial-omniauth-configuration) for more details. + For instalations from source: -1. Under `providers:` uncomment (or add) lines that look like the following: + ```sh + cd /home/git/gitlab - ``` - - { name: 'github', app_id: 'YOUR APP ID', - app_secret: 'YOUR APP SECRET', - args: { scope: 'user:email' } } + sudo -u git -H editor config/gitlab.yml ``` -1. Change 'YOUR APP ID' to the client ID from the GitHub application page from step 7. +1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings. -1. Change 'YOUR APP SECRET' to the client secret from the GitHub application page from step 7. +1. Add the provider configuration: + + For omnibus package: + + ```ruby + gitlab_rails['omniauth_providers'] = [ + { + "name" => "github", + "app_id" => "YOUR_APP_ID", + "app_secret" => "YOUR_APP_SECRET", + "url" => "https://github.com/", + "args" => { "scope" => "user:email" } + } + ] + ``` + + For installation from source: + + ``` + - { name: 'github', app_id: 'YOUR_APP_ID', + app_secret: 'YOUR_APP_SECRET', + args: { scope: 'user:email' } } + ``` + +1. Change 'YOUR_APP_ID' to the client ID from the GitHub application page from step 7. + +1. Change 'YOUR_APP_SECRET' to the client secret from the GitHub application page from step 7. 1. Save the configuration file. 1. Restart GitLab for the changes to take effect. -On the sign in page there should now be a GitHub icon below the regular sign in form. Click the icon to begin the authentication process. GitHub will ask the user to sign in and authorize the GitLab application. If everything goes well the user will be returned to GitLab and will be signed in. +On the sign in page there should now be a GitHub icon below the regular sign in form. +Click the icon to begin the authentication process. GitHub will ask the user to sign in and authorize the GitLab application. +If everything goes well the user will be returned to GitLab and will be signed in. diff --git a/doc/integration/github_app.png b/doc/integration/github_app.png index c0873b2e20d142779e3e2642a176bc2bdcd4899d..d890345ced917f2c78ae77dc39c2b66d1362e0d5 100644 GIT binary patch literal 75297 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 literal 75607 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%0D1mydEHTVILjFa^bqeP_{%Tx;ym}^pjw{U>Df|;7 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 diff --git a/doc/integration/gitlab.md b/doc/integration/gitlab.md new file mode 100644 index 0000000000..216f1f11a9 --- /dev/null +++ b/doc/integration/gitlab.md @@ -0,0 +1,84 @@ +# Integrate your server with GitLab.com + +Import projects from GitLab.com and login to your GitLab instance with your GitLab.com account. + +To enable the GitLab.com OmniAuth provider you must register your application with GitLab.com. +GitLab.com will generate an application ID and secret key for you to use. + +1. Sign in to GitLab.com + +1. Navigate to your profile settings. + +1. Select "Applications" in the left menu. + +1. Select "New application". + +1. Provide the required details. + - Name: This can be anything. Consider something like "\'s GitLab" or "\'s GitLab" or something else descriptive. + - Redirect URI: + + ``` + http://your-gitlab.example.com/import/gitlab/callback + http://your-gitlab.example.com/users/auth/gitlab/callback + ``` + + The first link is required for the importer and second for the authorization. + +1. Select "Submit". + +1. You should now see a Client ID and Client Secret near the top right of the page (see screenshot). + Keep this page open as you continue configuration. + ![GitLab app](gitlab_app.png) + +1. On your GitLab server, open the configuration file. + + For omnibus package: + + ```sh + sudo editor /etc/gitlab/gitlab.rb + ``` + + For instalations from source: + + ```sh + cd /home/git/gitlab + + sudo -u git -H editor config/gitlab.yml + ``` + +1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings. + +1. Add the provider configuration: + + For omnibus package: + + ```ruby + gitlab_rails['omniauth_providers'] = [ + { + "name" => "gitlab", + "app_id" => "YOUR_APP_ID", + "app_secret" => "YOUR_APP_SECRET", + "args" => { "scope" => "api" } + } + ] + ``` + + For installations from source: + + ``` + - { name: 'gitlab', app_id: 'YOUR_APP_ID', + app_secret: 'YOUR_APP_SECRET', + args: { scope: 'api' } } + ``` + +1. Change 'YOUR_APP_ID' to the Application ID from the GitLab.com application page. + +1. Change 'YOUR_APP_SECRET' to the secret from the GitLab.com application page. + +1. Save the configuration file. + +1. Restart GitLab for the changes to take effect. + +On the sign in page there should now be a GitLab.com icon below the regular sign in form. +Click the icon to begin the authentication process. GitLab.com will ask the user to sign in and authorize the GitLab application. +If everything goes well the user will be returned to your GitLab instance and will be signed in. diff --git a/doc/integration/gitlab_actions.png b/doc/integration/gitlab_actions.png new file mode 100644 index 0000000000000000000000000000000000000000..b08f54d137bd9ab1bde8471211f95016a9ee6aa0 GIT binary patch literal 17321 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 "google_oauth2", + "app_id" => "YOUR_APP_ID", + "app_secret" => "YOUR_APP_SECRET", + "args" => { "access_type" => "offline", "approval_prompt" => '' } + } + ] + ``` + + For installations from source: + + ``` + - { name: 'google_oauth2', app_id: 'YOUR_APP_ID', + app_secret: 'YOUR_APP_SECRET', + args: { access_type: 'offline', approval_prompt: '' } } + ``` + +1. Change 'YOUR_APP_ID' to the client ID from the Google Developer page from step 10. + +1. Change 'YOUR_APP_SECRET' to the client secret from the Google Developer page from step 10. 1. Save the configuration file. diff --git a/doc/integration/ldap.md b/doc/integration/ldap.md index 62bb957d95..b67f793c59 100644 --- a/doc/integration/ldap.md +++ b/doc/integration/ldap.md @@ -6,6 +6,103 @@ The first time a user signs in with LDAP credentials, GitLab will create a new G GitLab user attributes such as nickname and email will be copied from the LDAP user entry. +## Configuring GitLab for LDAP integration + +To enable GitLab LDAP integration you need to add your LDAP server settings in `/etc/gitlab/gitlab.rb` or `/home/git/gitlab/config/gitlab.yml`. +In GitLab Enterprise Edition you can have multiple LDAP servers connected to one GitLab server. + +Please note that before version 7.4, GitLab used a different syntax for configuring LDAP integration. +The old LDAP integration syntax still works in GitLab 7.4. +If your `gitlab.rb` or `gitlab.yml` file contains LDAP settings in both the old syntax and the new syntax, only the __old__ syntax will be used by GitLab. + +```ruby +# For omnibus packages +gitlab_rails['ldap_enabled'] = true +gitlab_rails['ldap_servers'] = YAML.load <<-EOS # remember to close this block with 'EOS' below +main: # 'main' is the GitLab 'provider ID' of this LDAP server + ## label + # + # A human-friendly name for your LDAP server. It is OK to change the label later, + # for instance if you find out it is too large to fit on the web page. + # + # Example: 'Paris' or 'Acme, Ltd.' + label: 'LDAP' + + host: '_your_ldap_server' + port: 389 + uid: 'sAMAccountName' + method: 'plain' # "tls" or "ssl" or "plain" + bind_dn: '_the_full_dn_of_the_user_you_will_bind_with' + password: '_the_password_of_the_bind_user' + + # This setting specifies if LDAP server is Active Directory LDAP server. + # For non AD servers it skips the AD specific queries. + # If your LDAP server is not AD, set this to false. + active_directory: true + + # If allow_username_or_email_login is enabled, GitLab will ignore everything + # after the first '@' in the LDAP username submitted by the user on login. + # + # Example: + # - the user enters 'jane.doe@example.com' and 'p@ssw0rd' as LDAP credentials; + # - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'. + # + # If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to + # disable this setting, because the userPrincipalName contains an '@'. + allow_username_or_email_login: false + + # To maintain tight control over the number of active users on your GitLab installation, + # enable this setting to keep new users blocked until they have been cleared by the admin + # (default: false). + block_auto_created_users: false + + # Base where we can search for users + # + # Ex. ou=People,dc=gitlab,dc=example + # + base: '' + + # Filter LDAP users + # + # Format: RFC 4515 http://tools.ietf.org/search/rfc4515 + # Ex. (employeeType=developer) + # + # Note: GitLab does not support omniauth-ldap's custom filter syntax. + # + user_filter: '' + +# GitLab EE only: add more LDAP servers +# Choose an ID made of a-z and 0-9 . This ID will be stored in the database +# so that GitLab can remember which LDAP server a user belongs to. +# uswest2: +# label: +# host: +# .... +EOS +``` + +If you are getting 'Connection Refused' errors when trying to connect to the LDAP server please double-check the LDAP `port` and `method` settings used by GitLab. +Common combinations are `method: 'plain'` and `port: 389`, OR `method: 'ssl'` and `port: 636`. + +If you are using a GitLab installation from source you can find the LDAP settings in `/home/git/gitlab/config/gitlab.yml`: + +``` +production: + # snip... + ldap: + enabled: false + servers: + main: # 'main' is the GitLab 'provider ID' of this LDAP server + ## label + # + # A human-friendly name for your LDAP server. It is OK to change the label later, + # for instance if you find out it is too large to fit on the web page. + # + # Example: 'Paris' or 'Acme, Ltd.' + label: 'LDAP' + # snip... +``` + ## Enabling LDAP sign-in for existing GitLab users When a user signs in to GitLab with LDAP for the first time, and their LDAP email address is the primary email address of an existing GitLab user, then the LDAP DN will be associated with the existing user. @@ -17,3 +114,35 @@ In other words, if an existing GitLab user wants to enable LDAP sign-in for them GitLab recognizes the following LDAP attributes as email addresses: `mail`, `email` and `userPrincipalName`. If multiple LDAP email attributes are present, e.g. `mail: foo@bar.com` and `email: foo@example.com`, then the first attribute found wins -- in this case `foo@bar.com`. + +## Using an LDAP filter to limit access to your GitLab server + +If you want to limit all GitLab access to a subset of the LDAP users on your LDAP server you can set up an LDAP user filter. +The filter must comply with [RFC 4515](http://tools.ietf.org/search/rfc4515). + +```ruby +# For omnibus packages; new LDAP server syntax +gitlab_rails['ldap_servers'] = YAML.load <<-EOS +main: + # snip... + user_filter: '(employeeType=developer)' +EOS +``` + +```yaml +# For installations from source; new LDAP server syntax +production: + ldap: + servers: + main: + # snip... + user_filter: '(employeeType=developer)' +``` + +Tip: if you want to limit access to the nested members of an Active Directory group you can use the following syntax: + +``` +(memberOf:1.2.840.113556.1.4.1941:=CN=My Group,DC=Example,DC=com) +``` + +Please note that GitLab does not support the custom filter syntax used by omniauth-ldap. diff --git a/doc/integration/oauth_provider.md b/doc/integration/oauth_provider.md new file mode 100644 index 0000000000..192c321f71 --- /dev/null +++ b/doc/integration/oauth_provider.md @@ -0,0 +1,35 @@ +## GitLab as OAuth2 authentication service provider + +This document is about using GitLab as an OAuth authentication service provider to sign into other services. +If you want to use other OAuth authentication service providers to sign into GitLab please see the [OAuth2 client documentation](../api/oauth2.md) + +OAuth2 provides client applications a 'secure delegated access' to server resources on behalf of a resource owner. Or you can allow users to sign in to your application with their GitLab.com account. +In fact OAuth allows to issue access token to third-party clients by an authorization server, +with the approval of the resource owner, or end-user. +Mostly, OAuth2 is using for SSO (Single sign-on). But you can find a lot of different usages for this functionality. +For example, our feature 'GitLab Importer' is using OAuth protocol to give an access to repositories without sharing user credentials to GitLab.com account. +Also GitLab.com application can be used for authentication to your GitLab instance if needed [GitLab OmniAuth](gitlab.md). + +GitLab has two ways to add new OAuth2 application to an instance, you can add application as regular user and through admin area. So GitLab actually can have an instance-wide and a user-wide applications. There is no defferences between them except the different permission levels. + +### Adding application through profile +Go to your profile section 'Application' and press button 'New Application' + +![applications](oauth_provider/user_wide_applications.png) + +After this you will see application form, where "Name" is arbitrary name, "Redirect URI" is URL in your app where users will be sent after authorization on GitLab.com. + +![application_form](oauth_provider/application_form.png) + +### Authorized application +Every application you authorized will be shown in your "Authorized application" sections. + +![authorized_application](oauth_provider/authorized_application.png) + +At any time you can revoke access just clicking button "Revoke" + +### OAuth applications in admin area + +If you want to create application that does not belong to certain user you can create it from admin area + +![admin_application](oauth_provider/admin_application.png) \ No newline at end of file diff --git a/doc/integration/oauth_provider/admin_application.png b/doc/integration/oauth_provider/admin_application.png new file mode 100644 index 0000000000000000000000000000000000000000..a5f34512aa85236ac9ecc7d70129d8a3a3f1a71b GIT binary patch literal 55533 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 z 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 literal 0 HcmV?d00001 diff --git a/doc/integration/oauth_provider/authorized_application.png b/doc/integration/oauth_provider/authorized_application.png new file mode 100644 index 0000000000000000000000000000000000000000..d3ce05be9cc71cf965a8941313057f6d0851b085 GIT binary patch literal 17260 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�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 literal 0 HcmV?d00001 diff --git a/doc/integration/omniauth.md b/doc/integration/omniauth.md index 1b0bf9c5f6..24f7b4bb4b 100644 --- a/doc/integration/omniauth.md +++ b/doc/integration/omniauth.md @@ -1,18 +1,47 @@ # OmniAuth -GitLab leverages OmniAuth to allow users to sign in using Twitter, GitHub, and other popular services. Configuring +GitLab leverages OmniAuth to allow users to sign in using Twitter, GitHub, and other popular services. -OmniAuth does not prevent standard GitLab authentication or LDAP (if configured) from continuing to work. Users can choose to sign in using any of the configured mechanisms. +Configuring OmniAuth does not prevent standard GitLab authentication or LDAP (if configured) from continuing to work. Users can choose to sign in using any of the configured mechanisms. - [Initial OmniAuth Configuration](#initial-omniauth-configuration) - [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) ## Initial OmniAuth Configuration -Before configuring individual OmniAuth providers there are a few global settings that need to be verified. +Before configuring individual OmniAuth providers there are a few global settings that are in common for all providers that we need to consider. -1. Open the configuration file. +- Omniauth needs to be enabled, see details below for example. +- `allow_single_sign_on` defaults to `false`. If `false` users must be created manually or they will not be able to +sign in via OmniAuth. +- `block_auto_created_users` defaults to `true`. If `true` auto created users will be blocked by default and will +have to be unblocked by an administrator before they are able to sign in. +- **Note:** If you set `allow_single_sign_on` to `true` and `block_auto_created_users` to `false` please be aware +that any user on the Internet will be able to successfully sign in to your GitLab without administrative approval. + +If you want to change these settings: + +* **For omnibus package** + + Open the configuration file: + + ```sh + sudo editor /etc/gitlab/gitlab.rb + ``` + + and change + + ``` + gitlab_rails['omniauth_enabled'] = true + gitlab_rails['omniauth_allow_single_sign_on'] = false + gitlab_rails['block_auto_created_users'] = true + ``` + +* **For installations from source** + + Open the configuration file: ```sh cd /home/git/gitlab @@ -20,13 +49,13 @@ Before configuring individual OmniAuth providers there are a few global settings sudo -u git -H editor config/gitlab.yml ``` -1. Find the section dealing with OmniAuth. The section will look similar to the following. + and change the following section ``` - ## OmniAuth settings + ## OmniAuth settings omniauth: # Allow login via Twitter, Google, etc. using OmniAuth providers - enabled: false + enabled: true # CAUTION! # This allows users to login without having a user account first (default: false). @@ -34,41 +63,17 @@ Before configuring individual OmniAuth providers there are a few global settings allow_single_sign_on: false # Locks down those users until they have been cleared by the admin (default: true). block_auto_created_users: true - - ## Auth providers - # Uncomment the following lines and fill in the data of the auth provider you want to use - # If your favorite auth provider is not listed you can use others: - # see https://github.com/gitlabhq/gitlab-public-wiki/wiki/Custom-omniauth-provider-configurations - # The 'app_id' and 'app_secret' parameters are always passed as the first two - # arguments, followed by optional 'args' which can be either a hash or an array. - providers: - # - { name: 'google_oauth2', app_id: 'YOUR APP ID', - # app_secret: 'YOUR APP SECRET', - # args: { access_type: 'offline', approval_prompt: '' } } - # - { name: 'twitter', app_id: 'YOUR APP ID', - # app_secret: 'YOUR APP SECRET'} - # - { name: 'github', app_id: 'YOUR APP ID', - # app_secret: 'YOUR APP SECRET', - # args: { scope: 'user:email' } } ``` -1. Change `enabled` to `true`. - -1. Consider the next two configuration options: `allow_single_sign_on` and `block_auto_created_users`. - - - `allow_single_sign_on` defaults to `false`. If `false` users must be created manually or they will not be able to - sign in via OmniAuth. - - `block_auto_created_users` defaults to `true`. If `true` auto created users will be blocked by default and will - have to be unblocked by an administrator before they are able to sign in. - - **Note:** If you set `allow_single_sign_on` to `true` and `block_auto_created_users` to `false` please be aware - that any user on the Internet will be able to successfully sign in to your GitLab without administrative approval. - -1. Choose one or more of the Supported Providers below to continue configuration. +Now we can choose one or more of the Supported Providers below to continue configuration. ## Supported Providers - [GitHub](github.md) +- [Bitbucket](bitbucket.md) +- [GitLab.com](gitlab.md) - [Google](google.md) +- [Shibboleth](shibboleth.md) - [Twitter](twitter.md) ## Enable OmniAuth for an Existing User @@ -82,3 +87,41 @@ Existing users can enable OmniAuth for specific providers after the account is c 1. The user will be redirected to the provider. Once the user authorized GitLab they will be redirected back to GitLab. The chosen OmniAuth provider is now active and can be used to sign in to GitLab from then on. + +## Using Custom Omniauth Providers + +GitLab uses [Omniauth](http://www.omniauth.org/) for authentication and already ships with a few providers preinstalled (e.g. LDAP, GitHub, Twitter). But sometimes that is not enough and you need to integrate with other authentication solutions. For these cases you can use the Omniauth provider. + +### Steps + +These steps are fairly general and you will need to figure out the exact details from the Omniauth provider's documentation. + +- Stop GitLab: + + sudo service gitlab stop + +- Add the gem to your [Gemfile](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/Gemfile): + + gem "omniauth-your-auth-provider" + +- If you're using MySQL, install the new Omniauth provider gem by running the following command: + + sudo -u git -H bundle install --without development test postgres --path vendor/bundle --no-deployment + +- If you're using PostgreSQL, install the new Omniauth provider gem by running the following command: + + sudo -u git -H bundle install --without development test mysql --path vendor/bundle --no-deployment + + > These are the same commands you used in the [Install Gems section](#install-gems) with `--path vendor/bundle --no-deployment` instead of `--deployment`. + +- Start GitLab: + + sudo service gitlab start + +### Examples + +If you have successfully set up a provider that is not shipped with GitLab itself, please let us know. + +You can help others by reporting successful configurations and probably share a few insights or provide warnings for common errors or pitfalls by sharing your experience [in the public Wiki](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Custom-omniauth-provider-configurations). + +While we can't officially support every possible authentication mechanism out there, we'd like to at least help those with specific needs. diff --git a/doc/integration/redmine_configuration.png b/doc/integration/redmine_configuration.png new file mode 100644 index 0000000000000000000000000000000000000000..6b1453632293f010e2349a98270e99d5ac60c39d GIT binary patch literal 118752 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≥#twI;4!`6qE1vW|;0VMY+|HSls?_%;-hI24B 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�zc&{$pDD&p+v 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 literal 0 HcmV?d00001 diff --git a/doc/integration/redmine_service_template.png b/doc/integration/redmine_service_template.png new file mode 100644 index 0000000000000000000000000000000000000000..1159eb5b9649f1efdd513e224931731bea99fbd3 GIT binary patch literal 198077 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 + AuthType shibboleth + ShibRequestSetting requireSession 1 + ShibUseHeaders On + require valid-user + + + Alias /shibboleth-sp /usr/share/shibboleth + + Satisfy any + + + + SetHandler shib + +``` +exclude shibboleth URLs from rewriting, add "RewriteCond %{REQUEST_URI} !/Shibboleth.sso" and "RewriteCond %{REQUEST_URI} !/shibboleth-sp", config should look like this: +``` + # Apache equivalent of Nginx try files + RewriteEngine on + RewriteCond %{DOCUMENT_ROOT}/%{REQUEST_FILENAME} !-f + RewriteCond %{REQUEST_URI} !/Shibboleth.sso + RewriteCond %{REQUEST_URI} !/shibboleth-sp + RewriteRule .* http://127.0.0.1:8080%{REQUEST_URI} [P,QSA] + RequestHeader set X_FORWARDED_PROTO 'https' +``` + +1. Edit /etc/gitlab/gitlab.rb configuration file, your shibboleth attributes should be in form of "HTTP_ATTRIBUTE" and you should addjust them to your need and environment. Add any other configuration you need. + +File should look like this: +``` +external_url 'https://gitlab.example.com' +gitlab_rails['internal_api_url'] = 'https://gitlab.example.com' + +# disable Nginx +nginx['enable'] = false + +gitlab_rails['omniauth_allow_single_sign_on'] = true +gitlab_rails['omniauth_block_auto_created_users'] = false +gitlab_rails['omniauth_enabled'] = true +gitlab_rails['omniauth_providers'] = [ + { + "name" => 'shibboleth', + "args" => { + "shib_session_id_field" => "HTTP_SHIB_SESSION_ID", + "shib_application_id_field" => "HTTP_SHIB_APPLICATION_ID", + "uid_field" => 'HTTP_EPPN', + "name_field" => 'HTTP_CN', + "info_fields" => { "email" => 'HTTP_MAIL'} + } + } +] + +``` +1. Save changes and reconfigure gitlab: +``` +sudo gitlab-ctl reconfigure +``` + +On the sign in page there should now be a "Sign in with: Shibboleth" icon below the regular sign in form. Click the icon to begin the authentication process. You will be redirected to IdP server (Depends on your Shibboleth module configuration). If everything goes well the user will be returned to GitLab and will be signed in. diff --git a/doc/integration/slack.md b/doc/integration/slack.md index 95cb0c6fae..2fd22c513a 100644 --- a/doc/integration/slack.md +++ b/doc/integration/slack.md @@ -4,15 +4,23 @@ To enable Slack integration you must create an Incoming WebHooks integration on Slack; -1. Sign in to [Slack](https://slack.com) (https://YOURSUBDOMAIN.slack.com/services) -1. Click on the Integrations menu at the top of the page. -1. Add a new Integration. -1. Pick Incoming WebHooks -1. Choose the channel name you want to send notifications to, in the Settings section -1. Add Integrations. - - Optional step; You can change bot's name and avatar by clicking "change the name of your bot", and "change the icon" after that you have to click "Save settings". +1. [Sign in to Slack](https://slack.com/signin) + +1. Select **Configure Integrations** from the dropdown next to your team name. + +1. Select the **All Services** tab + +1. Click **Add** next to Incoming Webhooks + +1. Pick Incoming WebHooks + +1. Choose the channel name you want to send notifications to + +1. Click **Add Incoming WebHooks Integration**Add Integrations. + - Optional step; You can change bot's name and avatar by clicking modifying the bot name or avatar under **Integration Settings**. + +1. Copy the **Webhook URL**, we'll need this later for GitLab. -Now, Slack is ready to get external hooks. Before you leave this page don't forget to get the Token that you'll need on GitLab. You can find it by clicking Expand button, located in the "Instructions for creating Incoming WebHooks" section. It's a random alpha-numeric text 24 characters long. ## On GitLab @@ -26,10 +34,8 @@ After Slack is ready we need to setup GitLab. Here are the steps to achieve this 1. Fill in your Slack details - - Mark as active it - - Type your subdomain's prefix (If your subdomain is https://somedomain.slack.com you only have to type the somedomain) - - Type in the token you got from Slack - - Type in the channel name you want to use (eg. #announcements) + - Mark it as active + - Paste in the webhook URL you got from Slack Have fun :) diff --git a/doc/integration/twitter.md b/doc/integration/twitter.md index d1b52927d3..fe9091ad9a 100644 --- a/doc/integration/twitter.md +++ b/doc/integration/twitter.md @@ -13,7 +13,7 @@ To enable the Twitter OmniAuth provider you must register your application with something else descriptive. - Description: Create a description. - Website: The URL to your GitLab installation. 'https://gitlab.example.com' - - Callback URL: 'https://gitlab.example.com/users/auth/github/callback' + - Callback URL: 'https://gitlab.example.com/users/auth/twitter/callback' - Agree to the "Rules of the Road." ![Twitter App Details](twitter_app_details.png) @@ -33,25 +33,46 @@ To enable the Twitter OmniAuth provider you must register your application with 1. On your GitLab server, open the configuration file. + For omnibus package: + ```sh - cd /home/git/gitlab - - sudo -u git -H editor config/gitlab.yml + sudo editor /etc/gitlab/gitlab.rb ``` -1. Find the section dealing with OmniAuth. See [Initial OmniAuth Configuration](README.md#initial-omniauth-configuration) -for more details. + For instalations from source: -1. Under `providers:` uncomment (or add) lines that look like the following: + ```sh + cd /home/git/gitlab - ``` - - { name: 'twitter', app_id: 'YOUR APP ID', - app_secret: 'YOUR APP SECRET' } + sudo -u git -H editor config/gitlab.yml ``` -1. Change 'YOUR APP ID' to the API key from Twitter page in step 11. +1. See [Initial OmniAuth Configuration](omniauth.md#initial-omniauth-configuration) for initial settings. -1. Change 'YOUR APP SECRET' to the API secret from the Twitter page in step 11. +1. Add the provider configuration: + + For omnibus package: + + ```ruby + gitlab_rails['omniauth_providers'] = [ + { + "name" => "twitter", + "app_id" => "YOUR_APP_ID", + "app_secret" => "YOUR_APP_SECRET" + } + ] + ``` + + For installations from source: + + ``` + - { name: 'twitter', app_id: 'YOUR_APP_ID', + app_secret: 'YOUR_APP_SECRET' } + ``` + +1. Change 'YOUR_APP_ID' to the API key from Twitter page in step 11. + +1. Change 'YOUR_APP_SECRET' to the API secret from the Twitter page in step 11. 1. Save the configuration file. diff --git a/doc/logs/logs.md b/doc/logs/logs.md new file mode 100644 index 0000000000..ec0109a426 --- /dev/null +++ b/doc/logs/logs.md @@ -0,0 +1,102 @@ +## 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) + +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. + +#### production.log +This file lives in `/var/log/gitlab/gitlab-rails/production.log` for omnibus package or in `/home/git/gitlab/logs/production.log` for installations from the source. + +This file contains information about all performed requests. You can see url and type of request, IP address and what exactly parts of code were involved to service this particular request. Also you can see all SQL request that have been performed and how much time it took. +This task is more useful for GitLab contributors and developers. Use part of this log file when you are going to report bug. + +``` +Started GET "/gitlabhq/yaml_db/tree/master" for 168.111.56.1 at 2015-02-12 19:34:53 +0200 +Processing by Projects::TreeController#show as HTML + Parameters: {"project_id"=>"gitlabhq/yaml_db", "id"=>"master"} + + ... [CUT OUT] + + amespaces"."created_at" DESC, "namespaces"."id" DESC LIMIT 1 [["id", 26]] + CACHE (0.0ms) SELECT "members".* FROM "members" WHERE "members"."source_type" = 'Project' AND "members"."type" IN ('ProjectMember') AND "members"."source_id" = $1 AND "members"."source_type" = $2 AND "members"."user_id" = 1 ORDER BY "members"."created_at" DESC, "members"."id" DESC LIMIT 1 [["source_id", 18], ["source_type", "Project"]] + CACHE (0.0ms) SELECT "members".* FROM "members" WHERE "members"."source_type" = 'Project' AND "members". +  (1.4ms) SELECT COUNT(*) FROM "merge_requests" WHERE "merge_requests"."target_project_id" = $1 AND ("merge_requests"."state" IN ('opened','reopened')) [["target_project_id", 18]] + Rendered layouts/nav/_project.html.haml (28.0ms) + Rendered layouts/_collapse_button.html.haml (0.2ms) + Rendered layouts/_flash.html.haml (0.1ms) + Rendered layouts/_page.html.haml (32.9ms) +Completed 200 OK in 166ms (Views: 117.4ms | ActiveRecord: 27.2ms) +``` +In this example we can see that server processed HTTP request with url `/gitlabhq/yaml_db/tree/master` from IP 168.111.56.1 at 2015-02-12 19:34:53 +0200. Also we can see that request was processed by Projects::TreeController. + +#### application.log +This file lives in `/var/log/gitlab/gitlab-rails/application.log` for omnibus package or in `/home/git/gitlab/logs/application.log` for installations from the source. + +This log file helps you discover events happening in your instance such as user creation, project removing and so on. + +``` +October 06, 2014 11:56: User "Administrator" (admin@example.com) was created +October 06, 2014 11:56: Documentcloud created a new project "Documentcloud / Underscore" +October 06, 2014 11:56: Gitlab Org created a new project "Gitlab Org / Gitlab Ce" +October 07, 2014 11:25: User "Claudie Hodkiewicz" (nasir_stehr@olson.co.uk) was removed +October 07, 2014 11:25: Project "project133" was removed +``` +#### githost.log +This file lives in `/var/log/gitlab/gitlab-rails/githost.log` for omnibus package or in `/home/git/gitlab/logs/githost.log` for installations from the source. + +The GitLab has to interact with git repositories but in some rare cases something can go wrong and in this case you will know what exactly happened. This log file contains all failed requests from GitLab to git repository. In majority of cases this file will be useful for developers only. +``` +December 03, 2014 13:20 -> ERROR -> Command failed [1]: /usr/bin/git --git-dir=/Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/gitlab-satellites/group184/gitlabhq/.git --work-tree=/Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/gitlab-satellites/group184/gitlabhq merge --no-ff -mMerge branch 'feature_conflict' into 'feature' source/feature_conflict + +error: failed to push some refs to '/Users/vsizov/gitlab-development-kit/repositories/gitlabhq/gitlab_git.git' +``` + +#### satellites.log +This file lives in `/var/log/gitlab/gitlab-rails/satellites.log` for omnibus package or in `/home/git/gitlab/logs/satellites.log` for installations from the source. + +In some cases GitLab should perform write actions to git repository, for example when it is needed to merge the merge request or edit a file with online editor. If something went wrong you can look into this file to find out what exactly happened. +``` +October 07, 2014 11:36: Failed to create satellite for Chesley Weimann III / project1817 +October 07, 2014 11:36: PID: 1872: git clone /Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/repositories/conrad6841/gitlabhq.git /Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/gitlab-satellites/conrad6841/gitlabhq +October 07, 2014 11:36: PID: 1872: -> fatal: repository '/Users/vsizov/gitlab-development-kit/gitlab/tmp/tests/repositories/conrad6841/gitlabhq.git' does not exist +``` + +#### sidekiq.log +This file lives in `/var/log/gitlab/gitlab-rails/sidekiq.log` for omnibus package or in `/home/git/gitlab/logs/sidekiq.log` for installations from the source. + +GitLab uses background jobs for processing tasks which can take a long time. All information about processing these jobs are writing down to this file. +``` +2014-06-10T07:55:20Z 2037 TID-tm504 ERROR: /opt/bitnami/apps/discourse/htdocs/vendor/bundle/ruby/1.9.1/gems/redis-3.0.7/lib/redis/client.rb:228:in `read' +2014-06-10T18:18:26Z 14299 TID-55uqo INFO: Booting Sidekiq 3.0.0 with redis options {:url=>"redis://localhost:6379/0", :namespace=>"sidekiq"} +``` + +#### gitlab-shell.log +This file lives in `/var/log/gitlab/gitlab-shell/gitlab-shell.log` for omnibus package or in `/home/git/gitlab-shell/logs/sidekiq.log` for installations from the source. + +gitlab-shell is using by Gitlab for executing git commands and provide ssh access to git repositories. + +``` +I, [2015-02-13T06:17:00.671315 #9291] INFO -- : Adding project root/example.git at . +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. +``` + +#### unicorn_stderr.log +This file lives in `/var/log/gitlab/unicorn/unicorn_stderr.log` for omnibus package or in `/home/git/gitlab/logs/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. + +``` +I, [2015-02-13T06:14:46.680381 #9047] INFO -- : Refreshing Gem list +I, [2015-02-13T06:14:56.931002 #9047] INFO -- : listening on addr=127.0.0.1:8080 fd=12 +I, [2015-02-13T06:14:56.931381 #9047] INFO -- : listening on addr=/var/opt/gitlab/gitlab-rails/sockets/gitlab.socket fd=13 +I, [2015-02-13T06:14:56.936638 #9047] INFO -- : master process ready +I, [2015-02-13T06:14:56.946504 #9092] INFO -- : worker=0 spawned pid=9092 +I, [2015-02-13T06:14:56.946943 #9092] INFO -- : worker=0 ready +I, [2015-02-13T06:14:56.947892 #9094] INFO -- : worker=1 spawned pid=9094 +I, [2015-02-13T06:14:56.948181 #9094] INFO -- : worker=1 ready +W, [2015-02-13T07:16:01.312916 #9094] WARN -- : #: worker (pid: 9094) exceeds memory limit (320626688 bytes > 247066940 bytes) +W, [2015-02-13T07:16:01.313000 #9094] WARN -- : Unicorn::WorkerKiller send SIGQUIT (pid: 9094) alive: 3621 sec (trial 1) +I, [2015-02-13T07:16:01.530733 #9047] INFO -- : reaped # worker=1 +I, [2015-02-13T07:16:01.534501 #13379] INFO -- : worker=1 spawned pid=13379 +I, [2015-02-13T07:16:01.534848 #13379] INFO -- : worker=1 ready +``` diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md index 5627fd0659..1d5fd4c8b0 100644 --- a/doc/markdown/markdown.md +++ b/doc/markdown/markdown.md @@ -6,10 +6,11 @@ * [Newlines](#newlines) * [Multiple underscores in words](#multiple-underscores-in-words) -* [URL autolinking](#url-autolinking) +* [URL auto-linking](#url-auto-linking) * [Code and Syntax Highlighting](#code-and-syntax-highlighting) * [Emoji](#emoji) * [Special GitLab references](#special-gitlab-references) +* [Task lists](#task-lists) **[Standard Markdown](#standard-markdown)** @@ -39,20 +40,21 @@ You can use GFM in - milestones - wiki pages -You can also use other rich text files in GitLab. You might have to install a depency to do so. Please see the [github-markup gem readme](https://github.com/gitlabhq/markup#markups) for more information. +You can also use other rich text files in GitLab. You might have to install a dependency to do so. Please see the [github-markup gem readme](https://github.com/gitlabhq/markup#markups) for more information. ## Newlines GFM honors the markdown specification in how [paragraphs and line breaks are handled](http://daringfireball.net/projects/markdown/syntax#p). -A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines.: +A paragraph is simply one or more consecutive lines of text, separated by one or more blank lines. +Line-breaks, or softreturns, are rendered if you end a line with two or more spaces - Roses are red + Roses are red [followed by two or more spaces] Violets are blue Sugar is sweet -Roses are red +Roses are red Violets are blue Sugar is sweet @@ -67,7 +69,7 @@ It is not reasonable to italicize just _part_ of a word, especially when you're perform_complicated_task do_this_and_do_that_and_another_thing -## URL autolinking +## URL auto-linking GFM will autolink standard URLs you copy and paste into your text. So if you want to link to a URL (instead of a textural link), you can simply put the URL in verbatim and it will be turned into a link to that URL. @@ -139,25 +141,25 @@ But let's throw in a tag. ## Emoji - Sometimes you want to be a :ninja: and add some :glowing_star: to your :speech_balloon:. Well we have a gift for you: + Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you: - :high_voltage_sign: You can use emoji anywhere GFM is supported. :victory_hand: + :zap: You can use emoji anywhere GFM is supported. :v: - You can use it to point out a :bug: or warn about :speak_no_evil_monkey: patches. And if someone improves your really :snail: code, send them some :cake:. People will :heart: you for that. + You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that. - If you are new to this, don't be :fearful_face:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes. + If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes. - Consult the [Emoji Cheat Sheet](https://www.dropbox.com/s/b9xaqb977s6d8w1/cheat_sheet.pdf) for a list of all supported emoji codes. :thumbsup: + Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported emoji codes. :thumbsup: -Sometimes you want to be a :ninja: and add some :glowing_star: to your :speech_balloon:. Well we have a gift for you: +Sometimes you want to :monkey: around a bit and add some :star2: to your :speech_balloon:. Well we have a gift for you: -:high_voltage_sign: You can use emoji anywhere GFM is supported. :victory_hand: +:zap: You can use emoji anywhere GFM is supported. :v: -You can use it to point out a :bug: or warn about :speak_no_evil_monkey: patches. And if someone improves your really :snail: code, send them some :cake:. People will :heart: you for that. +You can use it to point out a :bug: or warn about :speak_no_evil: patches. And if someone improves your really :snail: code, send them some :birthday:. People will :heart: you for that. -If you are new to this, don't be :fearful_face:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes. +If you are new to this, don't be :fearful:. You can easily join the emoji :family:. All you need to do is to look up on the supported codes. -Consult the [Emoji Cheat Sheet](https://www.dropbox.com/s/b9xaqb977s6d8w1/cheat_sheet.pdf) for a list of all supported emoji codes. :thumbsup: +Consult the [Emoji Cheat Sheet](http://emoji.codes) for a list of all supported emoji codes. :thumbsup: ## Special GitLab References @@ -169,7 +171,7 @@ GFM will turn that reference into a link so you can navigate between them easily GFM will recognize the following: -- @foo : for team members +- @foo : for specific team members or groups - @all : for the whole team - #123 : for issues - !123 : for merge requests @@ -177,6 +179,24 @@ GFM will recognize the following: - 1234567 : for commits - \[file\](path/to/file) : for file references +GFM also recognizes references to commits, issues, and merge requests in other projects: + +- namespace/project#123 : for issues +- namespace/project!123 : for merge requests +- namespace/project@1234567 : for commits + +## Task Lists + +You can add task lists to merge request and issue descriptions to keep track of to-do items. To create a task, add an unordered list to the description in an issue or merge request, formatted like so: + +```no-highlight +* [x] Completed task +* [ ] Unfinished task + * [x] Nested task +``` + +Task lists can only be created in descriptions, not in titles or comments. Task item state can be managed by editing the description's Markdown or by clicking the rendered checkboxes. + # Standard Markdown ## Headers @@ -231,17 +251,17 @@ The IDs are generated from the content of the header according to the following For example: ``` -###### ..Ab_c-d. e [anchor](url) ![alt text](url).. +###### ..Ab_c-d. e [anchor](URL) ![alt text](URL).. ``` which renders as: -###### ..Ab_c-d. e [anchor](url) ![alt text](url).. +###### ..Ab_c-d. e [anchor](URL) ![alt text](URL).. will first be converted by step 1) into a string like: ``` -..Ab_c-d. e <a href="url">anchor</a> <img src="url" alt="alt text"/>.. +..Ab_c-d. e <a href="URL">anchor</a> <img src="URL" alt="alt text"/>.. ``` After removing the tags in step 2) we get: @@ -258,8 +278,8 @@ ab_c-d-e-anchor Note in particular how: -- for markdown anchors `[text](url)`, only the `text` is used -- markdown images `![alt](url)` are completely ignored +- for markdown anchors `[text](URL)`, only the `text` is used +- markdown images `![alt](URL)` are completely ignored ## Emphasis @@ -401,6 +421,8 @@ Quote break. You can also use raw HTML in your Markdown, and it'll mostly work pretty well. +See the documentation for HTML::Pipeline's [SanitizationFilter](http://www.rubydoc.info/gems/html-pipeline/HTML/Pipeline/SanitizationFilter#WHITELIST-constant) class for the list of allowed HTML tags and attributes. In addition to the default `SanitizationFilter` whitelist, GitLab allows the `class`, `id`, and `style` attributes. + ```no-highlight
    Definition list
    @@ -464,6 +486,10 @@ This line is separated from the one above by two newlines, so it will be a *sepa This line is also a separate paragraph, but... This line is only separated by a single newline, so it's a separate line in the *same paragraph*. + +This line is also a separate paragraph, and... +This line is on its own line, because the previous line ends with two +spaces. ``` Here's a line for us to start with. @@ -473,6 +499,10 @@ This line is separated from the one above by two newlines, so it will be a *sepa This line is also begins a separate paragraph, but... This line is only separated by a single newline, so it's a separate line in the *same paragraph*. +This line is also a separate paragraph, and... +This line is on its own line, because the previous line ends with two +spaces. + ## Tables Tables aren't part of the core Markdown spec, but they are part of GFM and Markdown Here supports them. @@ -491,6 +521,10 @@ Code above produces next output: | cell 1 | cell 2 | | cell 3 | cell 4 | +**Note** + +The row of dashes between the table header and body must have at least three dashes in each column. + ## References - This document leveraged heavily from the [Markdown-Cheatsheet](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet). diff --git a/doc/operations/README.md b/doc/operations/README.md new file mode 100644 index 0000000000..f1456c6c8e --- /dev/null +++ b/doc/operations/README.md @@ -0,0 +1,4 @@ +# GitLab operations + +- [Sidekiq MemoryKiller](sidekiq_memory_killer.md) +- [Cleaning up Redis sessions](cleaning_up_redis_sessions.md) diff --git a/doc/operations/cleaning_up_redis_sessions.md b/doc/operations/cleaning_up_redis_sessions.md new file mode 100644 index 0000000000..93521e976d --- /dev/null +++ b/doc/operations/cleaning_up_redis_sessions.md @@ -0,0 +1,52 @@ +# Cleaning up stale Redis sessions + +Since version 6.2, GitLab stores web user sessions as key-value pairs in Redis. +Prior to GitLab 7.3, user sessions did not automatically expire from Redis. If +you have been running a large GitLab server (thousands of users) since before +GitLab 7.3 we recommend cleaning up stale sessions to compact the Redis +database after you upgrade to GitLab 7.3. You can also perform a cleanup while +still running GitLab 7.2 or older, but in that case new stale sessions will +start building up again after you clean up. + +In GitLab versions prior to 7.3.0, the session keys in Redis are 16-byte +hexadecimal values such as '976aa289e2189b17d7ef525a6702ace9'. Starting with +GitLab 7.3.0, the keys are +prefixed with 'session:gitlab:', so they would look like +'session:gitlab:976aa289e2189b17d7ef525a6702ace9'. Below we describe how to +remove the keys in the old format. + +First we define a shell function with the proper Redis connection details. + +``` +rcli() { + # This example works for Omnibus installations of GitLab 7.3 or newer. For an + # installation from source you will have to change the socket path and the + # path to redis-cli. + sudo /opt/gitlab/embedded/bin/redis-cli -s /var/opt/gitlab/redis/redis.socket "$@" +} + +# test the new shell function; the response should be PONG +rcli ping +``` + +Now we do a search to see if there are any session keys in the old format for +us to clean up. + +``` +# returns the number of old-format session keys in Redis +rcli keys '*' | grep '^[a-f0-9]\{32\}$' | wc -l +``` + +If the number is larger than zero, you can proceed to expire the keys from +Redis. If the number is zero there is nothing to clean up. + +``` +# Tell Redis to expire each matched key after 600 seconds. +rcli keys '*' | grep '^[a-f0-9]\{32\}$' | awk '{ print "expire", $0, 600 }' | rcli +# This will print '(integer) 1' for each key that gets expired. +``` + +Over the next 15 minutes (10 minutes expiry time plus 5 minutes Redis +background save interval) your Redis database will be compacted. If you are +still using GitLab 7.2, users who are not clicking around in GitLab during the +10 minute expiry window will be signed out of GitLab. diff --git a/doc/operations/sidekiq_memory_killer.md b/doc/operations/sidekiq_memory_killer.md new file mode 100644 index 0000000000..867b01b0d5 --- /dev/null +++ b/doc/operations/sidekiq_memory_killer.md @@ -0,0 +1,38 @@ +# Sidekiq MemoryKiller + +The GitLab Rails application code suffers from memory leaks. For web requests +this problem is made manageable using +[unicorn-worker-killer](https://github.com/kzk/unicorn-worker-killer) which +restarts Unicorn worker processes in between requests when needed. The Sidekiq +MemoryKiller applies the same approach to the Sidekiq processes used by GitLab +to process background jobs. + +Unlike unicorn-worker-killer, which is enabled by default for all GitLab +installations since GitLab 6.4, the Sidekiq MemoryKiller is enabled by default +_only_ for Omnibus packages. The reason for this is that the MemoryKiller +relies on Runit to restart Sidekiq after a memory-induced shutdown and GitLab +installations from source do not all use Runit or an equivalent. + +With the default settings, the MemoryKiller will cause a Sidekiq restart no +more often than once every 15 minutes, with the restart causing about one +minute of delay for incoming background jobs. + +## Configuring the MemoryKiller + +The MemoryKiller is controlled using environment variables. + +- `SIDEKIQ_MEMORY_KILLER_MAX_RSS`: if this variable is set, and its value is + greater than 0, then after each Sidekiq job, the MemoryKiller will check the + RSS of the Sidekiq process that executed the job. If the RSS of the Sidekiq + process (expressed in kilobytes) exceeds SIDEKIQ_MEMORY_KILLER_MAX_RSS, a + delayed shutdown is triggered. The default value for Omnibus packages is set + [in the omnibus-gitlab + repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/files/gitlab-cookbooks/gitlab/attributes/default.rb). +- `SIDEKIQ_MEMORY_KILLER_GRACE_TIME`: defaults 900 seconds (15 minutes). When + a shutdown is triggered, the Sidekiq process will keep working normally for + another 15 minutes. +- `SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT`: defaults to 30 seconds. When the grace + time has expired, the MemoryKiller tells Sidekiq to stop accepting new jobs. + Existing jobs get 30 seconds to finish. After that, the MemoryKiller tells + Sidekiq to shut down, and an external supervision mechanism (e.g. Runit) must + restart Sidekiq. diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md index db22b7dbe5..8cfa7f9c87 100644 --- a/doc/permissions/permissions.md +++ b/doc/permissions/permissions.md @@ -8,7 +8,6 @@ If a user is a GitLab administrator they receive all permissions. ## Project - | Action | Guest | Reporter | Developer | Master | Owner | |---------------------------------------|---------|------------|-------------|----------|--------| | Create new issue | ✓ | ✓ | ✓ | ✓ | ✓ | @@ -19,6 +18,7 @@ If a user is a GitLab administrator they receive all permissions. | Create new merge request | | | ✓ | ✓ | ✓ | | Create new branches | | | ✓ | ✓ | ✓ | | Push to non-protected branches | | | ✓ | ✓ | ✓ | +| Force push to non-protected branches | | | ✓ | ✓ | ✓ | | Remove non-protected branches | | | ✓ | ✓ | ✓ | | Add tags | | | ✓ | ✓ | ✓ | | Write a wiki | | | ✓ | ✓ | ✓ | @@ -27,17 +27,25 @@ If a user is a GitLab administrator they receive all permissions. | Create new milestones | | | | ✓ | ✓ | | Add new team members | | | | ✓ | ✓ | | Push to protected branches | | | | ✓ | ✓ | -| Enable/Disable branch protection | | | | ✓ | ✓ | +| Enable/disable branch protection | | | | ✓ | ✓ | +| Turn on/off prot. branch push for devs| | | | ✓ | ✓ | | Rewrite/remove git tags | | | | ✓ | ✓ | | Edit project | | | | ✓ | ✓ | -| Add Deploy Keys to project | | | | ✓ | ✓ | -| Configure Project Hooks | | | | ✓ | ✓ | +| Add deploy keys to project | | | | ✓ | ✓ | +| Configure project hooks | | | | ✓ | ✓ | | Switch visibility level | | | | | ✓ | | Transfer project to another namespace | | | | | ✓ | | Remove project | | | | | ✓ | +| Force push to protected branches | | | | | | +| Remove protected branches | | | | | | ## Group +In order for a group to appear as public and be browsable, it must contain at +least one public project. + +Any user can remove themselves from a group, unless they are the last Owner of the group. + | Action | Guest | Reporter | Developer | Master | Owner | |-------------------------|-------|----------|-----------|--------|-------| | Browse group | ✓ | ✓ | ✓ | ✓ | ✓ | @@ -45,5 +53,3 @@ If a user is a GitLab administrator they receive all permissions. | Create project in group | | | | ✓ | ✓ | | Manage group members | | | | | ✓ | | Remove group | | | | | ✓ | - -Any user can remove himself from a group, unless he is the last Owner of the group. diff --git a/doc/project_services/bamboo.md b/doc/project_services/bamboo.md new file mode 100644 index 0000000000..51668128c6 --- /dev/null +++ b/doc/project_services/bamboo.md @@ -0,0 +1,60 @@ +# Atlassian Bamboo CI Service + +GitLab provides integration with Atlassian Bamboo for continuous integration. +When configured, pushes to a project will trigger a build in Bamboo automatically. +Merge requests will also display CI status showing whether the build is pending, +failed, or completed successfully. It also provides a link to the Bamboo build +page for more information. + +Bamboo doesn't quite provide the same features as a traditional build system when +it comes to accepting webhooks and commit data. There are a few things that +need to be configured in a Bamboo build plan before GitLab can integrate. + +## Setup + +### Complete these steps in Bamboo: + +1. Navigate to a Bamboo build plan and choose 'Configure plan' from the 'Actions' +dropdown. +1. Select the 'Triggers' tab. +1. Click 'Add trigger'. +1. Enter a description such as 'GitLab trigger' +1. Choose 'Repository triggers the build when changes are committed' +1. Check one or more repositories checkboxes +1. Enter the GitLab IP address in the 'Trigger IP addresses' box. This is a +whitelist of IP addresses that are allowed to trigger Bamboo builds. +1. Save the trigger. +1. In the left pane, select a build stage. If you have multiple build stages +you want to select the last stage that contains the git checkout task. +1. Select the 'Miscellaneous' tab. +1. Under 'Pattern Match Labelling' put '${bamboo.repository.revision.number}' +in the 'Labels' box. +1. Save + +Bamboo is now ready to accept triggers from GitLab. Next, set up the Bamboo +service in GitLab + +### Complete these steps in GitLab: + +1. Navigate to the project you want to configure to trigger builds. +1. Select 'Settings' in the top navigation. +1. Select 'Services' in the left navigation. +1. Click 'Atlassian Bamboo CI' +1. Select the 'Active' checkbox. +1. Enter the base URL of your Bamboo server. 'https://bamboo.example.com' +1. Enter the build key from your Bamboo build plan. Build keys are a short, +all capital letter, identifier that is unique. It will be something like PR-BLD +1. If necessary, enter username and password for a Bamboo user that has +access to trigger the build plan. Leave these fields blank if you do not require +authentication. +1. Save or optionally click 'Test Settings'. Please note that 'Test Settings' +will actually trigger a build in Bamboo. + +## Troubleshooting + +If builds are not triggered, these are a couple of things to keep in mind. + +1. Ensure you entered the right GitLab IP address in Bamboo under 'Trigger +IP addresses'. +1. Remember that GitLab only triggers builds on push events. A commit via the +web interface will not trigger CI currently. diff --git a/doc/project_services/hipchat.md b/doc/project_services/hipchat.md new file mode 100644 index 0000000000..021a93a288 --- /dev/null +++ b/doc/project_services/hipchat.md @@ -0,0 +1,54 @@ +# Atlassian HipChat + +GitLab provides a way to send HipChat notifications upon a number of events, +such as when a user pushes code, creates a branch or tag, adds a comment, and +creates a merge request. + +## Setup + +GitLab requires the use of a HipChat v2 API token to work. v1 tokens are +not supported at this time. Note the differences between v1 and v2 tokens: + +HipChat v1 API (legacy) supports "API Auth Tokens" in the Group API menu. A v1 +token is allowed to send messages to *any* room. + +HipChat v2 API has tokens that are can be created using the Integrations tab +in the Group or Room admin page. By design, these are lightweight tokens that +allow GitLab to send messages only to *one* room. + +### Complete these steps in HipChat: + +1. Go to: https://admin.hipchat.com/admin +1. Click on "Group Admin" -> "Integrations". +1. Find "Build Your Own!" and click "Create". +1. Select the desired room, name the integration "GitLab", and click "Create". +1. In the "Send messages to this room by posting this URL" column, you should +see a URL in the format: + +``` + https://api.hipchat.com/v2/room//notification?auth_token= +``` + +HipChat is now ready to accept messages from GitLab. Next, set up the HipChat +service in GitLab. + +### Complete these steps in GitLab: + +1. Navigate to the project you want to configure for notifications. +1. Select "Settings" in the top navigation. +1. Select "Services" in the left navigation. +1. Click "HipChat". +1. Select the "Active" checkbox. +1. Insert the `token` field from the URL into the `Token` field on the Web page. +1. Insert the `room` field from the URL into the `Room` field on the Web page. +1. Save or optionally click "Test Settings". + +## Troubleshooting + +If you do not see notifications, make sure you are using a HipChat v2 API +token, not a v1 token. + +Note that the v2 token is tied to a specific room. If you want to be able to +specify arbitrary rooms, you can create an API token for a specific user in +HipChat under "Account settings" and "API access". Use the `XXX` value under +`auth_token=XXX`. diff --git a/doc/project_services/irker.md b/doc/project_services/irker.md new file mode 100644 index 0000000000..780a45bca2 --- /dev/null +++ b/doc/project_services/irker.md @@ -0,0 +1,46 @@ +# Irker IRC Gateway + +GitLab provides a way to push update messages to an Irker server. When +configured, pushes to a project will trigger the service to send data directly +to the Irker server. + +See the project homepage for further info: http://www.catb.org/esr/irker/ + +## Needed setup + +You will first need an Irker daemon. You can download the Irker code from its +gitorious repository on https://gitorious.org/irker: `git clone +git@gitorious.org:irker/irker.git`. Once you have downloaded the code, you can +run the python script named `irkerd`. This script is the gateway script, it acts +both as an IRC client, for sending messages to an IRC server obviously, and as a +TCP server, for receiving messages from the GitLab service. + +If the Irker server runs on the same machine, you are done. If not, you will +need to follow the firsts steps of the next section. + +## Optional setup + +In the `app/models/project_services/irker_service.rb` file, you can modify some +options in the `initialize_settings` method: +- **server_ip** (defaults to `localhost`): the server IP address where the +`irkerd` daemon runs; +- **server_port** (defaults to `6659`): the server port of the `irkerd` daemon; +- **max_channels** (defaults to `3`): the maximum number of recipients the +client is authorized to join, per project; +- **default_irc_uri** (no default) : if this option is set, it has to be in the +format `irc[s]://domain.name` and will be prepend to each and every channel +provided by the user which is not a full URI. + +If the Irker server and the GitLab application do not run on the same host, you +will **need** to setup at least the **server_ip** option. + +## Note on Irker recipients + +Irker accepts channel names of the form `chan` and `#chan`, both for the +`#chan` channel. If you want to send messages in query, you will need to add +`,isnick` avec the channel name, in this form: `Aorimn,isnick`. In this latter +case, `Aorimn` is treated as a nick and no more as a channel name. + +Irker can also join password-protected channels. Users need to append +`?key=thesecretpassword` to the chan name. + diff --git a/doc/project_services/project_services.md b/doc/project_services/project_services.md new file mode 100644 index 0000000000..03937d2072 --- /dev/null +++ b/doc/project_services/project_services.md @@ -0,0 +1,20 @@ +# Project Services + +__Project integrations with external services for continuous integration and more.__ + +## Services + +- Assembla +- [Atlassian Bamboo CI](bamboo.md) An Atlassian product for continuous integration. +- Build box +- Campfire +- Emails on push +- Flowdock +- Gemnasium +- GitLab CI +- [HipChat](hipchat.md) An Atlassian product for private group chat and instant messaging. +- [Irker](irker.md) An IRC gateway to receive messages on repository updates. +- Pivotal Tracker +- Pushover +- Slack +- TeamCity diff --git a/doc/public_access/public_access.md b/doc/public_access/public_access.md index 4712c38702..bd439f7c6f 100644 --- a/doc/public_access/public_access.md +++ b/doc/public_access/public_access.md @@ -41,4 +41,4 @@ When visiting the public page of an user, you will only see listed projects whic ## Restricting the use of public or internal projects -In [gitlab.yml](https://gitlab.com/gitlab-org/gitlab-ce/blob/dbd88d453b8e6c78a423fa7e692004b1db6ea069/config/gitlab.yml.example#L64) you can disable public projects or public and internal projects for the entire GitLab installation to prevent people making code public by accident. +In the Admin area under Settings you can disable public projects or public and internal projects for the entire GitLab installation to prevent people making code public by accident. The restricted visibility settings do not apply to admin users. diff --git a/doc/raketasks/README.md b/doc/raketasks/README.md index 9e2f697bca..770b7a70fe 100644 --- a/doc/raketasks/README.md +++ b/doc/raketasks/README.md @@ -1,5 +1,8 @@ +# Rake tasks + - [Backup restore](backup_restore.md) - [Cleanup](cleanup.md) +- [Features](features.md) - [Maintenance](maintenance.md) and self-checks - [User management](user_management.md) - [Web hooks](web_hooks.md) diff --git a/doc/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md index 8200a8cf63..2e41fad89e 100644 --- a/doc/raketasks/backup_restore.md +++ b/doc/raketasks/backup_restore.md @@ -6,15 +6,22 @@ A backup creates an archive file that contains the database, all repositories and all attachments. This archive will be saved in backup_path (see `config/gitlab.yml`). - The filename will be `[TIMESTAMP]_gitlab_backup.tar`. This timestamp can be used to restore an specific backup. +You can only restore a backup to exactly the same version of GitLab that you created it on, for example 7.2.1. ``` -# omnibus-gitlab +# use this command if you've installed GitLab with the Omnibus package sudo gitlab-rake gitlab:backup:create -# installation from source or cookbook -bundle exec rake gitlab:backup:create RAILS_ENV=production +# if you've installed GitLab from source +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +Also you can choose what should be backed up by adding environment variable SKIP. Available options: db, +uploads (attachments), repositories. Use a comma to specify several options at the same time. + +``` +sudo gitlab-rake gitlab:backup:create SKIP=db,uploads ``` Example output: @@ -46,20 +53,108 @@ Deleting tmp directories...[DONE] Deleting old backups... [SKIPPING] ``` +## Upload backups to remote (cloud) storage + +Starting with GitLab 7.4 you can let the backup script upload the '.tar' file it creates. +It uses the [Fog library](http://fog.io/) to perform the upload. +In the example below we use Amazon S3 for storage. +But Fog also lets you use [other storage providers](http://fog.io/storage/). + +For omnibus packages: + +```ruby +gitlab_rails['backup_upload_connection'] = { + 'provider' => 'AWS', + 'region' => 'eu-west-1', + 'aws_access_key_id' => 'AKIAKIAKI', + 'aws_secret_access_key' => 'secret123' +} +gitlab_rails['backup_upload_remote_directory'] = 'my.s3.bucket' +``` + +For installations from source: + +```yaml + backup: + # snip + upload: + # Fog storage connection settings, see http://fog.io/storage/ . + connection: + provider: AWS + region: eu-west-1 + aws_access_key_id: AKIAKIAKI + aws_secret_access_key: 'secret123' + # The remote 'directory' to store your backups. For S3, this would be the bucket name. + remote_directory: 'my.s3.bucket' +``` + +If you are uploading your backups to S3 you will probably want to create a new +IAM user with restricted access rights. To give the upload user access only for +uploading backups create the following IAM profile, replacing `my.s3.bucket` +with the name of your bucket: + +```json +{ + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "Stmt1412062044000", + "Effect": "Allow", + "Action": [ + "s3:AbortMultipartUpload", + "s3:GetBucketAcl", + "s3:GetBucketLocation", + "s3:GetObject", + "s3:GetObjectAcl", + "s3:ListBucketMultipartUploads", + "s3:PutObject", + "s3:PutObjectAcl" + ], + "Resource": [ + "arn:aws:s3:::my.s3.bucket/*" + ] + }, + { + "Sid": "Stmt1412062097000", + "Effect": "Allow", + "Action": [ + "s3:GetBucketLocation", + "s3:ListAllMyBuckets" + ], + "Resource": [ + "*" + ] + }, + { + "Sid": "Stmt1412062128000", + "Effect": "Allow", + "Action": [ + "s3:ListBucket" + ], + "Resource": [ + "arn:aws:s3:::my.s3.bucket" + ] + } + ] +} +``` + ## Storing configuration files Please be informed that a backup does not store your configuration files. -If you use Omnibus-GitLab please see the [instructions in the readme to backup your configuration](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#backup-and-restore-omnibus-gitlab-configuration). +If you use an Omnibus package please see the [instructions in the readme to backup your configuration](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#backup-and-restore-omnibus-gitlab-configuration). If you have a cookbook installation there should be a copy of your configuration in Chef. -If you have a manual installation please consider backing up your gitlab.yml file and any SSL keys and certificates. +If you have an installation from source, please consider backing up your `gitlab.yml` file, any SSL keys and certificates, and your [SSH host keys](https://superuser.com/questions/532040/copy-ssh-keys-from-one-server-to-another-server/532079#532079). ## Restore a previously created backup +You can only restore a backup to exactly the same version of GitLab that you created it on, for example 7.2.1. + ``` -# omnibus-gitlab +# Omnibus package installation sudo gitlab-rake gitlab:backup:restore -# installation from source or cookbook +# installation from source bundle exec rake gitlab:backup:restore RAILS_ENV=production ``` @@ -102,8 +197,9 @@ Deleting tmp directories...[DONE] ## Configure cron to make daily backups -For omnibus-gitlab, see https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#scheduling-a-backup . +For Omnibus package installations, see https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#scheduling-a-backup . +For installation from source: ``` cd /home/git/gitlab sudo -u git -H editor config/gitlab.yml # Enable keep_time in the backup section to automatically delete old backups @@ -113,6 +209,32 @@ sudo -u git crontab -e # Edit the crontab for the git user Add the following lines at the bottom: ``` -# Create a full backup of the GitLab repositories and SQL database every day at 2am -0 2 * * * cd /home/git/gitlab && PATH=/usr/local/bin:/usr/bin:/bin bundle exec rake gitlab:backup:create RAILS_ENV=production +# Create a full backup of the GitLab repositories and SQL database every day at 4am +0 4 * * * cd /home/git/gitlab && PATH=/usr/local/bin:/usr/bin:/bin bundle exec rake gitlab:backup:create RAILS_ENV=production CRON=1 ``` + +The `CRON=1` environment setting tells the backup script to suppress all progress output if there are no errors. +This is recommended to reduce cron spam. + +## Alternative backup strategies + +If your GitLab server contains a lot of Git repository data you may find the GitLab backup script to be too slow. +In this case you can consider using filesystem snapshots as part of your backup strategy. + +Example: Amazon EBS + +> A GitLab server using omnibus-gitlab hosted on Amazon AWS. +> An EBS drive containing an ext4 filesystem is mounted at `/var/opt/gitlab`. +> In this case you could make an application backup by taking an EBS snapshot. +> The backup includes all repositories, uploads and Postgres data. + +Example: LVM snapshots + rsync + +> A GitLab server using omnibus-gitlab, with an LVM logical volume mounted at `/var/opt/gitlab`. +> Replicating the `/var/opt/gitlab` directory using rsync would not be reliable because too many files would change while rsync is running. +> Instead of rsync-ing `/var/opt/gitlab`, we create a temporary LVM snapshot, which we mount as a read-only filesystem at `/mnt/gitlab_backup`. +> Now we can have a longer running rsync job which will create a consistent replica on the remote server. +> The replica includes all repositories, uploads and Postgres data. + +If you are running GitLab on a virtualized server you can possibly also create VM snapshots of the entire GitLab server. +It is not uncommon however for a VM snapshot to require you to power down the server, so this approach is probably of limited practical use. diff --git a/doc/raketasks/cleanup.md b/doc/raketasks/cleanup.md index 9e48f56c95..96d67f7b5d 100644 --- a/doc/raketasks/cleanup.md +++ b/doc/raketasks/cleanup.md @@ -8,7 +8,7 @@ Remove namespaces(dirs) from `/home/git/repositories` if they don't exist in Git # omnibus-gitlab sudo gitlab-rake gitlab:cleanup:dirs -# installation from source or cookbook +# installation from source bundle exec rake gitlab:cleanup:dirs RAILS_ENV=production ``` @@ -18,6 +18,6 @@ Remove repositories (global only for now) from `/home/git/repositories` if they # omnibus-gitlab sudo gitlab-rake gitlab:cleanup:repos -# installation from source or cookbook +# installation from source bundle exec rake gitlab:cleanup:repos RAILS_ENV=production ``` diff --git a/doc/raketasks/features.md b/doc/raketasks/features.md index 99b3d5525b..f9a4619354 100644 --- a/doc/raketasks/features.md +++ b/doc/raketasks/features.md @@ -6,7 +6,7 @@ This command will enable the namespaces feature introduced in v4.0. It will move Note: -- Because the **repository location will change**, you will need to **update all your git url's** to point to the new location. +- Because the **repository location will change**, you will need to **update all your git URLs** to point to the new location. - Username can be changed at [Profile / Account](/profile/account) **Example:** diff --git a/doc/raketasks/import.md b/doc/raketasks/import.md index 39b1a52a44..8a38937062 100644 --- a/doc/raketasks/import.md +++ b/doc/raketasks/import.md @@ -1,28 +1,62 @@ -# Import +# Import bare repositories into your GitLab instance -### Import bare repositories into GitLab project instance +## Notes -Notes: +- The owner of the project will be the first admin +- The groups will be created as needed +- The owner of the group will be the first admin +- Existing projects will be skipped -* project owner will be a first admin -* groups will be created as needed -* group owner will be the first admin -* existing projects will be skipped +## How to use -How to use: +### Create a new folder inside the git repositories path. This will be the name of the new group. -1. copy your bare repos under git repos_path (see `config/gitlab.yml` gitlab_shell -> repos_path) -2. run the command below +- For omnibus-gitlab, it is located at: `/var/opt/gitlab/git-data/repositories` by default, unless you changed +it in the `/etc/gitlab/gitlab.rb` file. +- For installations from source, it is usually located at: `/home/git/repositories` or you can see where +your repositories are located by looking at `config/gitlab.yml` under the `gitlab_shell => repos_path` entry. + +New folder needs to have git user ownership and read/write/execute access for git user and its group: ``` -# omnibus-gitlab -sudo gitlab-rake gitlab:import:repos - -# installation from source or cookbook -bundle exec rake gitlab:import:repos RAILS_ENV=production +sudo -u git mkdir /var/opt/gitlab/git-data/repositories/new_group ``` -Example output: +If you are using an installation from source, replace `/var/opt/gitlab/git-data` +with `/home/git`. + +### Copy your bare repositories inside this newly created folder: + +``` +sudo cp -r /old/git/foo.git /var/opt/gitlab/git-data/repositories/new_group/ + +# Do this once when you are done copying git repositories +sudo chown -R git:git /var/opt/gitlab/git-data/repositories/new_group/ +``` + +`foo.git` needs to be owned by the git user and git users group. + +If you are using an installation from source, replace `/var/opt/gitlab/git-data` +with `/home/git`. + +### Run the command below depending on your type of installation: + +#### Omnibus Installation + +``` +$ sudo gitlab-rake gitlab:import:repos +``` + +#### Installation from source + +Before running this command you need to change the directory to where your GitLab installation is located: + +``` +$ cd /home/git/gitlab +$ sudo -u git -H bundle exec rake gitlab:import:repos RAILS_ENV=production +``` + +#### Example output ``` Processing abcd.git diff --git a/doc/raketasks/maintenance.md b/doc/raketasks/maintenance.md index a0901cc407..41a994f3f6 100644 --- a/doc/raketasks/maintenance.md +++ b/doc/raketasks/maintenance.md @@ -8,7 +8,7 @@ This command gathers information about your GitLab installation and the System i # omnibus-gitlab sudo gitlab-rake gitlab:env:info -# installation from source or cookbook +# installation from source bundle exec rake gitlab:env:info RAILS_ENV=production ``` @@ -16,30 +16,31 @@ Example output: ``` System information -System: Debian 6.0.7 -Current User: git -Using RVM: no -Ruby Version: 2.0.0-p481 -Gem Version: 1.8.23 -Bundler Version:1.3.5 -Rake Version: 10.0.4 +System: Debian 7.8 +Current User: git +Using RVM: no +Ruby Version: 2.1.5p273 +Gem Version: 2.4.3 +Bundler Version: 1.7.6 +Rake Version: 10.3.2 +Sidekiq Version: 2.17.8 GitLab information -Version: 5.1.0.beta2 -Revision: 4da8b37 -Directory: /home/git/gitlab -DB Adapter: mysql2 -URL: http://example.com -HTTP Clone URL: http://example.com/some-project.git -SSH Clone URL: git@example.com:some-project.git -Using LDAP: no -Using Omniauth: no +Version: 7.7.1 +Revision: 41ab9e1 +Directory: /home/git/gitlab +DB Adapter: postgresql +URL: https://gitlab.example.com +HTTP Clone URL: https://gitlab.example.com/some-project.git +SSH Clone URL: git@gitlab.example.com:some-project.git +Using LDAP: no +Using Omniauth: no GitLab Shell -Version: 1.2.0 -Repositories: /home/git/repositories/ -Hooks: /home/git/gitlab-shell/hooks/ -Git: /usr/bin/git +Version: 2.4.1 +Repositories: /home/git/repositories/ +Hooks: /home/git/gitlab-shell/hooks/ +Git: /usr/bin/git ``` ## Check GitLab configuration @@ -59,7 +60,7 @@ You may also have a look at our [Trouble Shooting Guide](https://github.com/gitl # omnibus-gitlab sudo gitlab-rake gitlab:check -# installation from source or cookbook +# installation from source bundle exec rake gitlab:check RAILS_ENV=production ``` @@ -115,8 +116,63 @@ Checking GitLab ... Finished This will create satellite repositories for all your projects. -If necessary, remove the `tmp/repo_satellites` directory and rerun the command below. +If necessary, remove the `repo_satellites` directory and rerun the commands below. ``` -bundle exec rake gitlab:satellites:create RAILS_ENV=production +sudo -u git -H mkdir -p /home/git/gitlab-satellites +sudo -u git -H bundle exec rake gitlab:satellites:create RAILS_ENV=production +sudo chmod u+rwx,g=rx,o-rwx /home/git/gitlab-satellites +``` + +## Rebuild authorized_keys file + +In some case it is necessary to rebuild the `authorized_keys` file. + +For Omnibus-packages: +``` +sudo gitlab-rake gitlab:shell:setup +``` + +For installations from source: +``` +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:shell:setup RAILS_ENV=production +``` + +``` +This will rebuild an authorized_keys file. +You will lose any data stored in authorized_keys file. +Do you want to continue (yes/no)? yes +``` + +## Clear redis cache + +If for some reason the dashboard shows wrong information you might want to +clear Redis' cache. + +For Omnibus-packages: +``` +sudo gitlab-rake cache:clear +``` + +For installations from source: +``` +cd /home/git/gitlab +sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production +``` + +## Precompile the assets + +Sometimes during version upgrades you might end up with some wrong CSS or +missing some icons. In that case, try to precompile the assets again. + +For Omnibus-packages: +``` +sudo gitlab-rake assets:precompile +``` + +For installations from source: +``` +cd /home/git/gitlab +sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production ``` diff --git a/doc/raketasks/user_management.md b/doc/raketasks/user_management.md index eb9eebb4b4..80b01ca404 100644 --- a/doc/raketasks/user_management.md +++ b/doc/raketasks/user_management.md @@ -6,8 +6,8 @@ # omnibus-gitlab sudo gitlab-rake gitlab:import:user_to_projects[username@domain.tld] -# installation from source or cookbook -bundle exec rake gitlab:import:user_to_projects[username@domain.tld] +# installation from source +bundle exec rake gitlab:import:user_to_projects[username@domain.tld] RAILS_ENV=production ``` ## Add all users to all projects @@ -20,8 +20,8 @@ Notes: # omnibus-gitlab sudo gitlab-rake gitlab:import:all_users_to_all_projects -# installation from source or cookbook -bundle exec rake gitlab:import:all_users_to_all_projects +# installation from source +bundle exec rake gitlab:import:all_users_to_all_projects RAILS_ENV=production ``` ## Add user as a developer to all groups @@ -30,8 +30,8 @@ bundle exec rake gitlab:import:all_users_to_all_projects # omnibus-gitlab sudo gitlab-rake gitlab:import:user_to_groups[username@domain.tld] -# installation from source or cookbook -bundle exec rake gitlab:import:user_to_groups[username@domain.tld] +# installation from source +bundle exec rake gitlab:import:user_to_groups[username@domain.tld] RAILS_ENV=production ``` ## Add all users to all groups @@ -44,6 +44,6 @@ Notes: # omnibus-gitlab sudo gitlab-rake gitlab:import:all_users_to_all_groups -# installation from source or cookbook -bundle exec rake gitlab:import:all_users_to_all_groups +# installation from source +bundle exec rake gitlab:import:all_users_to_all_groups RAILS_ENV=production ``` diff --git a/doc/raketasks/web_hooks.md b/doc/raketasks/web_hooks.md index e1a58835d8..5a8b94af9b 100644 --- a/doc/raketasks/web_hooks.md +++ b/doc/raketasks/web_hooks.md @@ -4,42 +4,42 @@ # omnibus-gitlab sudo gitlab-rake gitlab:web_hook:add URL="http://example.com/hook" - # source installations or cookbook + # source installations bundle exec rake gitlab:web_hook:add URL="http://example.com/hook" RAILS_ENV=production ## Add a web hook for projects in a given **NAMESPACE**: # omnibus-gitlab sudo gitlab-rake gitlab:web_hook:add URL="http://example.com/hook" NAMESPACE=acme - # source installations or cookbook + # source installations bundle exec rake gitlab:web_hook:add URL="http://example.com/hook" NAMESPACE=acme RAILS_ENV=production ## Remove a web hook from **ALL** projects using: # omnibus-gitlab sudo gitlab-rake gitlab:web_hook:rm URL="http://example.com/hook" - # source installations or cookbook + # source installations bundle exec rake gitlab:web_hook:rm URL="http://example.com/hook" RAILS_ENV=production ## Remove a web hook from projects in a given **NAMESPACE**: # omnibus-gitlab sudo gitlab-rake gitlab:web_hook:rm URL="http://example.com/hook" NAMESPACE=acme - # source installations or cookbook + # source installations bundle exec rake gitlab:web_hook:rm URL="http://example.com/hook" NAMESPACE=acme RAILS_ENV=production ## List **ALL** web hooks: # omnibus-gitlab sudo gitlab-rake gitlab:web_hook:list - # source installations or cookbook + # source installations bundle exec rake gitlab:web_hook:list RAILS_ENV=production ## List the web hooks from projects in a given **NAMESPACE**: # omnibus-gitlab sudo gitlab-rake gitlab:web_hook:list NAMESPACE=/ - # source installations or cookbook + # source installations bundle exec rake gitlab:web_hook:list NAMESPACE=/ RAILS_ENV=production > Note: `/` is the global namespace. diff --git a/doc/release/howto_rc1.md b/doc/release/howto_rc1.md new file mode 100644 index 0000000000..07c703142d --- /dev/null +++ b/doc/release/howto_rc1.md @@ -0,0 +1,55 @@ +# How to create RC1 + +The RC1 release comes with the task to update the installation and upgrade docs. Be mindful that there might already be merge requests for this on GitLab or GitHub. + +### 1. Update the installation guide + +1. Check if it references the correct branch `x-x-stable` (doesn't exist yet, but that is okay) +1. Check the [GitLab Shell version](/lib/tasks/gitlab/check.rake#L782) +1. Check the [Git version](/lib/tasks/gitlab/check.rake#L794) +1. There might be other changes. Ask around. + +### 2. Create update guides + +[Follow this guide](howto_update_guides.md) to create update guides. + +### 3. Code quality indicators + +Make sure the code quality indicators are green / good. + +- [![Build status](http://ci.gitlab.org/projects/1/status.png?ref=master)](http://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch) + +- [![Build Status](https://semaphoreapp.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/243338/badge.png)](https://semaphoreapp.com/gitlabhq/gitlabhq) (master branch) + +- [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.png)](https://codeclimate.com/github/gitlabhq/gitlabhq) + +- [![Dependency Status](https://gemnasium.com/gitlabhq/gitlabhq.png)](https://gemnasium.com/gitlabhq/gitlabhq) this button can be yellow (small updates are available) but must not be red (a security fix or an important update is available) + +- [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq) + +### 4. Run release tool + +**Make sure EE `master` has latest changes from CE `master`** + +Get release tools + +``` +git clone git@dev.gitlab.org:gitlab/release-tools.git +cd release-tools +``` + +Release candidate creates stable branch from master. +So we need to sync master branch between all CE, EE and CI remotes. + +``` +bundle exec rake sync +``` + +Create release candidate and stable branch: + +``` +bundle exec rake release["x.x.0.rc1"] +``` + +Now developers can use master for merging new features. +So you should use stable branch for future code changes related to release. diff --git a/doc/release/howto_update_guides.md b/doc/release/howto_update_guides.md new file mode 100644 index 0000000000..23d0959c33 --- /dev/null +++ b/doc/release/howto_update_guides.md @@ -0,0 +1,55 @@ +# Create update guides + +1. Create: CE update guide from previous version. Like `7.3-to-7.4.md` +1. Create: CE to EE update guide in EE repository for latest version. +1. Update: `6.x-or-7.x-to-7.x.md` to latest version. +1. Create: CI update guide from previous version + +It's best to copy paste the previous guide and make changes where necessary. +The typical steps are listed below with any points you should specifically look at. + +#### 0. Any major changes? + +List any major changes here, so the user is aware of them before starting to upgrade. For instance: + +- Database updates +- Web server changes +- File structure changes + +#### 1. Stop server + +#### 2. Make backup + +#### 3. Do users need to update dependencies like `git`? + +- Check if the [GitLab Shell version](/lib/tasks/gitlab/check.rake#L782) changed since the last release. + +- Check if the [Git version](/lib/tasks/gitlab/check.rake#L794) changed since the last release. + +#### 4. Get latest code + +#### 5. Does GitLab shell need to be updated? + +#### 6. Install libs, migrations, etc. + +#### 7. Any config files updated since last release? + +Check if any of these changed since last release: + +- [lib/support/nginx/gitlab](/lib/support/nginx/gitlab) +- [lib/support/nginx/gitlab-ssl](/lib/support/nginx/gitlab-ssl) +- +- [config/gitlab.yml.example](/config/gitlab.yml.example) +- [config/unicorn.rb.example](/config/unicorn.rb.example) +- [config/database.yml.mysql](/config/database.yml.mysql) +- [config/database.yml.postgresql](/config/database.yml.postgresql) +- [config/initializers/rack_attack.rb.example](/config/initializers/rack_attack.rb.example) +- [config/resque.yml.example](/config/resque.yml.example) + +#### 8. Need to update init script? + +Check if the `init.d/gitlab` script changed since last release: [lib/support/init.d/gitlab](/lib/support/init.d/gitlab) + +#### 9. Start application + +#### 10. Check application status diff --git a/doc/release/monthly.md b/doc/release/monthly.md index 09bdde81dc..cfe01896d8 100644 --- a/doc/release/monthly.md +++ b/doc/release/monthly.md @@ -1,173 +1,126 @@ # Monthly Release -NOTE: This is a guide for GitLab developers. +NOTE: This is a guide used by the GitLab B.V. developers. -# **15th - Code Freeze & Release Manager** +It starts 7 working days before the release. +The release manager doesn't have to perform all the work but must ensure someone is assigned. +The current release manager must schedule the appointment of the next release manager. +The new release manager should create overall issue to track the progress. -### **1. Stop merging in code, except for important bugfixes** +## Release Manager -### **2. Release Manager** +A release manager is selected that coordinates all releases the coming month, including the patch releases for previous releases. +The release manager has to make sure all the steps below are done and delegated where necessary. +This person should also make sure this document is kept up to date and issues are created and updated. -A release manager is selected that coordinates the entire release of this version. The release manager has to make sure all the steps below are done and delegated where necessary. This person should also make sure this document is kept up to date and issues are created and updated. +## Take vacations into account -### **3. Update Changelog** +The time is measured in weekdays to compensate for weekends. +Do everything on time to prevent problems due to rush jobs or too little testing time. +Make sure that you take into account any vacations of maintainers. +If the release is falling behind immediately warn the team. -Any changes not yet added to the changelog are added by lead developer and in that merge request the complete team is asked if there is anything missing. +## Create an overall issue and follow it -### **4. Create an overall issue** +Create issue for GitLab CE project(internal). Name it "Release x.x.x" for easier searching. +Replace the dates with actual dates based on the number of workdays before the release. +All steps from issue template are explained below ``` -15th: +Xth: (7 working days before the 22nd) -* Update the changelog (#LINK) -* Triage the omnibus-gitlab milestone +- [ ] Code freeze +- [ ] Update the CE changelog (#LINK) +- [ ] Update the EE changelog (#LINK) +- [ ] Update the CI changelog (#LINK) +- [ ] Triage the omnibus-gitlab milestone -16th: +Xth: (6 working days before the 22nd) -* Merge CE in to EE (#LINK) -* Close the omnibus-gitlab milestone +- [ ] Merge CE master in to EE master via merge request (#LINK) +- [ ] Determine QA person and notify this person +- [ ] Check the tasks in [how to rc1 guide](howto_rc1.md) and delegate tasks if necessary +- [ ] Create CE, EE, CI RC1 versions (#LINK) -17th: +Xth: (5 working days before the 22nd) -* Create x.x.0.rc1 (#LINK) +- [ ] Do QA and fix anything coming out of it (#LINK) +- [ ] Close the omnibus-gitlab milestone +- [ ] Prepare the blog post (#LINK) -18th: +Xth: (4 working days before the 22nd) -* Update GitLab.com with rc1 (#LINK) -* Regression issue and tweet about rc1 (#LINK) -* Start blog post (#LINK) +- [ ] Update GitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package) +- [ ] Update ci.gitLab.com with rc1 (#LINK) (https://dev.gitlab.org/cookbooks/chef-repo/blob/master/doc/administration.md#deploy-the-package) +- [ ] Create regression issues (CE, CI) (#LINK) +- [ ] Tweet about rc1 (#LINK) -21th: +Xth: (3 working days before the 22nd) -* Do QA and fix anything coming out of it (#LINK) +- [ ] Merge CE stable branch into EE stable branch + +Xth: (2 working days before the 22nd) + +- [ ] Check that everyone is mentioned on the blog post (the reviewer should have done this one working day ago) +- [ ] Check that MVP is added to the mvp page (source/mvp/index.html in www-gitlab-com) + +Xth: (1 working day before the 22nd) + +- [ ] Create CE, EE, CI stable versions (#LINK) +- [ ] Create Omnibus tags and build packages +- [ ] Update GitLab.com with the stable version (#LINK) +- [ ] Update ci.gitLab.com with the stable version (#LINK) 22nd: -* Release CE and EE (#LINK) - -23rd: - -* Prepare package for GitLab.com release (#LINK) - -24th: - -* Deploy to GitLab.com (#LINK) -``` - -# **16th - Merge the CE into EE** - -Do this via a merge request. - -# **17th - Create RC1** - -The RC1 release comes with the task to update the installation and upgrade docs. Be mindful that there might already be merge requests for this on GitLab or GitHub. - -### **1. Update the installation guide** - -1. Check if it references the correct branch `x-x-stable` (doesn't exist yet, but that is okay) -1. Check the [GitLab Shell version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L782) -1. Check the [Git version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L794) -1. There might be other changes. Ask around. - -### **2. Create an update guides** - -1. Create: CE update guide from previous version. Like `from-6-8-to-6.9` -1. Create: CE to EE update guide in EE repository for latest version. -1. Update: https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/6.0-to-6.x.md to latest version. - -It's best to copy paste the previous guide and make changes where necessary. -The typical steps are listed below with any points you should specifically look at. - -#### 0. Any major changes? - -List any major changes here, so the user is aware of them before starting to upgrade. For instance: - -- Database updates -- Web server changes -- File structure changes - -#### 1. Make backup - -#### 2. Stop server - -#### 3. Do users need to update dependencies like `git`? - -- Check if the [GitLab Shell version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L782) changed since the last release. - -- Check if the [Git version](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/lib/tasks/gitlab/check.rake#L794) changed since the last release. - -#### 4. Get latest code - -#### 5. Does GitLab shell need to be updated? - -#### 6. Install libs, migrations, etc. - -#### 7. Any config files updated since last release? - -Check if any of these changed since last release: - -- -- -- -- -- -- - -#### 8. Need to update init script? - -Check if the `init.d/gitlab` script changed since last release: - -#### 9. Start application - -#### 10. Check application status - -### **3. Code quality indicators** - -Make sure the code quality indicators are green / good. - -- [![build status](http://ci.gitlab.org/projects/1/status.png?ref=master)](http://ci.gitlab.org/projects/1?ref=master) on ci.gitlab.org (master branch) - -- [![build status](https://secure.travis-ci.org/gitlabhq/gitlabhq.png)](https://travis-ci.org/gitlabhq/gitlabhq) on travis-ci.org (master branch) - -- [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.png)](https://codeclimate.com/github/gitlabhq/gitlabhq) - -- [![Dependency Status](https://gemnasium.com/gitlabhq/gitlabhq.png)](https://gemnasium.com/gitlabhq/gitlabhq) this button can be yellow (small updates are available) but must not be red (a security fix or an important update is available) - -- [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq) - -### **4. Set VERSION** - -Change version in VERSION to `x.x.0.rc1`. - -### **5. Tag** - -Create an annotated tag that points to the version change commit: +- [ ] Release CE, EE and CI (#LINK) -``` -git tag -a vx.x.0.rc1 -m 'Version x.x.0.rc1' ``` -# **18th - Release RC1** +- - - -### **1. Update GitLab.com** +## Code Freeze -Merge the RC1 EE code into GitLab.com. -Once the build is green, create a package. +Stop merging code in master, except for important bug fixes + +## Update changelog + +Any changes not yet added to the changelog are added by lead developer and in that merge request the complete team is +asked if there is anything missing. + +There are three changelogs that need to be updated: CE, EE and CI. + +## Create RC1 (CE, EE, CI) + +[Follow this How-to guide](howto_rc1.md) to create RC1. + +## Prepare CHANGELOG for next release + +Once the stable branches have been created, update the CHANGELOG in `master` with the upcoming version, usually X.X.X.pre. + +## QA + +Create issue on dev.gitlab.org `gitlab` repository, named "GitLab X.X QA" in order to keep track of the progress. + +Use the omnibus packages created for RC1 of Enterprise Edition using [this guide](https://dev.gitlab.org/gitlab/gitlab-ee/blob/master/doc/release/manual_testing.md). + +**NOTE** Upgrader can only be tested when tags are pushed to all repositories. Do not forget to confirm it is working before releasing. Note that in the issue. + +#### Fix anything coming out of the QA + +Create an issue with description of a problem, if it is quick fix fix it yourself otherwise contact the team for advice. + +**NOTE** If there is a problem that cannot be fixed in a timely manner, reverting the feature is an option! If the feature is reverted, +create an issue about it in order to discuss the next steps after the release. + +## Update GitLab.com with RC1 + +Use the omnibus EE packages created for RC1. +If there are big database migrations consider testing them with the production db on a VM. Try to deploy in the morning. It is important to do this as soon as possible, so we can catch any errors before we release the full version. -### **2. Prepare the blog post** - -- Start with a complete copy of the [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md) and fill it out. -- Check the changelog of CE and EE for important changes. -- Create a WIP MR for the blog post -- Ask Dmitriy to add screenshots to the WIP MR. -- Decide with team who will be the MVP user. -- Add a note if there are security fixes: This release fixes an important security issue and we advise everyone to upgrade as soon as possible. -- Assign to one reviewer who will fix spelling issues by editing the branch (can use the online editor) -- After the reviewer is finished the whole team will be mentioned to give their suggestions via line comments - -### **3. Create a regressions issue** +## Create a regressions issue On [the GitLab CE issue tracker on GitLab.com](https://gitlab.com/gitlab-org/gitlab-ce/issues/) create an issue titled "GitLab X.X regressions" add the following text: @@ -176,116 +129,84 @@ Please do not raise issues directly in this issue but link to issues that might The decision to create a patch release or not is with the release manager who is assigned to this issue. The release manager will comment here about the plans for patch releases. -Assign the issue to the release manager and /cc all the core-team members active on the issue tracker. If there are any known bugs in the release add them immediately. +Assign the issue to the release manager and at mention all members of gitlab core team. If there are any known bugs in the release add them immediately. -### **4. Tweet** +## Tweet about RC1 Tweet about the RC release: > GitLab x.x.0.rc1 is out. This release candidate is only suitable for testing. Please link regressions issues from LINK_TO_REGRESSION_ISSUE -# **21st - Preparation** +## Prepare the blog post -### **1. Pre QA merge** +1. Start with a complete copy of the [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md) and fill it out. +1. Make sure the blog post contains information about the GitLab CI release. +1. Check the changelog of CE and EE for important changes. +1. Also check the CI changelog +1. Add a proposed tweet text to the blog post WIP MR description. +1. Create a WIP MR for the blog post +1. Ask Dmitriy (or a team member with OS X) to add screenshots to the WIP MR. +1. Decide with core team who will be the MVP user. +1. Create WIP MR for adding MVP to MVP page on website +1. Add a note if there are security fixes: This release fixes an important security issue and we advise everyone to upgrade as soon as possible. +1. Create a merge request on [GitLab.com](https://gitlab.com/gitlab-com/www-gitlab-com/tree/master) +1. Assign to one reviewer who will fix spelling issues by editing the branch (either with a git client or by using the online editor) +1. Comment to the reviewer: '@person Please mention the whole team as soon as you are done (3 workdays before release at the latest)' -Merge CE into EE before doing the QA. +## Create CE, EE, CI stable versions -### **2. QA** - -Create issue on dev.gitlab.org `gitlab` repository, named "GitLab X.X release" in order to keep track of the progress. - -Use the omnibus packages of Enterprise Edition using [this guide](https://dev.gitlab.org/gitlab/gitlab-ee/blob/master/doc/release/manual_testing.md). - -**NOTE** Upgrader can only be tested when tags are pushed to all repositories. Do not forget to confirm it is working before releasing. Note that in the issue. - -### **3. Fix anything coming out of the QA** - -Create an issue with description of a problem, if it is quick fix fix yourself otherwise contact the team for advice. - -# **22nd - Release CE and EE** - -For GitLab EE, append `-ee` to the branches and tags. - -`x-x-stable-ee` - -`v.x.x.0-ee` - -Merge CE into EE if needed. - -### **1. Create x-x-stable branch and push to the repositories** +Get release tools ``` -git checkout master -git pull -git checkout -b x-x-stable -git push x-x-stable +git clone git@dev.gitlab.org:gitlab/release-tools.git +cd release-tools ``` -### **2. Build the Omnibus packages** +Bump version, create release tag and push to remotes: + +``` +bundle exec rake release["x.x.0"] +``` + +This will create correct version and tag and push to all CE, EE and CI remotes. + +Update [installation.md](/doc/install/installation.md) to the newest version in master. + + +## Create Omnibus tags and build packages Follow the [release doc in the Omnibus repository](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md). This can happen before tagging because Omnibus uses tags in its own repo and SHA1's to refer to the GitLab codebase. -### **3. Set VERSION to x.x.x and push** +## Update GitLab.com with the stable version -Change the GITLAB_SHELL_VERSION file in `master` of the CE repository if the version changed. +- Deploy the package (should not need downtime because of the small difference with RC1) +- Deploy the package for ci.gitlab.com -Change the GITLAB_SHELL_VERSION file in `master` of the EE repository if the version changed. +## Release CE, EE and CI -Change the VERSION file in `master` branch of the CE repository and commit. Cherry-pick into the `x-x-stable` branch of CE. +__1. Publish packages for new release__ -Change the VERSION file in `master` branch of the EE repository and commit. Cherry-pick into the `x-x-stable-ee` branch of EE. +Update `downloads/index.html` and `downloads/archive/index.html` in `www-gitlab-com` repository. -### **4. Create annotated tag vx.x.x** - -In `x-x-stable` branch check for the SHA-1 of the commit with VERSION file changed. Tag that commit, - -``` -git tag -a vx.x.0 -m 'Version x.x.0' xxxxx -``` - -where `xxxxx` is SHA-1. - -### **5. Push the tag** - -``` -git push origin vx.x.0 -``` - -### **6. Push to remotes** - -For GitLab CE, push to dev, GitLab.com and GitHub. - -For GitLab EE, push to the subscribers repo. - -Make sure the branch is marked 'protected' on each of the remotes you pushed to. - -NOTE: You might not have the rights to push to master on dev. Ask Dmitriy. - -### **7. Publish blog for new release** +__2. Publish blog for new release__ +Doublecheck the everyone has been mentioned in the blog post. Merge the [blog merge request](#1-prepare-the-blog-post) in `www-gitlab-com` repository. -### **8. Tweet to blog** +__3. Tweet to blog__ Send out a tweet to share the good news with the world. List the most important features and link to the blog post. -Proposed tweet for CE "GitLab X.X is released! It brings *** " +Proposed tweet "Release of GitLab X.X & CI Y.Y! FEATURE, FEATURE and FEATURE <link-to-blog-post> #gitlab" -### **9. Send out the newsletter** +Consider creating a post on Hacker News. -Send out an email to the 'GitLab Newsletter' mailing list on MailChimp. -Replicate the former release newsletter and modify it accordingly. -Include a link to the blog post and keep it short. +## Release new AMIs -Proposed email text: -"We have released a new version of GitLab. See our blog post() for more information." +[Follow this guide](https://dev.gitlab.org/gitlab/AMI/blob/master/README.md) -# **23rd - Optional Patch Release** +## Create a WIP blogpost for the next release -# **24th - Update GitLab.com** - -Merge the stable release into GitLab.com. Once the build is green deploy the next morning. - -# **25th - Release GitLab CI** +Create a WIP blogpost using [release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/release_blog_template.md). diff --git a/doc/release/patch.md b/doc/release/patch.md index bcc14568fc..4c7b471785 100644 --- a/doc/release/patch.md +++ b/doc/release/patch.md @@ -10,22 +10,46 @@ Otherwise include it in the monthly release and note there was a regression fix ## Release Procedure +### Preparation + 1. Verify that the issue can be reproduced 1. Note in the 'GitLab X.X regressions' that you will create a patch 1. Create an issue on private GitLab development server 1. Name the issue "Release X.X.X CE and X.X.X EE", this will make searching easier 1. Fix the issue on a feature branch, do this on the private GitLab development server +1. If it is a security issue, then assign it to the release manager and apply a 'security' label 1. Consider creating and testing workarounds 1. After the branch is merged into master, cherry pick the commit(s) into the current stable branch -1. In a separate commit in the stable branch update the CHANGELOG -1. For EE, update the CHANGELOG-EE if it is EE specific fix. Otherwise, merge the stable CE branch and add to CHANGELOG-EE "Merge community edition changes for version X.X.X" -1. In a separate commit in the stable branch update the VERSION -1. Create an annotated tag vX.X.X for CE and another patch release for EE `git tag -a vx.x.x -m 'Version x.x.x'` 1. Make sure that the build has passed and all tests are passing -1. Push the code and the tags to all the CE and EE repositories -1. Apply the patch to GitLab Cloud and the private GitLab development server +1. In a separate commit in the master branch update the CHANGELOG +1. For EE, update the CHANGELOG-EE if it is EE specific fix. Otherwise, merge the stable CE branch and add to CHANGELOG-EE "Merge community edition changes for version X.X.X" +1. Merge CE stable branch into EE stable branch + + +### Bump version + +Get release tools + +``` +git clone git@dev.gitlab.org:gitlab/release-tools.git +cd release-tools +``` + +Bump all versions in stable branch, even if the changes affect only EE, CE, or CI. Since all the versions are synced now, +it doesn't make sense to say upgrade CE to 7.2, EE to 7.3 and CI to 7.1. + +Create release tag and push to remotes: + +``` +bundle exec rake release["x.x.x"] +``` + +### Release + 1. [Build new packages with the latest version](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/release.md) -1. Cherry-pick the changelog update back into master -1. Send tweets about the release from `@gitlabhq`, tweet should include the most important feature that the release is addressing as well as the link to the changelog +1. Apply the patch to GitLab.com and the private GitLab development server +1. Apply the patch to ci.gitLab.com and the private GitLab CI development server +1. Create and publish a blog post, see [patch release blog template](https://gitlab.com/gitlab-com/www-gitlab-com/blob/master/doc/patch_release_blog_template.md) +1. Send tweets about the release from `@gitlab`, tweet should include the most important feature that the release is addressing and link to the blog post 1. Note in the 'GitLab X.X regressions' issue that the patch was published (CE only) -1. Send out an email to the 'GitLab Newsletter' mailing list on MailChimp (or the 'Subscribers' list if the patch is EE only) +1. [Create new AMIs](https://dev.gitlab.org/gitlab/AMI/blob/master/README.md) diff --git a/doc/release/security.md b/doc/release/security.md index da442de6ee..60bcfbb6da 100644 --- a/doc/release/security.md +++ b/doc/release/security.md @@ -8,20 +8,24 @@ Do a security release when there is a critical issue that needs to be addresses ## Security vulnerability disclosure -Please report suspected security vulnerabilities in private to , also see the [disclosure section on the GitLab.com website](http://www.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. +Please report suspected security vulnerabilities in private to , also see the [disclosure section on the GitLab.com website](http://about.gitlab.com/disclosure/). Please do NOT create publicly viewable issues for suspected security vulnerabilities. ## Release Procedure 1. Verify that the issue can be reproduced 1. Acknowledge the issue to the researcher that disclosed it +1. Inform the release manager that there needs to be a security release 1. Do the steps from [patch release document](doc/release/patch.md), starting with "Create an issue on private GitLab development server" +1. The MR with the security fix should get a 'security' label and be assigned to the release manager +1. Build the package for GitLab.com and do a deploy +1. Build the package for ci.gitLab.com and do a deploy +1. [Create new AMIs](https://dev.gitlab.org/gitlab/AMI/blob/master/README.md) 1. Create feature branches for the blog post on GitLab.com and link them from the code branch 1. Merge and publish the blog posts 1. Send tweets about the release from `@gitlabhq` -1. Send out an email to the 'GitLab Newsletter' mailing list on MailChimp (or the 'Subscribers' list if the security fix is for EE only) 1. Send out an email to [the community google mailing list](https://groups.google.com/forum/#!forum/gitlabhq) -1. Post a signed copy of our complete announcement to [oss-security](http://www.openwall.com/lists/oss-security/) and request a CVE number -1. Add the security researcher to the [Security Researcher Acknowledgments list](http://www.gitlab.com/vulnerability-acknowledgements/) +1. Post a signed copy of our complete announcement to [oss-security](http://www.openwall.com/lists/oss-security/) and request a CVE number. CVE is only needed for bugs that allow someone to own the server (Remote Code Execution) or access to code of projects they are not a member of. +1. Add the security researcher to the [Security Researcher Acknowledgments list](http://about.gitlab.com/vulnerability-acknowledgements/) 1. Thank the security researcher in an email for their cooperation 1. Update the blog post and the CHANGELOG when we receive the CVE number diff --git a/doc/security/README.md b/doc/security/README.md index b89e8cbe02..49dfa6eec7 100644 --- a/doc/security/README.md +++ b/doc/security/README.md @@ -2,3 +2,5 @@ - [Password length limits](password_length_limits.md) - [Rack attack](rack_attack.md) +- [Web Hooks and insecure internal web services](webhooks.md) +- [Information exclusivity](information_exclusivity.md) diff --git a/doc/security/information_exclusivity.md b/doc/security/information_exclusivity.md new file mode 100644 index 0000000000..f8e7fc3fd0 --- /dev/null +++ b/doc/security/information_exclusivity.md @@ -0,0 +1,9 @@ +# Information exclusivity + +Git is a distributed version control system (DVCS). +This means that everyone that works with the source code has a local copy of the complete repository. +In GitLab every project member that is not a guest (so reporters, developers and masters) can clone the repository to get a local copy. +After obtaining this local copy the user can upload the full repository anywhere, including another project under their control or another server. +The consequence is that you can't build access controls that prevent the intentional sharing of source code by users that have access to the source code. +This is an inherent feature of a DVCS and all git management systems have this limitation. +Obviously you can take steps to prevent unintentional sharing and information destruction, this is why only some people are allowed to invite others and nobody can force push a protected branch. diff --git a/doc/security/webhooks.md b/doc/security/webhooks.md new file mode 100644 index 0000000000..1e9d33e87c --- /dev/null +++ b/doc/security/webhooks.md @@ -0,0 +1,13 @@ +# Web Hooks and insecure internal web services + +If you have non-GitLab web services running on your GitLab server or within its local network, these may be vulnerable to exploitation via Web Hooks. + +With [Web Hooks](../web_hooks/web_hooks.md), you and your project masters and owners can set up URLs to be triggered when specific things happen to projects. Normally, these requests are sent to external web services specifically set up for this purpose, that process the request and its attached data in some appropriate way. + +Things get hairy, however, when a Web Hook is set up with a URL that doesn't point to an external, but to an internal service, that may do something completely unintended when the web hook is triggered and the POST request is sent. + +Because Web Hook requests are made by the GitLab server itself, these have complete access to everything running on the server (http://localhost:123) or within the server's local network (http://192.168.1.12:345), even if these services are otherwise protected and inaccessible from the outside world. + +If a web service does not require authentication, Web Hooks can be used to trigger destructive commands by getting the GitLab server to make POST requests to endpoints like "http://localhost:123/some-resource/delete". + +To prevent this type of exploitation from happening, make sure that you are aware of every web service GitLab could potentially have access to, and that all of these are set up to require authentication for every potentially destructive command. Enabling authentication but leaving a default password is not enough. \ No newline at end of file diff --git a/doc/ssh/README.md b/doc/ssh/README.md index c87fffd7d2..0acb15896d 100644 --- a/doc/ssh/README.md +++ b/doc/ssh/README.md @@ -1,4 +1,73 @@ # SSH -- [Deploy keys](deploy_keys.md) -- [SSH](ssh.md) +## SSH keys + +An SSH key allows you to establish a secure connection between your +computer and GitLab. + +Before generating an SSH key, check if your system already has one by +running `cat ~/.ssh/id_rsa.pub`. If you see a long string starting with +`ssh-rsa` or `ssh-dsa`, you can skip the ssh-keygen step. + +To generate a new SSH key, just open your terminal and use code below. The +ssh-keygen command prompts you for a location and filename to store the key +pair and for a password. When prompted for the location and filename, you +can press enter to use the default. + +It is a best practice to use a password for an SSH key, but it is not +required and you can skip creating a password by pressing enter. Note that +the password you choose here can't be altered or retrieved. + +```bash +ssh-keygen -t rsa -C "$your_email" +``` + +Use the code below to show your public key. + +```bash +cat ~/.ssh/id_rsa.pub +``` + +Copy-paste the key to the 'My SSH Keys' section under the 'SSH' tab in your +user profile. Please copy the complete key starting with `ssh-` and ending +with your username and host. + +Use code below to copy your public key to the clipboard. Depending on your +OS you'll need to use a different command: + +**Windows:** +```bash +clip < ~/.ssh/id_rsa.pub +``` + +**Mac:** +```bash +pbcopy < ~/.ssh/id_rsa.pub +``` + +**GNU/Linux (requires xclip):** +```bash +xclip -sel clip < ~/.ssh/id_rsa.pub +``` + +## Deploy keys + +Deploy keys allow read-only access to multiple projects with a single SSH +key. + +This is really useful for cloning repositories to your Continuous +Integration (CI) server. By using deploy keys, you don't have to setup a +dummy user account. + +If you are a project master or owner, you can add a deploy key in the +project settings under the section 'Deploy Keys'. Press the 'New Deploy +Key' button and upload a public SSH key. After this, the machine that uses +the corresponding private key has read-only access to the project. + +You can't add the same deploy key twice with the 'New Deploy Key' option. +If you want to add the same key to another project, please enable it in the +list that says 'Deploy keys from projects available to you'. All the deploy +keys of all the projects you have access to are available. This project +access can happen through being a direct member of the project, or through +a group. See `def accessible_deploy_keys` in `app/models/user.rb` for more +information. diff --git a/doc/ssh/deploy_keys.md b/doc/ssh/deploy_keys.md deleted file mode 100644 index dcca8bdc61..0000000000 --- a/doc/ssh/deploy_keys.md +++ /dev/null @@ -1,9 +0,0 @@ -# Deploy keys - -Deploy keys allow read-only access one or multiple projects with a single SSH key. - -This is really useful for cloning repositories to your Continuous Integration (CI) server. By using a deploy keys you don't have to setup a dummy user account. - -If you are a project master or owner you can add a deploy key in the project settings under the section Deploy Keys. Press the 'New Deploy Key' button and upload a public ssh key. After this the machine that uses the corresponding private key has read-only access to the project. - -You can't add the same deploy key twice with the 'New Deploy Key' option. If you want to add the same key to another project please enable it in the list that says 'Deploy keys from projects available to you'. All the deploy keys of all the projects you have access to are available. This project access can happen through being a direct member of the project or through a group. See `def accessible_deploy_keys` in `app/models/user.rb` for more information. diff --git a/doc/ssh/ssh.md b/doc/ssh/ssh.md deleted file mode 100644 index d466c1bde7..0000000000 --- a/doc/ssh/ssh.md +++ /dev/null @@ -1,21 +0,0 @@ -# SSH keys - -SSH key allows you to establish a secure connection between your computer and GitLab - -Before generating an SSH key, check if your system already has one by running `cat ~/.ssh/id_rsa.pub` If your see a long string starting with `ssh-rsa` or `ssh-dsa`, you can skip the ssh-keygen step. - -To generate a new SSH key just open your terminal and use code below. The ssh-keygen command prompts you for a location and filename to store the key pair and for a password. When prompted for the location and filename you can press enter to use the default. -It is a best practice to use a password for an SSH key but it is not required and you can skip creating a password by pressing enter. -Note that the password you choose here can't be altered or retrieved. - -```bash -ssh-keygen -t rsa -C "$your_email" -``` - -Use the code below to show your public key. - -```bash -cat ~/.ssh/id_rsa.pub -``` - -Copy-paste the key to the 'My SSH Keys' section under the 'SSH' tab in your user profile. Please copy the complete key starting with `ssh-` and ending with your username and host. diff --git a/doc/system_hooks/system_hooks.md b/doc/system_hooks/system_hooks.md index 47f17c1a08..f9b6d37d84 100644 --- a/doc/system_hooks/system_hooks.md +++ b/doc/system_hooks/system_hooks.md @@ -1,6 +1,6 @@ # System hooks -Your GitLab instance can perform HTTP POST requests on the following events: `create_project`, `delete_project`, `create_user`, `delete_user` and `change_team_member`. +Your GitLab instance can perform HTTP POST requests on the following events: `project_create`, `project_destroy`, `user_add_to_team`, `user_remove_from_team`, `user_create`, `user_destroy`, `key_create`, `key_destroy`, `group_create`, `group_destroy`, `user_add_to_group` and `user_remove_from_group`. System hooks can be used, e.g. for logging or changing information in a LDAP server. @@ -15,8 +15,8 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser "name": "StoreCloud", "owner_email": "johnsmith@gmail.com", "owner_name": "John Smith", - "path": "stormcloud", - "path_with_namespace": "jsmith/stormcloud", + "path": "storecloud", + "path_with_namespace": "jsmith/storecloud", "project_id": 74, "project_visibility": "private", } @@ -50,6 +50,7 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser "project_path": "storecloud", "user_email": "johnsmith@gmail.com", "user_name": "John Smith", + "user_id": 41, "project_visibility": "private", } ``` @@ -66,6 +67,7 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser "project_path": "storecloud", "user_email": "johnsmith@gmail.com", "user_name": "John Smith", + "user_id": 41, "project_visibility": "private", } ``` @@ -93,3 +95,86 @@ System hooks can be used, e.g. for logging or changing information in a LDAP ser "user_id": 41 } ``` + +**Key added** + +```json +{ + "event_name": "key_create", + "created_at": "2014-08-18 18:45:16 UTC", + "username": "root", + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC58FwqHUbebw2SdT7SP4FxZ0w+lAO/erhy2ylhlcW/tZ3GY3mBu9VeeiSGoGz8hCx80Zrz+aQv28xfFfKlC8XQFpCWwsnWnQqO2Lv9bS8V1fIHgMxOHIt5Vs+9CAWGCCvUOAurjsUDoE2ALIXLDMKnJxcxD13XjWdK54j6ZXDB4syLF0C2PnAQSVY9X7MfCYwtuFmhQhKaBussAXpaVMRHltie3UYSBUUuZaB3J4cg/7TxlmxcNd+ppPRIpSZAB0NI6aOnqoBCpimscO/VpQRJMVLr3XiSYeT6HBiDXWHnIVPfQc03OGcaFqOit6p8lYKMaP/iUQLm+pgpZqrXZ9vB john@localhost", + "id": 4 +} +``` + +**Key removed** + +```json +{ + "event_name": "key_destroy", + "created_at": "2014-08-18 18:45:16 UTC", + "username": "root", + "key": "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC58FwqHUbebw2SdT7SP4FxZ0w+lAO/erhy2ylhlcW/tZ3GY3mBu9VeeiSGoGz8hCx80Zrz+aQv28xfFfKlC8XQFpCWwsnWnQqO2Lv9bS8V1fIHgMxOHIt5Vs+9CAWGCCvUOAurjsUDoE2ALIXLDMKnJxcxD13XjWdK54j6ZXDB4syLF0C2PnAQSVY9X7MfCYwtuFmhQhKaBussAXpaVMRHltie3UYSBUUuZaB3J4cg/7TxlmxcNd+ppPRIpSZAB0NI6aOnqoBCpimscO/VpQRJMVLr3XiSYeT6HBiDXWHnIVPfQc03OGcaFqOit6p8lYKMaP/iUQLm+pgpZqrXZ9vB john@localhost", + "id": 4 +} +``` + +**Group created:** + +```json +{ + "created_at": "2012-07-21T07:30:54Z", + "event_name": "group_create", + "name": "StoreCloud", + "owner_email": "johnsmith@gmail.com", + "owner_name": "John Smith", + "path": "storecloud", + "group_id": 78 +} +``` + +**Group removed:** + +```json +{ + "created_at": "2012-07-21T07:30:54Z", + "event_name": "group_destroy", + "name": "StoreCloud", + "owner_email": "johnsmith@gmail.com", + "owner_name": "John Smith", + "path": "storecloud", + "group_id": 78 +} +``` + +**New Group Member:** + +```json +{ + "created_at": "2012-07-21T07:30:56Z", + "event_name": "user_add_to_group", + "group_access": "Master", + "group_id": 78, + "group_name": "StoreCloud", + "group_path": "storecloud", + "user_email": "johnsmith@gmail.com", + "user_name": "John Smith", + "user_id": 41 +} +``` +**Group Member Removed:** + +```json +{ + "created_at": "2012-07-21T07:30:56Z", + "event_name": "user_remove_from_group", + "group_access": "Master", + "group_id": 78, + "group_name": "StoreCloud", + "group_path": "storecloud", + "user_email": "johnsmith@gmail.com", + "user_name": "John Smith", + "user_id": 41 +} +``` diff --git a/doc/update/2.6-to-3.0.md b/doc/update/2.6-to-3.0.md index 6aabbe095d..4827ef9501 100644 --- a/doc/update/2.6-to-3.0.md +++ b/doc/update/2.6-to-3.0.md @@ -1,4 +1,5 @@ # From 2.6 to 3.0 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/2.6-to-3.0.md) for the most up to date instructions.* ## 1. Stop server & resque @@ -22,29 +23,29 @@ sudo -u gitlab bundle exec rake db:migrate RAILS_ENV=production # !!! Config should be replaced with a new one. Check it after replace cp config/gitlab.yml.example config/gitlab.yml -# update gitolite hooks +# update Gitolite hooks -# GITOLITE v2: +# Gitolite v2: sudo cp ./lib/hooks/post-receive /home/git/share/gitolite/hooks/common/post-receive sudo chown git:git /home/git/share/gitolite/hooks/common/post-receive -# GITOLITE v3: +# Gitolite v3: sudo cp ./lib/hooks/post-receive /home/git/.gitolite/hooks/common/post-receive sudo chown git:git /home/git/.gitolite/hooks/common/post-receive # set valid path to hooks in gitlab.yml in git_host section # like this git_host: - # gitolite 2 + # Gitolite 2 hooks_path: /home/git/share/gitolite/hooks - # gitolite 3 + # Gitolite 3 hooks_path: /home/git/.gitolite/hooks/ -# Make some changes to gitolite config +# Make some changes to Gitolite config # For more information visit https://github.com/gitlabhq/gitlabhq/pull/1719 -# gitolite v2 +# Gitolite v2 sudo -u git -H sed -i 's/\(GL_GITCONFIG_KEYS\s*=>*\s*\).\{2\}/\\1"\.\*"/g' /home/git/.gitolite.rc # gitlite v3 diff --git a/doc/update/2.9-to-3.0.md b/doc/update/2.9-to-3.0.md index 8af86b0dc9..f4a997a8c5 100644 --- a/doc/update/2.9-to-3.0.md +++ b/doc/update/2.9-to-3.0.md @@ -1,4 +1,5 @@ # From 2.9 to 3.0 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/2.9-to-3.0.md) for the most up to date instructions.* ## 1. Stop server & resque diff --git a/doc/update/3.0-to-3.1.md b/doc/update/3.0-to-3.1.md index 3206df3499..a30485c42f 100644 --- a/doc/update/3.0-to-3.1.md +++ b/doc/update/3.0-to-3.1.md @@ -1,4 +1,5 @@ # From 3.0 to 3.1 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/3.0-to-3.1.md) for the most up to date instructions.* **IMPORTANT!** diff --git a/doc/update/3.1-to-4.0.md b/doc/update/3.1-to-4.0.md index 165f4e6a30..f1ef4df474 100644 --- a/doc/update/3.1-to-4.0.md +++ b/doc/update/3.1-to-4.0.md @@ -1,4 +1,5 @@ # From 3.1 to 4.0 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/3.1-to-4.0.md) for the most up to date instructions.* ## Important changes diff --git a/doc/update/4.0-to-4.1.md b/doc/update/4.0-to-4.1.md index 4149ed6b08..d89d523591 100644 --- a/doc/update/4.0-to-4.1.md +++ b/doc/update/4.0-to-4.1.md @@ -1,4 +1,5 @@ # From 4.0 to 4.1 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/4.0-to-4.1.md) for the most up to date instructions.* ## Important changes diff --git a/doc/update/4.1-to-4.2.md b/doc/update/4.1-to-4.2.md index 5ee8e8781e..6fe4412ff9 100644 --- a/doc/update/4.1-to-4.2.md +++ b/doc/update/4.1-to-4.2.md @@ -1,4 +1,5 @@ # From 4.1 to 4.2 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/4.1-to-4.2.md) for the most up to date instructions.* ## 1. Stop server & Resque diff --git a/doc/update/4.2-to-5.0.md b/doc/update/4.2-to-5.0.md index 6ec153f624..f9faf65f95 100644 --- a/doc/update/4.2-to-5.0.md +++ b/doc/update/4.2-to-5.0.md @@ -1,4 +1,5 @@ # From 4.2 to 5.0 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/4.2-to-5.0.md) for the most up to date instructions.* ## Warning @@ -10,7 +11,7 @@ GitLab 5.0 is affected by critical security vulnerability CVE-2013-4490. - Self signed SSL certificates are not supported until GitLab 5.1 - **requires ruby1.9.3** -## 0. Stop gitlab +## 0. Stop GitLab sudo service gitlab stop @@ -41,22 +42,22 @@ git checkout v1.1.0 # copy config cp config.yml.example config.yml -# change url to gitlab instance -# ! make sure url end with '/' like 'https://gitlab.example/' +# change URL to GitLab instance +# ! make sure the URL ends with '/' like 'https://gitlab.example/' vim config.yml # rewrite hooks ./support/rewrite-hooks.sh # check ruby version for git user ( 1.9 required!! ) -# gitlab shell requires system ruby 1.9 +# GitLab shell requires system ruby 1.9 ruby -v # exit from git user exit ``` -## 4. Copy gitlab instance to git user +## 4. Copy GitLab instance to git user ```bash sudo cp -R /home/gitlab/gitlab /home/git/gitlab @@ -111,7 +112,7 @@ sudo chmod -R u+rwX /home/git/gitlab/tmp/pids ``` -## 6. Update init.d script and nginx config +## 6. Update init.d script and Nginx config ```bash # init.d @@ -123,7 +124,7 @@ sudo chmod +x /etc/init.d/gitlab sudo -u git -H cp /home/git/gitlab/config/unicorn.rb /home/git/gitlab/config/unicorn.rb.old sudo -u git -H cp /home/git/gitlab/config/unicorn.rb.example /home/git/gitlab/config/unicorn.rb -#nginx +# Nginx # Replace path from '/home/gitlab/' to '/home/git/' sudo vim /etc/nginx/sites-enabled/gitlab sudo service nginx restart @@ -137,7 +138,7 @@ sudo service gitlab start # check if unicorn and sidekiq started # If not try to logout, also check replaced path from '/home/gitlab/' to '/home/git/' -# in nginx, unicorn, init.d etc +# in Nginx, unicorn, init.d etc ps aux | grep unicorn ps aux | grep sidekiq @@ -162,8 +163,49 @@ sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production ``` -**P.S. If everything works as expected you can remove gitlab user from system** +## 9. Cleanup + +**If everything works as expected you can cleanup some old things** +Recommend you wait a bit and do a backup before completing the following. ```bash +# remove GitLab user from system sudo userdel -r gitlab + +cd /home/git + +# cleanup .profile +## remove text from .profile added during gitolite installation: +## PATH=\$PATH:/home/git/bin +## export PATH +## to see what a clean .profile for new users on your system would look like see /etc/skel/.profile +sudo -u git -H vim .profile + +# remove gitolite +sudo rm -R bin +sudo rm -Rf gitolite +sudo rm -R .gitolite +sudo rm .gitolite.rc +sudo rm -f gitlab.pub +sudo rm projects.list + +# reset tmp folders +sudo service gitlab stop +cd /home/git/gitlab +sudo rm -R tmp +sudo -u git -H mkdir tmp +sudo chmod -R u+rwX tmp/ + +# create directory for pids, make sure GitLab can write to it +sudo -u git -H mkdir tmp/pids/ +sudo chmod -R u+rwX tmp/pids/ + +# if you are already running a newer version of GitLab check that installation guide for other tmp folders you need to create + +# reboot system +sudo reboot + +# login, check that GitLab is running fine +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production ``` diff --git a/doc/update/5.0-to-5.1.md b/doc/update/5.0-to-5.1.md index 0e597abb1a..9fbd1f8851 100644 --- a/doc/update/5.0-to-5.1.md +++ b/doc/update/5.0-to-5.1.md @@ -1,4 +1,5 @@ # From 5.0 to 5.1 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.0-to-5.1.md) for the most up to date instructions.* ## Warning diff --git a/doc/update/5.1-to-5.2.md b/doc/update/5.1-to-5.2.md index 6ef559ac9f..cf9c4e4f77 100644 --- a/doc/update/5.1-to-5.2.md +++ b/doc/update/5.1-to-5.2.md @@ -1,4 +1,5 @@ # From 5.1 to 5.2 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.1-to-5.2.md) for the most up to date instructions.* ## Warning diff --git a/doc/update/5.1-to-5.4.md b/doc/update/5.1-to-5.4.md index 8ec56b266c..97a98ede07 100644 --- a/doc/update/5.1-to-5.4.md +++ b/doc/update/5.1-to-5.4.md @@ -1,4 +1,5 @@ # From 5.1 to 5.4 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.1-to-5.4.md) for the most up to date instructions.* Also works starting from 5.2. diff --git a/doc/update/5.1-to-6.0.md b/doc/update/5.1-to-6.0.md index 8870f5bc85..a3fdd92bd2 100644 --- a/doc/update/5.1-to-6.0.md +++ b/doc/update/5.1-to-6.0.md @@ -1,4 +1,5 @@ # From 5.1 to 6.0 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.1-to-6.0.md) for the most up to date instructions.* ## Warning @@ -28,7 +29,7 @@ Any changes to group members will immediately be reflected in the project permis You can even have multiple owners for a group, greatly simplifying administration. -## 0. Backup +## 0. Backup & prepare for update It's useful to make a backup just in case things go south: (With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) @@ -38,6 +39,72 @@ cd /home/git/gitlab sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production ``` +The migrations in this update are very sensitive to incomplete or inconsistent data. If you have a long-running GitLab installation and some of the previous upgrades did not work out 100% correct this may bite you now. The following can help you have a more smooth upgrade. + +### Find projects with invalid project names + +#### MySQL +Login to MySQL: + + mysql -u root -p + +Find projects with invalid names: + +```bash +mysql> use gitlabhq_production; + +# find projects with invalid first char, projects must start with letter +mysql> select name from projects where name REGEXP '^[^A-Za-z]'; + +# find projects with other invalid chars +## names must only contain alphanumeric chars, underscores, spaces, periods, and dashes +mysql> select name from projects where name REGEXP '[^a-zA-Z0-9_ .-]+'; +``` + +If any projects have invalid names try correcting them from the web interface before starting the upgrade. +If correcting them from the web interface fails you can correct them using MySQL: + +```bash +# e.g. replace invalid / with allowed _ +mysql> update projects set name = REPLACE(name,'/','_'); +# repeat for all invalid chars found in project names +``` + +#### PostgreSQL +Make sure all project names start with a letter and only contain alphanumeric chars, underscores, spaces, periods, and dashes (a-zA-Z0-9_ .-). + +### Find other common errors + +``` +cd /home/git/gitlab +# Start rails console +sudo -u git -H bin/rails console production + +# Make sure none of the following rails commands return results + +# All project owners should have an owner: +Project.all.select { |project| project.owner.blank? } + +# Every user should have a namespace: +User.all.select { |u| u.namespace.blank? } + +# Projects in the global namespace should not conflict with projects in the owner namespace: +Project.where(namespace_id: nil).select { |p| Project.where(path: p.path, namespace_id: p.owner.try(:namespace).try(:id)).present? } +``` + +If any of the above rails commands returned results other than `=> []` try correcting the issue from the web interface. + +If you find projects without an owner (first rails command above), correct it. For MySQL setups: + +```bash +# get your user id +mysql> select id, name from users order by name; + +# set yourself as owner of project +# replace your_user_id with your user id and bad_project_id with the project id from the rails command +mysql> update projects set creator_id=your_user_id where id=bad_project_id; +``` + ## 1. Stop server sudo service gitlab stop @@ -147,31 +214,3 @@ Follow the [upgrade guide from 5.0 to 5.1](5.0-to-5.1.md), except for the databa cd /home/git/gitlab sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production ``` - -## Troubleshooting - -The migrations in this update are very sensitive to incomplete or inconsistent data. If you have a long-running GitLab installation and some of the previous upgrades did not work out 100% correct this may bite you now. The following commands can be run in the rails console to look for 'bad' data. - -Start rails console: - -``` -sudo -u git -H rails console production -``` - -All project owners should have an owner: - -``` -Project.all.select { |project| project.owner.blank? } -``` - -Every user should have a namespace: - -``` -User.all.select { |u| u.namespace.blank? } -``` - -Projects in the global namespace should not conflict with projects in the owner namespace: - -``` -Project.where(namespace_id: nil).select { |p| Project.where(path: p.path, namespace_id: p.owner.try(:namespace).try(:id)).present? } -``` diff --git a/doc/update/5.2-to-5.3.md b/doc/update/5.2-to-5.3.md index 61ddf13564..27613aeda0 100644 --- a/doc/update/5.2-to-5.3.md +++ b/doc/update/5.2-to-5.3.md @@ -1,4 +1,5 @@ # From 5.2 to 5.3 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.2-to-5.3.md) for the most up to date instructions.* ## Warning diff --git a/doc/update/5.3-to-5.4.md b/doc/update/5.3-to-5.4.md index 8a0d43e3e6..577b9a585f 100644 --- a/doc/update/5.3-to-5.4.md +++ b/doc/update/5.3-to-5.4.md @@ -1,4 +1,5 @@ # From 5.3 to 5.4 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.3-to-5.4.md) for the most up to date instructions.* ## 0. Backup diff --git a/doc/update/5.4-to-6.0.md b/doc/update/5.4-to-6.0.md index 7bf7bce6aa..d9c6d9bfb9 100644 --- a/doc/update/5.4-to-6.0.md +++ b/doc/update/5.4-to-6.0.md @@ -1,16 +1,20 @@ # From 5.4 to 6.0 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/5.4-to-6.0.md) for the most up to date instructions.* ## Warning GitLab 6.0 is affected by critical security vulnerabilities CVE-2013-4490 and CVE-2013-4489. +**You need to follow this guide first, before updating past 6.0, as it contains critical migration steps that are only present +in the `6-0-stable` branch** + ## Deprecations ### Global projects The root (global) namespace for projects is deprecated. -So you need to move all your global projects under groups or users manually before update or they will be automatically moved to the project owner namespace during the update. When a project is moved all its members will receive an email with instructions how to update their git remote url. Please make sure you disable sending email when you do a test of the upgrade. +So you need to move all your global projects under groups or users manually before update or they will be automatically moved to the project owner namespace during the update. When a project is moved all its members will receive an email with instructions how to update their git remote URL. Please make sure you disable sending email when you do a test of the upgrade. ### Teams diff --git a/doc/update/6.0-to-6.1.md b/doc/update/6.0-to-6.1.md index b8df16bfd9..c5eba1c01c 100644 --- a/doc/update/6.0-to-6.1.md +++ b/doc/update/6.0-to-6.1.md @@ -1,4 +1,5 @@ # From 6.0 to 6.1 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.0-to-6.1.md) for the most up to date instructions.* ## Warning @@ -73,7 +74,6 @@ sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production ```bash sudo rm /etc/init.d/gitlab sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab -sudo chmod +x /etc/init.d/gitlab ``` ## 7. Start application diff --git a/doc/update/6.0-to-7.1.md b/doc/update/6.0-to-7.1.md deleted file mode 100644 index 84deaf3376..0000000000 --- a/doc/update/6.0-to-7.1.md +++ /dev/null @@ -1,182 +0,0 @@ -# From 6.0 to 7.1 - -## Deprecations - -The 'Wall' feature has been removed in GitLab 7.1. Existing wall comments will remain stored in the database after the upgrade. - -## Global issue numbers - -As of 6.1 issue numbers are project specific. This means all issues are renumbered and get a new number in their URL. If you use an old issue number URL and the issue number does not exist yet you are redirected to the new one. This conversion does not trigger if the old number already exists for this project, this is unlikely but will happen with old issues and large projects. - -## 0. Backup - -It's useful to make a backup just in case things go south: -(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) - -```bash -cd /home/git/gitlab -sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production -``` - -## 1. Stop server - - sudo service gitlab stop - -## 2. Update Ruby - -If you are still using Ruby 1.9.3 or below, you will need to update Ruby. -You can check which version you are running with `ruby -v`. - -If you are you running Ruby 2.0.x, you do not need to upgrade ruby, but can consider doing so for performance reasons. - -If you are running Ruby 2.1.1 consider upgrading to 2.1.2, because of the high memory usage of Ruby 2.1.1. - -Install, update dependencies: - -```bash -sudo apt-get install build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl -``` - -Download and compile Ruby: - -```bash -mkdir /tmp/ruby && cd /tmp/ruby -curl --progress ftp://ftp.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz | tar xz -cd ruby-2.1.2 -./configure --disable-install-rdoc -make -sudo make install -``` - -Install Bundler: - -```bash -sudo gem install bundler --no-ri --no-rdoc -``` - -## 3. Get latest code - -```bash -cd /home/git/gitlab -sudo -u git -H git fetch --all -``` - -For GitLab Community Edition: - -```bash -sudo -u git -H git checkout 7-1-stable -``` - -OR - -For GitLab Enterprise Edition: - -```bash -sudo -u git -H git checkout 7-1-stable-ee -``` - - -## 4. Install additional packages - -```bash -# Add support for lograte for better log file handling -sudo apt-get install logrotate -``` - -## 5. Update gitlab-shell - -```bash -cd /home/git/gitlab-shell -sudo -u git -H git fetch -sudo -u git -H git checkout v1.9.6 # Addresses multiple critical security vulnerabilities -``` - -## 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 development test postgres --deployment - -# PostgreSQL installations (note: the line below states '--without ... mysql') -sudo -u git -H bundle install --without development test mysql --deployment - - -# Run database migrations -sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production - -# Enable internal issue IDs (introduced in GitLab 6.1) -sudo -u git -H bundle exec rake migrate_iids RAILS_ENV=production - -# Clean up assets and cache -sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production - -# Close access to gitlab-satellites for others -sudo chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites -``` - -## 7. Update config files - -TIP: to see what changed in gitlab.yml.example in this release use next command: - -``` -git diff 6-0-stable:config/gitlab.yml.example 7-1-stable:config/gitlab.yml.example -``` - -* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-1-stable/config/gitlab.yml.example but with your settings. -* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-1-stable/config/unicorn.rb.example but with your settings. -* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v1.9.6/config.yml.example but with your settings. -* Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-1-stable/lib/support/nginx/gitlab but with your settings. -* Copy rack attack middleware config - -```bash -sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb -``` - -* Set up logrotate - -```bash -sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab -``` - -## 8. Update Init script - -```bash -sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab -``` - -## 9. Start application - - sudo service gitlab start - sudo service nginx restart - -## 10. Check application status - -Check if GitLab and its environment are configured correctly: - - cd /home/git/gitlab - sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production - -To make sure you didn't miss anything run a more thorough check with: - - sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production - -If all items are green, then congratulations upgrade complete! - -## Things went south? Revert to previous version (6.0) - -### 1. Revert the code to the previous version - -Follow the [upgrade guide from 5.4 to 6.0](5.4-to-6.0.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 -``` - -## Login issues after upgrade? - -If running in HTTPS mode, be sure to read [Can't Verify CSRF token authenticity](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide#cant-verify-csrf-token-authenticitycant-get-past-login-pageredirected-to-login-page) diff --git a/doc/update/6.0-to-7.2.md b/doc/update/6.0-to-7.2.md deleted file mode 100644 index 51e260b9e6..0000000000 --- a/doc/update/6.0-to-7.2.md +++ /dev/null @@ -1,194 +0,0 @@ -# From 6.0 to 7.2 - -## Global issue numbers - -As of 6.1 issue numbers are project specific. This means all issues are renumbered and get a new number in their URL. If you use an old issue number URL and the issue number does not exist yet you are redirected to the new one. This conversion does not trigger if the old number already exists for this project, this is unlikely but will happen with old issues and large projects. - -## Editable labels - -In GitLab 7.2 we replace Issue and Merge Request tags with labels, making it -possible to edit the label text and color. The characters '?', '&' and ',' are -no longer allowed however so those will be removed from your tags during the -database migrations for GitLab 7.2. - -## 0. Backup - -It's useful to make a backup just in case things go south: -(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) - -```bash -cd /home/git/gitlab -sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production -``` - -## 1. Stop server - - sudo service gitlab stop - -## 2. Update Ruby - -If you are still using Ruby 1.9.3 or below, you will need to update Ruby. -You can check which version you are running with `ruby -v`. - -If you are you running Ruby 2.0.x, you do not need to upgrade ruby, but can consider doing so for performance reasons. - -If you are running Ruby 2.1.1 consider upgrading to 2.1.2, because of the high memory usage of Ruby 2.1.1. - -Install, update dependencies: - -```bash -sudo apt-get install build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl -``` - -Download and compile Ruby: - -```bash -mkdir /tmp/ruby && cd /tmp/ruby -curl --progress ftp://ftp.ruby-lang.org/pub/ruby/2.1/ruby-2.1.2.tar.gz | tar xz -cd ruby-2.1.2 -./configure --disable-install-rdoc -make -sudo make install -``` - -Install Bundler: - -```bash -sudo gem install bundler --no-ri --no-rdoc -``` - -## 3. Get latest code - -```bash -cd /home/git/gitlab -sudo -u git -H git fetch --all -``` - -For GitLab Community Edition: - -```bash -sudo -u git -H git checkout 7-2-stable -``` - -OR - -For GitLab Enterprise Edition: - -```bash -sudo -u git -H git checkout 7-2-stable-ee -``` - - -## 4. Install additional packages - -```bash -# Add support for lograte for better log file handling -sudo apt-get install logrotate - -# Install pkg-config and cmake, which is needed for the latest versions of rugged -sudo apt-get install pkg-config cmake -``` - -## 5. Update gitlab-shell - -```bash -cd /home/git/gitlab-shell -sudo -u git -H git fetch -sudo -u git -H git checkout v1.9.7 -``` - -## 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 development test postgres --deployment - -# PostgreSQL installations (note: the line below states '--without ... mysql') -sudo -u git -H bundle install --without development test mysql --deployment - - -# Run database migrations -sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production - -# Enable internal issue IDs (introduced in GitLab 6.1) -sudo -u git -H bundle exec rake migrate_iids RAILS_ENV=production - -# Clean up assets and cache -sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production - -# Close access to gitlab-satellites for others -sudo chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites -``` - -## 7. Update config files - -TIP: to see what changed in gitlab.yml.example in this release use next command: - -``` -git diff 6-0-stable:config/gitlab.yml.example 7-2-stable:config/gitlab.yml.example -``` - -* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-2-stable/config/gitlab.yml.example but with your settings. -* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-2-stable/config/unicorn.rb.example but with your settings. -* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v1.9.7/config.yml.example but with your settings. -* Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-2-stable/lib/support/nginx/gitlab but with your settings. -* Copy rack attack middleware config - -```bash -sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb -``` - -* Set up logrotate - -```bash -sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab -``` - -## 8. Update Init script - -```bash -sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab -``` - -## 9. Start application - - sudo service gitlab start - sudo service nginx restart - -## 10. Check application status - -Check if GitLab and its environment are configured correctly: - - cd /home/git/gitlab - sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production - -To make sure you didn't miss anything run a more thorough check with: - - sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production - -If all items are green, then congratulations upgrade complete! - -## 11. Update OmniAuth configuration - -When using Google omniauth login, changes of the Google account required. -Ensure that `Contacts API` and the `Google+ API` are enabled in the [Google Developers Console](https://console.developers.google.com/). -More details can be found at the [integration documentation](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/integration/google.md). - -## Things went south? Revert to previous version (6.0) - -### 1. Revert the code to the previous version - -Follow the [upgrade guide from 5.4 to 6.0](5.4-to-6.0.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 -``` - -## Login issues after upgrade? - -If running in HTTPS mode, be sure to read [Can't Verify CSRF token authenticity](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide#cant-verify-csrf-token-authenticitycant-get-past-login-pageredirected-to-login-page) diff --git a/doc/update/6.1-to-6.2.md b/doc/update/6.1-to-6.2.md index f189899f57..a534528108 100644 --- a/doc/update/6.1-to-6.2.md +++ b/doc/update/6.1-to-6.2.md @@ -1,4 +1,5 @@ # From 6.1 to 6.2 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.1-to-6.2.md) for the most up to date instructions.* **You should update to 6.1 before installing 6.2 so all the necessary conversions are run.** @@ -35,7 +36,7 @@ sudo -u git -H git checkout v1.7.9 # Addresses multiple critical security vulner ## 4. Install additional packages ```bash -# Add support for lograte for better log file handling +# Add support for logrotate for better log file handling sudo apt-get install logrotate ``` @@ -88,7 +89,6 @@ sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab ```bash sudo rm /etc/init.d/gitlab sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab -sudo chmod +x /etc/init.d/gitlab ``` ## 8. Start application diff --git a/doc/update/6.2-to-6.3.md b/doc/update/6.2-to-6.3.md index aa6ef56990..b08ebde080 100644 --- a/doc/update/6.2-to-6.3.md +++ b/doc/update/6.2-to-6.3.md @@ -1,4 +1,5 @@ # From 6.2 to 6.3 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.2-to-6.3.md) for the most up to date instructions.* **Requires version: 6.1 or 6.2.** @@ -74,7 +75,6 @@ sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers ```bash sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab -sudo chmod +x /etc/init.d/gitlab ``` ## 7. Start application diff --git a/doc/update/6.3-to-6.4.md b/doc/update/6.3-to-6.4.md index 96c2895981..951d92dfeb 100644 --- a/doc/update/6.3-to-6.4.md +++ b/doc/update/6.3-to-6.4.md @@ -1,4 +1,5 @@ # From 6.3 to 6.4 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.3-to-6.4.md) for the most up to date instructions.* ## 0. Backup diff --git a/doc/update/6.4-to-6.5.md b/doc/update/6.4-to-6.5.md index 1624296fc3..0dae9a9fe5 100644 --- a/doc/update/6.4-to-6.5.md +++ b/doc/update/6.4-to-6.5.md @@ -1,4 +1,5 @@ # From 6.4 to 6.5 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.4-to-6.5.md) for the most up to date instructions.* ## 0. Backup diff --git a/doc/update/6.5-to-6.6.md b/doc/update/6.5-to-6.6.md index 544eee17fe..c24e83eb00 100644 --- a/doc/update/6.5-to-6.6.md +++ b/doc/update/6.5-to-6.6.md @@ -1,4 +1,5 @@ # From 6.5 to 6.6 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.5-to-6.6.md) for the most up to date instructions.* ## 0. Backup diff --git a/doc/update/6.6-to-6.7.md b/doc/update/6.6-to-6.7.md index 77ac4d0bfa..b4298c9342 100644 --- a/doc/update/6.6-to-6.7.md +++ b/doc/update/6.6-to-6.7.md @@ -1,4 +1,5 @@ # From 6.6 to 6.7 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.6-to-6.7.md) for the most up to date instructions.* ## 0. Backup @@ -70,6 +71,9 @@ sudo -u git -H gzip /home/git/gitlab-shell/gitlab-shell.log.1 # Close access to gitlab-satellites for others sudo chmod u+rwx,g=rx,o-rwx /home/git/gitlab-satellites + +# Add directory for uploads +sudo -u git -H mkdir -p /home/git/gitlab/public/uploads ``` ## 5. Start application diff --git a/doc/update/6.7-to-6.8.md b/doc/update/6.7-to-6.8.md index b5b47f8930..4fb90639f1 100644 --- a/doc/update/6.7-to-6.8.md +++ b/doc/update/6.7-to-6.8.md @@ -1,4 +1,5 @@ # From 6.7 to 6.8 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.7-to-6.8.md) for the most up to date instructions.* ## 0. Backup @@ -62,7 +63,6 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS # Update init.d script sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab -sudo chmod +x /etc/init.d/gitlab # Close access to gitlab-satellites for others sudo chmod u+rwx,g=rx,o-rwx /home/git/gitlab-satellites diff --git a/doc/update/6.8-to-6.9.md b/doc/update/6.8-to-6.9.md index 9efb384ff5..b9b8b63f65 100644 --- a/doc/update/6.8-to-6.9.md +++ b/doc/update/6.8-to-6.9.md @@ -1,4 +1,5 @@ # From 6.8 to 6.9 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.8-to-6.9.md) for the most up to date instructions.* ### 0. Backup diff --git a/doc/update/6.9-to-7.0.md b/doc/update/6.9-to-7.0.md index f1d3d9c7b2..236430b595 100644 --- a/doc/update/6.9-to-7.0.md +++ b/doc/update/6.9-to-7.0.md @@ -1,4 +1,5 @@ # From 6.9 to 7.0 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.9-to-7.0.md) for the most up to date instructions.* ### 0. Backup @@ -93,7 +94,6 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS # Update init.d script sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab -sudo chmod +x /etc/init.d/gitlab ``` ### 6. Update config files @@ -106,6 +106,9 @@ There are new configuration options available for gitlab.yml. View them with the git diff origin/6-9-stable:config/gitlab.yml.example origin/7-0-stable:config/gitlab.yml.example ``` +* HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-0-stable/lib/support/nginx/gitlab but with your settings. +* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-0-stable/lib/support/nginx/gitlab-ssl but with your setting + ### 7. Start application sudo service gitlab start diff --git a/doc/update/6.x-or-7.x-to-7.10.md b/doc/update/6.x-or-7.x-to-7.10.md new file mode 100644 index 0000000000..39e12f32d0 --- /dev/null +++ b/doc/update/6.x-or-7.x-to-7.10.md @@ -0,0 +1,298 @@ +# From 6.x or 7.x to 7.10 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.x-or-7.x-to-7.10.md) for the most up to date instructions.* + +This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.10. + +## Global issue numbers + +As of 6.1 issue numbers are project specific. This means all issues are renumbered and get a new number in their URL. If you use an old issue number URL and the issue number does not exist yet you are redirected to the new one. This conversion does not trigger if the old number already exists for this project, this is unlikely but will happen with old issues and large projects. + +## Editable labels + +In GitLab 7.2 we replace Issue and Merge Request tags with labels, making it +possible to edit the label text and color. The characters `?`, `&` and `,` are +no longer allowed however so those will be removed from your tags during the +database migrations for GitLab 7.2. + +## 0. Stop server + + sudo service gitlab stop + +## 1. Backup + +It's useful to make a backup just in case things go south: +(With MySQL, this may require granting "LOCK TABLES" privileges to the GitLab user on the database version) + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +## 2. Update Ruby + +If you are still using Ruby 1.9.3 or below, you will need to update Ruby. +You can check which version you are running with `ruby -v`. + +If you are you running Ruby 2.0.x, you do not need to upgrade ruby, but can consider doing so for performance reasons. + +If you are running Ruby 2.1.1 consider upgrading to 2.1.6, because of the high memory usage of Ruby 2.1.1. + +Install, update dependencies: + +```bash +sudo apt-get install build-essential zlib1g-dev libyaml-dev libssl-dev libgdbm-dev libreadline-dev libncurses5-dev libffi-dev curl +``` + +Download and compile Ruby: + +```bash +mkdir /tmp/ruby && cd /tmp/ruby +curl --progress http://cache.ruby-lang.org/pub/ruby/2.1/ruby-2.1.6.tar.gz | tar xz +cd ruby-2.1.6 +./configure --disable-install-rdoc +make +sudo make install +``` + +Install Bundler: + +```bash +sudo gem install bundler --no-ri --no-rdoc +``` + +## 3. Get latest code + +```bash +cd /home/git/gitlab +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 7-10-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +sudo -u git -H git checkout 7-10-stable-ee +``` + +## 4. Install additional packages + +```bash +# Add support for logrotate for better log file handling +sudo apt-get install logrotate + +# Install pkg-config and cmake, which is needed for the latest versions of rugged +sudo apt-get install pkg-config cmake + +# Install Kerberos header files, which are needed for GitLab EE Kerberos support +sudo apt-get install libkrb5-dev + +# Install nodejs, javascript runtime required for assets +sudo apt-get install nodejs +``` + +## 5. Configure Redis to use sockets + + # Configure redis to use sockets + sudo cp /etc/redis/redis.conf /etc/redis/redis.conf.orig + # Disable Redis listening on TCP by setting 'port' to 0 + sed 's/^port .*/port 0/' /etc/redis/redis.conf.orig | sudo tee /etc/redis/redis.conf + # Enable Redis socket for default Debian / Ubuntu path + echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf + # Be sure redis group can write to the socket, enable only if supported (>= redis 2.4.0). + sudo sed -i '/# unixsocketperm/ s/^# unixsocketperm.*/unixsocketperm 0775/' /etc/redis/redis.conf + # Activate the changes to redis.conf + sudo service redis-server restart + # Add git to the redis group + sudo usermod -aG redis git + + # Configure Redis connection settings + sudo -u git -H cp config/resque.yml.example config/resque.yml + # Change the Redis socket path if you are not using the default Debian / Ubuntu configuration + sudo -u git -H editor config/resque.yml + + # Configure gitlab-shell to use Redis sockets + sudo -u git -H sed -i 's|^ # socket.*| socket: /var/run/redis/redis.sock|' /home/git/gitlab-shell/config.yml + +## 6. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch +sudo -u git -H git checkout v2.6.2 +``` + +## 7. 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 development test postgres --deployment + +# PostgreSQL installations (note: the line below states '--without ... mysql') +sudo -u git -H bundle install --without development test mysql --deployment + +# Run database migrations +sudo -u git -H bundle exec rake db:migrate RAILS_ENV=production + +# Enable internal issue IDs (introduced in GitLab 6.1) +sudo -u git -H bundle exec rake migrate_iids RAILS_ENV=production + +# Clean up assets and cache +sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS_ENV=production + +# Close access to gitlab-satellites for others +sudo chmod u+rwx,g+rx,o-rwx /home/git/gitlab-satellites + +# Update init.d script +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +``` + +## 8. Update config files + +TIP: to see what changed in `gitlab.yml.example` in this release use next command: + +``` +git diff 6-0-stable:config/gitlab.yml.example 7-10-stable:config/gitlab.yml.example +``` + +* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-10-stable/config/gitlab.yml.example but with your settings. +* Make `/home/git/gitlab/config/unicorn.rb` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-10-stable/config/unicorn.rb.example but with your settings. +* Make `/home/git/gitlab-shell/config.yml` the same as https://gitlab.com/gitlab-org/gitlab-shell/blob/v2.6.0/config.yml.example but with your settings. +* Copy rack attack middleware config + +```bash +sudo -u git -H cp config/initializers/rack_attack.rb.example config/initializers/rack_attack.rb +``` + +* Set up logrotate + +```bash +sudo cp lib/support/logrotate/gitlab /etc/logrotate.d/gitlab +``` + +### Change Nginx settings + +* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-10-stable/lib/support/nginx/gitlab but with your settings. +* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-10-stable/lib/support/nginx/gitlab-ssl but with your settings. +* A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section. + +## 9. Start application + + sudo service gitlab start + sudo service nginx restart + +## 10. Check application status + +Check if GitLab and its environment are configured correctly: + + cd /home/git/gitlab + sudo -u git -H bundle exec rake gitlab:env:info RAILS_ENV=production + +To make sure you didn't miss anything run a more thorough check with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade complete! + +## 11. Update OmniAuth configuration + +When using Google omniauth login, changes of the Google account required. +Ensure that `Contacts API` and the `Google+ API` are enabled in the [Google Developers Console](https://console.developers.google.com/). +More details can be found at the [integration documentation](../../../master/doc/integration/google.md). + +## 12. Optional optimizations for GitLab setups with MySQL databases + +Only applies if running MySQL database created with GitLab 6.7 or earlier. If you are not experiencing any issues you may not need the following instructions however following them will bring your database in line with the latest recommended installation configuration and help avoid future issues. Be sure to follow these directions exactly. These directions should be safe for any MySQL instance but to be sure make a current MySQL database backup beforehand. + +``` +# Stop GitLab +sudo service gitlab stop + +# Secure your MySQL installation (added in GitLab 6.2) +sudo mysql_secure_installation + +# Login to MySQL +mysql -u root -p + +# do not type the 'mysql>', this is part of the prompt + +# Convert all tables to use the InnoDB storage engine (added in GitLab 6.8) +SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' ENGINE=InnoDB;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `ENGINE` <> 'InnoDB' AND `TABLE_TYPE` = 'BASE TABLE'; + +# If previous query returned results, copy & run all shown SQL statements + +# Convert all tables to correct character set +SET foreign_key_checks = 0; +SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `TABLE_COLLATION` <> 'utf8_unicode_ci' AND `TABLE_TYPE` = 'BASE TABLE'; + +# If previous query returned results, copy & run all shown SQL statements + +# turn foreign key checks back on +SET foreign_key_checks = 1; + +# Find MySQL users +mysql> SELECT user FROM mysql.user WHERE user LIKE '%git%'; + +# If git user exists and gitlab user does not exist +# you are done with the database cleanup tasks +mysql> \q + +# If both users exist skip to Delete gitlab user + +# Create new user for GitLab (changed in GitLab 6.4) +# change $password in the command below to a real password you pick +mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password'; + +# Grant the git user necessary permissions on the database +mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES ON `gitlabhq_production`.* TO 'git'@'localhost'; + +# Delete the old gitlab user +mysql> DELETE FROM mysql.user WHERE user='gitlab'; + +# Quit the database session +mysql> \q + +# Try connecting to the new database with the new user +sudo -u git -H mysql -u git -p -D gitlabhq_production + +# Type the password you replaced $password with earlier + +# You should now see a 'mysql>' prompt + +# Quit the database session +mysql> \q + +# Update database configuration details +# See config/database.yml.mysql for latest recommended configuration details +# Remove the reaping_frequency setting line if it exists (removed in GitLab 6.8) +# Set production -> pool: 10 (updated in GitLab 5.3) +# Set production -> username: git +# Set production -> password: the password your replaced $password with earlier +sudo -u git -H editor /home/git/gitlab/config/database.yml +``` + +## Things went south? Revert to previous version (7.0) + +### 1. Revert the code to the previous version + +Follow the [upgrade guide from 6.9 to 7.0](6.9-to-7.0.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 +``` + +## Login issues after upgrade? + +If running in HTTPS mode, be sure to read [Can't Verify CSRF token authenticity](https://github.com/gitlabhq/gitlab-public-wiki/wiki/Trouble-Shooting-Guide#cant-verify-csrf-token-authenticitycant-get-past-login-pageredirected-to-login-page) diff --git a/doc/update/7.0-to-7.1.md b/doc/update/7.0-to-7.1.md index 166ff0ea13..a4e9be9946 100644 --- a/doc/update/7.0-to-7.1.md +++ b/doc/update/7.0-to-7.1.md @@ -1,4 +1,5 @@ # From 7.0 to 7.1 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/7.0-to-7.1.md) for the most up to date instructions.* ### 0. Backup @@ -93,7 +94,6 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS # Update init.d script sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab -sudo chmod +x /etc/init.d/gitlab ``` ### 6. Update config files diff --git a/doc/update/7.1-to-7.2.md b/doc/update/7.1-to-7.2.md index 04b9ce76a1..88cb63d7d4 100644 --- a/doc/update/7.1-to-7.2.md +++ b/doc/update/7.1-to-7.2.md @@ -1,9 +1,10 @@ # From 7.1 to 7.2 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/7.1-to-7.2.md) for the most up to date instructions.* ## Editable labels In GitLab 7.2 we replace Issue and Merge Request tags with labels, making it -possible to edit the label text and color. The characters '?', '&' and ',' are +possible to edit the label text and color. The characters `?`, `&` and `,` are no longer allowed however so those will be removed from your tags during the database migrations for GitLab 7.2. @@ -46,7 +47,7 @@ sudo -u git -H git checkout 7-2-stable-ee ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch -sudo -u git -H git checkout v1.9.7 +sudo -u git -H git checkout v1.9.8 ``` ### 4. Install new system dependencies @@ -77,19 +78,21 @@ sudo -u git -H bundle exec rake assets:clean assets:precompile cache:clear RAILS # Update init.d script sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab -sudo chmod +x /etc/init.d/gitlab ``` ### 6. Update config files -#### New configuration options for gitlab.yml +#### New configuration options for `gitlab.yml` -There are new configuration options available for gitlab.yml. View them with the command below and apply them to your current gitlab.yml. +There are new configuration options available for `gitlab.yml`. View them with the command below and apply them to your current `gitlab.yml`. ``` git diff 7-1-stable:config/gitlab.yml.example 7-2-stable:config/gitlab.yml.example ``` +* HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-0-stable/lib/support/nginx/gitlab but with your settings. +* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-0-stable/lib/support/nginx/gitlab-ssl but with your setting + Update rack attack middleware config ``` diff --git a/doc/update/7.2-to-7.3.md b/doc/update/7.2-to-7.3.md new file mode 100644 index 0000000000..18f77d6396 --- /dev/null +++ b/doc/update/7.2-to-7.3.md @@ -0,0 +1,145 @@ +# From 7.2 to 7.3 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/7.2-to-7.3.md) for the most up to date instructions.* + +### 0. Backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 1. Stop server + +```bash +sudo service gitlab stop +``` + +### 2. Get latest code + +```bash +cd /home/git/gitlab +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 7-3-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +sudo -u git -H git checkout 7-3-stable-ee +``` + +### 3. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch +sudo -u git -H git checkout v2.0.1 +``` + +### 4. 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 development test postgres --deployment + +# PostgreSQL installations (note: the line below states '--without ... mysql') +sudo -u git -H bundle install --without development test mysql --deployment + +# 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 + +# Update init.d script +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +``` + + +### 5. Configure Redis to use sockets + + # Configure redis to use sockets + sudo cp /etc/redis/redis.conf /etc/redis/redis.conf.orig + # Disable Redis listening on TCP by setting 'port' to 0 + sed 's/^port .*/port 0/' /etc/redis/redis.conf.orig | sudo tee /etc/redis/redis.conf + # Enable Redis socket for default Debian / Ubuntu path + echo 'unixsocket /var/run/redis/redis.sock' | sudo tee -a /etc/redis/redis.conf + # Be sure redis group can write to the socket, enable only if supported (>= redis 2.4.0). + sudo sed -i '/# unixsocketperm/ s/^# unixsocketperm.*/unixsocketperm 0775/' /etc/redis/redis.conf + # Activate the changes to redis.conf + sudo service redis-server restart + # Add git to the redis group + sudo usermod -aG redis git + + # Configure Redis connection settings + sudo -u git -H cp config/resque.yml.example config/resque.yml + # Change the Redis socket path if you are not using the default Debian / Ubuntu configuration + sudo -u git -H editor config/resque.yml + + # Configure gitlab-shell to use Redis sockets + sudo -u git -H sed -i 's|^ # socket.*| socket: /var/run/redis/redis.sock|' /home/git/gitlab-shell/config.yml + +### 6. Update config files + +#### New configuration options for gitlab.yml + +There are new configuration options available for gitlab.yml. View them with the command below and apply them to your current gitlab.yml. + +``` +git diff origin/7-2-stable:config/gitlab.yml.example origin/7-3-stable:config/gitlab.yml.example +``` + +``` +# Use the default Unicorn socket backlog value of 1024 +sudo -u git -H sed -i 's/:backlog => 64/:backlog => 1024/' config/unicorn.rb +``` + +* HTTP setups: Make `/etc/nginx/sites-available/nginx` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab but with your settings. +* HTTPS setups: Make `/etc/nginx/sites-available/nginx-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-3-stable/lib/support/nginx/gitlab-ssl but with your setting + +### 7. Start application + + sudo service gitlab start + sudo service nginx restart + +### 8. 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 with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade is complete! + +### 9. Update OmniAuth configuration + +When using Google omniauth login, changes of the Google account required. +Ensure that `Contacts API` and the `Google+ API` are enabled in the [Google Developers Console](https://console.developers.google.com/). +More details can be found at the [integration documentation](../integration/google.md). + +## Things went south? Revert to previous version (7.2) + +### 1. Revert the code to the previous version +Follow the [upgrade guide from 7.1 to 7.2](7.1-to-7.2.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/7.3-to-7.4.md b/doc/update/7.3-to-7.4.md new file mode 100644 index 0000000000..53e739c06f --- /dev/null +++ b/doc/update/7.3-to-7.4.md @@ -0,0 +1,197 @@ +# From 7.3 to 7.4 +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/7.3-to-7.4.md) for the most up to date instructions.* + +### 0. Stop server + + sudo service gitlab stop + +### 1. Backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 2. 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 7-4-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +sudo -u git -H git checkout 7-4-stable-ee +``` + +### 3. Install libs, migrations, etc. + +```bash +# MySQL installations (note: the line below states '--without ... postgres') +sudo -u git -H bundle install --without development test postgres --deployment + +# PostgreSQL installations (note: the line below states '--without ... mysql') +sudo -u git -H bundle install --without development test mysql --deployment + +# 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 + +# Update init.d script +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +``` + +### 4. Update config files + +#### New configuration options for gitlab.yml + +There are new configuration options available for gitlab.yml. View them with the command below and apply them to your current gitlab.yml. + +``` +git diff origin/7-3-stable:config/gitlab.yml.example origin/7-4-stable:config/gitlab.yml.example +``` + +#### Change timeout for unicorn + +``` +# set timeout to 60 +sudo -u git -H editor config/unicorn.rb +``` + +#### Change Nginx HTTPS settings + +* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-4-stable/lib/support/nginx/gitlab-ssl but with your setting + +#### MySQL Databases: Update database.yml config file + +* Add `collation: utf8_general_ci` to `config/database.yml` as seen in [config/database.yml.mysql](/config/database.yml.mysql) + +``` +sudo -u git -H editor config/database.yml +``` + +### 5. Start application + + sudo service gitlab start + sudo service nginx restart + +### 6. 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 with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade is complete! + + +### 7. Optional optimizations for GitLab setups with MySQL databases + +Only applies if running MySQL database created with GitLab 6.7 or earlier. If you are not experiencing any issues you may not need the following instructions however following them will bring your database in line with the latest recommended installation configuration and help avoid future issues. Be sure to follow these directions exactly. These directions should be safe for any MySQL instance but to be sure make a current MySQL database backup beforehand. + +``` +# Stop GitLab +sudo service gitlab stop + +# Secure your MySQL installation (added in GitLab 6.2) +sudo mysql_secure_installation + +# Login to MySQL +mysql -u root -p + +# do not type the 'mysql>', this is part of the prompt + +# Convert all tables to use the InnoDB storage engine (added in GitLab 6.8) +SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' ENGINE=InnoDB;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `ENGINE` <> 'InnoDB' AND `TABLE_TYPE` = 'BASE TABLE'; + +# If previous query returned results, copy & run all shown SQL statements + +# Convert all tables to correct character set +SET foreign_key_checks = 0; +SELECT CONCAT('ALTER TABLE gitlabhq_production.', table_name, ' CONVERT TO CHARACTER SET utf8 COLLATE utf8_general_ci;') AS 'Copy & run these SQL statements:' FROM information_schema.tables WHERE table_schema = 'gitlabhq_production' AND `TABLE_COLLATION` <> 'utf8_unicode_ci' AND `TABLE_TYPE` = 'BASE TABLE'; + +# If previous query returned results, copy & run all shown SQL statements + +# turn foreign key checks back on +SET foreign_key_checks = 1; + +# Find MySQL users +mysql> SELECT user FROM mysql.user WHERE user LIKE '%git%'; + +# If git user exists and gitlab user does not exist +# you are done with the database cleanup tasks +mysql> \q + +# If both users exist skip to Delete gitlab user + +# Create new user for GitLab (changed in GitLab 6.4) +# change $password in the command below to a real password you pick +mysql> CREATE USER 'git'@'localhost' IDENTIFIED BY '$password'; + +# Grant the git user necessary permissions on the database +mysql> GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, LOCK TABLES ON `gitlabhq_production`.* TO 'git'@'localhost'; + +# Delete the old gitlab user +mysql> DELETE FROM mysql.user WHERE user='gitlab'; + +# Quit the database session +mysql> \q + +# Try connecting to the new database with the new user +sudo -u git -H mysql -u git -p -D gitlabhq_production + +# Type the password you replaced $password with earlier + +# You should now see a 'mysql>' prompt + +# Quit the database session +mysql> \q + +# Update database configuration details +# See config/database.yml.mysql for latest recommended configuration details +# Remove the reaping_frequency setting line if it exists (removed in GitLab 6.8) +# Set production -> pool: 10 (updated in GitLab 5.3) +# Set production -> username: git +# Set production -> password: the password your replaced $password with earlier +sudo -u git -H editor /home/git/gitlab/config/database.yml + +# Start GitLab +sudo service gitlab start +sudo service nginx restart + +# Run thorough check +sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production +``` + + +## Things went south? Revert to previous version (7.3) + +### 1. Revert the code to the previous version +Follow the [upgrade guide from 7.2 to 7.3](7.2-to-7.3.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/7.4-to-7.5.md b/doc/update/7.4-to-7.5.md new file mode 100644 index 0000000000..673eab3c56 --- /dev/null +++ b/doc/update/7.4-to-7.5.md @@ -0,0 +1,108 @@ +# From 7.4 to 7.5 + +### 0. Stop server + + sudo service gitlab stop + +### 1. Backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 2. 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 7-5-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +sudo -u git -H git checkout 7-5-stable-ee +``` + +### 3. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch +sudo -u git -H git checkout v2.2.0 +``` + +### 4. 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 development test postgres --deployment + +# PostgreSQL installations (note: the line below states '--without ... mysql') +sudo -u git -H bundle install --without development test mysql --deployment + +# 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 + +# Update init.d script +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +``` + +### 5. Update config files + +#### New configuration options for gitlab.yml + +There are new configuration options available for gitlab.yml. View them with the command below and apply them to your current gitlab.yml. + +``` +git diff origin/7-4-stable:config/gitlab.yml.example origin/7-5-stable:config/gitlab.yml.example +``` + +#### Change Nginx settings + +* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings +* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your setting + +### 6. Start application + + sudo service gitlab start + sudo service nginx restart + +### 7. 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 with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade is complete! + +## Things went south? Revert to previous version (7.4) + +### 1. Revert the code to the previous version +Follow the [upgrade guide from 7.3 to 7.4](7.3-to-7.4.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/7.5-to-7.6.md b/doc/update/7.5-to-7.6.md new file mode 100644 index 0000000000..35cd437fdc --- /dev/null +++ b/doc/update/7.5-to-7.6.md @@ -0,0 +1,114 @@ +# From 7.5 to 7.6 + +### 0. Stop server + + sudo service gitlab stop + +### 1. Backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 2. 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 7-6-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +sudo -u git -H git checkout 7-6-stable-ee +``` + +### 3. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch +sudo -u git -H git checkout v2.4.0 +``` + +### 4. Install libs, migrations, etc. + +```bash +sudo apt-get install libkrb5-dev + +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without ... postgres') +sudo -u git -H bundle install --without development test postgres --deployment + +# PostgreSQL installations (note: the line below states '--without ... mysql') +sudo -u git -H bundle install --without development test mysql --deployment + +# 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 + +# Update init.d script +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +``` + +### 5. Update config 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 to your current `gitlab.yml`. + +``` +git diff origin/7-5-stable:config/gitlab.yml.example origin/7-6-stable:config/gitlab.yml.example +``` + +#### Change Nginx settings + +* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings +* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your setting + +#### Setup time zone (optional) + +Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`](config/application.rb) (unlikely), unset it. + +### 6. Start application + + sudo service gitlab start + sudo service nginx restart + +### 7. 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 with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade is complete! + +## Things went south? Revert to previous version (7.5) + +### 1. Revert the code to the previous version +Follow the [upgrade guide from 7.4 to 7.5](7.4-to-7.5.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/7.6-to-7.7.md b/doc/update/7.6-to-7.7.md new file mode 100644 index 0000000000..5924371315 --- /dev/null +++ b/doc/update/7.6-to-7.7.md @@ -0,0 +1,119 @@ +# From 7.6 to 7.7 + +### 0. Stop server + + sudo service gitlab stop + +### 1. Backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 2. 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 7-7-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +sudo -u git -H git checkout 7-7-stable-ee +``` + +### 3. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch +sudo -u git -H git checkout v2.4.2 +``` + +### 4. Install libs, migrations, etc. + +```bash +sudo apt-get install libkrb5-dev + +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without ... postgres') +sudo -u git -H bundle install --without development test postgres --deployment + +# PostgreSQL installations (note: the line below states '--without ... mysql') +sudo -u git -H bundle install --without development test mysql --deployment + +# 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 + +# Update init.d script +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +``` + +### 5. Update config 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 to your current `gitlab.yml`. + +``` +git diff origin/7-6-stable:config/gitlab.yml.example origin/7-7-stable:config/gitlab.yml.example +``` + +#### Change Nginx settings + +* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings +* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your setting + +#### Setup time zone (optional) + +Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`](config/application.rb) (unlikely), unset it. + +### 6. Start application + + sudo service gitlab start + sudo service nginx restart + +### 7. 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 with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade is complete! + +### 8. GitHub settings (if applicable) + +If you are using GitHub as an OAuth provider for authentication, you should change the callback URL so that it +only contains a root URL (ex. `https://gitlab.example.com/`) + +## Things went south? Revert to previous version (7.6) + +### 1. Revert the code to the previous version +Follow the [upgrade guide from 7.5 to 7.6](7.5-to-7.6.md), except for the database migration +(The backup is already migrated to the previous version) + +### 2. Restore from the backup: + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:restore RAILS_ENV=production +``` +If you have more than one backup *.tar file(s) please add `BACKUP=timestamp_of_backup` to the command above. diff --git a/doc/update/7.7-to-7.8.md b/doc/update/7.7-to-7.8.md new file mode 100644 index 0000000000..46ca163c1b --- /dev/null +++ b/doc/update/7.7-to-7.8.md @@ -0,0 +1,120 @@ +# From 7.7 to 7.8 + +### 0. Stop server + + sudo service gitlab stop + +### 1. Backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 2. 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 7-8-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +sudo -u git -H git checkout 7-8-stable-ee +``` + +### 3. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch +sudo -u git -H git checkout v2.5.4 +``` + +### 4. Install libs, migrations, etc. + +```bash +sudo apt-get install libkrb5-dev + +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without ... postgres') +sudo -u git -H bundle install --without development test postgres --deployment + +# PostgreSQL installations (note: the line below states '--without ... mysql') +sudo -u git -H bundle install --without development test mysql --deployment + +# 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 + +# Update init.d script +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +``` + +### 5. Update config 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 to your current `gitlab.yml`. + +``` +git diff origin/7-7-stable:config/gitlab.yml.example origin/7-8-stable:config/gitlab.yml.example +``` + +#### Change Nginx settings + +* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings. +* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your settings. +* A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section. + +#### Setup time zone (optional) + +Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`](config/application.rb) (unlikely), unset it. + +### 6. Start application + + sudo service gitlab start + sudo service nginx restart + +### 7. 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 with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade is complete! + +### 8. GitHub settings (if applicable) + +If you are using GitHub as an OAuth provider for authentication, you should change the callback URL so that it +only contains a root URL (ex. `https://gitlab.example.com/`) + +## Things went south? Revert to previous version (7.7) + +### 1. Revert the code to the previous version +Follow the [upgrade guide from 7.6 to 7.7](7.6-to-7.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/7.8-to-7.9.md b/doc/update/7.8-to-7.9.md new file mode 100644 index 0000000000..28fd433e1c --- /dev/null +++ b/doc/update/7.8-to-7.9.md @@ -0,0 +1,120 @@ +# From 7.8 to 7.9 + +### 0. Stop server + + sudo service gitlab stop + +### 1. Backup + +```bash +cd /home/git/gitlab +sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production +``` + +### 2. 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 7-9-stable +``` + +OR + +For GitLab Enterprise Edition: + +```bash +sudo -u git -H git checkout 7-9-stable-ee +``` + +### 3. Update gitlab-shell + +```bash +cd /home/git/gitlab-shell +sudo -u git -H git fetch +sudo -u git -H git checkout v2.6.0 +``` + +### 4. Install libs, migrations, etc. + +```bash +sudo apt-get install nodejs + +cd /home/git/gitlab + +# MySQL installations (note: the line below states '--without ... postgres') +sudo -u git -H bundle install --without development test postgres --deployment + +# PostgreSQL installations (note: the line below states '--without ... mysql') +sudo -u git -H bundle install --without development test mysql --deployment + +# 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 + +# Update init.d script +sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab +``` + +### 5. Update config 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 to your current `gitlab.yml`. + +``` +git diff origin/7-8-stable:config/gitlab.yml.example origin/7-9-stable:config/gitlab.yml.example +``` + +#### Change Nginx settings + +* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as [`lib/support/nginx/gitlab`](/lib/support/nginx/gitlab) but with your settings. +* HTTPS setups: Make `/etc/nginx/sites-available/gitlab-ssl` the same as [`lib/support/nginx/gitlab-ssl`](/lib/support/nginx/gitlab-ssl) but with your settings. +* A new `location /uploads/` section has been added that needs to have the same content as the existing `location @gitlab` section. + +#### Setup time zone (optional) + +Consider setting the time zone in `gitlab.yml` otherwise GitLab will default to UTC. If you set a time zone previously in [`application.rb`](config/application.rb) (unlikely), unset it. + +### 6. Start application + + sudo service gitlab start + sudo service nginx restart + +### 7. 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 with: + + sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production + +If all items are green, then congratulations upgrade is complete! + +### 8. GitHub settings (if applicable) + +If you are using GitHub as an OAuth provider for authentication, you should change the callback URL so that it +only contains a root URL (ex. `https://gitlab.example.com/`) + +## Things went south? Revert to previous version (7.8) + +### 1. Revert the code to the previous version +Follow the [upgrade guide from 7.7 to 7.8](7.7-to-7.8.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 9a6f09b370..0472537eeb 100644 --- a/doc/update/README.md +++ b/doc/update/README.md @@ -1,4 +1,16 @@ -- [The individual upgrade guides](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update) -- [Upgrader](upgrader.md) -- [Patch versions](patch_versions.md) -- [MySQL to PostgreSQL](mysql_to_postgresql.md) +Depending on the installation method and your GitLab version, there are multiple update guides. Choose one that fits your needs. + +## Omnibus Packages + +- [Omnibus update guide](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/update.md) contains the steps needed to update a GitLab [package](https://about.gitlab.com/downloads/). + +## Installation from source + +- [The individual upgrade guides](https://gitlab.com/gitlab-org/gitlab-ce/tree/master/doc/update) are for those who have installed GitLab from source. +- [The CE to EE update guides](https://gitlab.com/subscribers/gitlab-ee/tree/master/doc/update) are for subscribers of the Enterprise Edition only. The steps are very similar to a version upgrade: stop the server, get the code, update config files for the new functionality, install libs and do migrations, update the init script, start the application and check the application status. +- [Upgrader](upgrader.md) is an automatic ruby script that performs the update for installations from source. +- [Patch versions](patch_versions.md) guide includes the steps needed for a patch version, eg. 6.2.0 to 6.2.1. + +## Miscellaneous + +- [MySQL to PostgreSQL](mysql_to_postgresql.md) guides you through migrating your database from MySQL to PostgreSQL. diff --git a/doc/update/mysql_to_postgresql.md b/doc/update/mysql_to_postgresql.md index 219a3bb635..50941db25f 100644 --- a/doc/update/mysql_to_postgresql.md +++ b/doc/update/mysql_to_postgresql.md @@ -1,4 +1,5 @@ # Migrating GitLab from MySQL to Postgres +*Make sure you view this [guide from the `master` branch](../../../master/doc/update/mysql_to_postgresql.md) for the most up to date instructions.* If you are replacing MySQL with Postgres while keeping GitLab on the same server all you need to do is to export from MySQL, import into Postgres and rebuild the indexes as described below. If you are also moving GitLab to another server, or if you are switching to omnibus-gitlab, you may want to use a GitLab backup file. The second part of this documents explains the procedure to do this. @@ -11,14 +12,19 @@ sudo service gitlab stop # Update /home/git/gitlab/config/database.yml -git clone https://github.com/gitlabhq/mysql-postgresql-converter.git +git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab cd mysql-postgresql-converter -mysqldump --compatible=postgresql --default-character-set=utf8 -r databasename.mysql -u root gitlabhq_production +mysqldump --compatible=postgresql --default-character-set=utf8 -r databasename.mysql -u root gitlabhq_production -p python db_converter.py databasename.mysql databasename.psql -psql -f databasename.psql -d gitlabhq_production + +# Import the database dump as the application database user +sudo -u git psql -f databasename.psql -d gitlabhq_production # Rebuild indexes (see below) +# Install gems for PostgreSQL (note: the line below states '--without ... mysql') +sudo -u git -H bundle install --without development test mysql --deployment + sudo service gitlab start ``` @@ -33,7 +39,7 @@ On non-omnibus installations (distributed using Git) we retrieve the index decla ``` # Clone the database converter on your Postgres-backed GitLab server cd /tmp -git clone https://github.com/gitlabhq/mysql-postgresql-converter.git +git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab cd /home/git/gitlab @@ -54,7 +60,7 @@ On omnibus-gitlab we need to get the index declarations from a file called `sche ``` # Clone the database converter on your Postgres-backed GitLab server cd /tmp -/opt/gitlab/embedded/bin/git clone https://github.com/gitlabhq/mysql-postgresql-converter.git +/opt/gitlab/embedded/bin/git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab cd /tmp/mysql-postgresql-converter # Download schema.rb.bundled if necessary @@ -70,7 +76,7 @@ test -e /opt/gitlab/embedded/service/gitlab-rails/db/schema.rb.bundled || sudo / ## Converting a GitLab backup file from MySQL to Postgres **Note:** Please make sure to have Python 2.7.x (or higher) installed. -GitLab backup files (_gitlab_backup.tar) contain a SQL dump. Using the lanyrd database converter we can replace a MySQL database dump inside the tar file with a Postgres database dump. This can be useful if you are moving to another server. +GitLab backup files (`_gitlab_backup.tar`) contain a SQL dump. Using the lanyrd database converter we can replace a MySQL database dump inside the tar file with a Postgres database dump. This can be useful if you are moving to another server. ``` # Stop GitLab @@ -89,10 +95,10 @@ sudo -u git -H mv tmp/backups/TIMESTAMP_gitlab_backup.tar tmp/backups/postgresql # Create a separate database dump with PostgreSQL compatibility cd tmp/backups/postgresql -sudo -u git -H mysqldump --compatible=postgresql --default-character-set=utf8 -r gitlabhq_production.mysql -u root gitlabhq_production +sudo -u git -H mysqldump --compatible=postgresql --default-character-set=utf8 -r gitlabhq_production.mysql -u root gitlabhq_production -p # Clone the database converter -sudo -u git -H git clone https://github.com/gitlabhq/mysql-postgresql-converter.git +sudo -u git -H git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab # Convert gitlabhq_production.mysql sudo -u git -H mkdir db diff --git a/doc/update/patch_versions.md b/doc/update/patch_versions.md index c4a77d1280..e29ee2a7b3 100644 --- a/doc/update/patch_versions.md +++ b/doc/update/patch_versions.md @@ -1,4 +1,5 @@ # Universal update guide for patch versions +*Make sure you view this [upgrade guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/update/patch_versions.md) from the `master` branch for the most up to date instructions.* For example from 6.2.0 to 6.2.1, also see the [semantic versioning specification](http://semver.org/). @@ -26,16 +27,14 @@ sudo -u git -H git checkout LATEST_TAG Replace LATEST_TAG with the latest GitLab tag you want to upgrade to, for example `v6.6.3`. -### 3. Update gitlab-shell if it is not the latest version +### 3. Update gitlab-shell to the corresponding version ```bash cd /home/git/gitlab-shell sudo -u git -H git fetch -sudo -u git -H git checkout LATEST_TAG +sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION` ``` -Replace LATEST_TAG with the latest GitLab Shell tag you want to upgrade to, for example `v1.7.9`. - ### 4. Install libs, migrations, etc. ```bash diff --git a/doc/update/upgrader.md b/doc/update/upgrader.md index e8379fcc31..f62a53d334 100644 --- a/doc/update/upgrader.md +++ b/doc/update/upgrader.md @@ -1,4 +1,5 @@ # GitLab Upgrader +*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/upgrader.md) for the most up to date instructions.* GitLab Upgrader - a ruby script that allows you easily upgrade GitLab to latest minor version. @@ -10,6 +11,8 @@ If you have local changes to your GitLab repository the script will stash them a **GitLab Upgrader is available only for GitLab version 6.4.2 or higher.** +**This script does NOT update gitlab-shell, it needs manual update. See step 5 below.** + ## 0. Backup cd /home/git/gitlab @@ -21,7 +24,7 @@ If you have local changes to your GitLab repository the script will stash them a ## 2. Run GitLab upgrade tool -Note: GitLab 7.2 adds `pkg-config` and `cmake` as dependency. Please check the dependencies in the [installation guide.](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies) +Note: GitLab 7.9 adds `nodejs` as a dependency. GitLab 7.6 adds `libkrb5-dev` as a dependency (installed by default on Ubuntu and OSX). GitLab 7.2 adds `pkg-config` and `cmake` as dependency. Please check the dependencies in the [installation guide.](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies) # Starting with GitLab version 7.0 upgrader script has been moved to bin directory cd /home/git/gitlab @@ -43,28 +46,31 @@ Check if GitLab and its dependencies are configured correctly: If all items are green, then congratulations upgrade is complete! -## 5. Upgrade GitLab Shell (if needed) +## 5. Upgrade GitLab Shell -If the `gitlab:check` task reports an outdated version of `gitlab-shell` you should upgrade it. - -Upgrade it by running the commands below after replacing 1.9.4 with the correct version number: +GitLab Shell might be outdated, running the commands below ensures you're using a compatible version: ``` cd /home/git/gitlab-shell sudo -u git -H git fetch -sudo -u git -H git checkout v1.9.4 +sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION` ``` ## One line upgrade command You've read through the entire guide and probably already did all the steps one by one. -Here is a one line command with step 1 to 4 for the next time you upgrade: +Here is a one line command with step 1 to 5 for the next time you upgrade: ```bash -cd /home/git/gitlab; sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production; \ +cd /home/git/gitlab; \ + sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production; \ sudo service gitlab stop; \ if [ -f bin/upgrade.rb ]; then sudo -u git -H ruby bin/upgrade.rb -y; else sudo -u git -H ruby script/upgrade.rb -y; fi; \ + cd /home/git/gitlab-shell; \ + sudo -u git -H git fetch; \ + sudo -u git -H git checkout v`cat /home/git/gitlab/GITLAB_SHELL_VERSION`; \ + cd /home/git/gitlab; \ sudo service gitlab start; \ sudo service nginx restart; sudo -u git -H bundle exec rake gitlab:check RAILS_ENV=production ``` diff --git a/doc/web_hooks/web_hooks.md b/doc/web_hooks/web_hooks.md index 13c4de4301..851f50f5e9 100644 --- a/doc/web_hooks/web_hooks.md +++ b/doc/web_hooks/web_hooks.md @@ -16,24 +16,29 @@ Triggered when you push to the repository except when pushing tags. ```json { + "object_kind": "push", "before": "95790bf891e76fee5e1747ab589903a6a1f80f22", "after": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", "ref": "refs/heads/master", "user_id": 4, "user_name": "John Smith", + "user_email": "john@example.com", "project_id": 15, "repository": { "name": "Diaspora", - "url": "git@example.com:diaspora.git", + "url": "git@example.com:mike/diasporadiaspora.git", "description": "", - "homepage": "http://example.com/diaspora" + "homepage": "http://example.com/mike/diaspora", + "git_http_url":"http://example.com/mike/diaspora.git", + "git_ssh_url":"git@example.com:mike/diaspora.git", + "visibility_level":0 }, "commits": [ { "id": "b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", "message": "Update Catalan translation to e38cb41.", "timestamp": "2011-12-12T14:27:31+02:00", - "url": "http://example.com/diaspora/commits/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", + "url": "http://example.com/mike/diaspora/commit/b6568db1bc1dcd7f8b4d5a946b0b91f9dacd7327", "author": { "name": "Jordi Mallach", "email": "jordi@softcatala.org" @@ -43,7 +48,7 @@ Triggered when you push to the repository except when pushing tags. "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", "message": "fixed readme", "timestamp": "2012-01-03T23:36:29+02:00", - "url": "http://example.com/diaspora/commits/da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "url": "http://example.com/mike/diaspora/commit/da1560886d4f094c3e6c9ef40349f7d38b5d27d7", "author": { "name": "GitLab dev user", "email": "gitlabdev@dv6700.(none)" @@ -54,6 +59,35 @@ Triggered when you push to the repository except when pushing tags. } ``` +## Tag events + +Triggered when you create (or delete) tags to the repository. + +**Request body:** + +```json +{ + "object_kind": "tag_push", + "ref": "refs/tags/v1.0.0", + "before": "0000000000000000000000000000000000000000", + "after": "82b3d5ae55f7080f1e6022629cdb57bfae7cccc7", + "user_id": 1, + "user_name": "John Smith", + "project_id": 1, + "repository": { + "name": "jsmith", + "url": "ssh://git@example.com/jsmith/example.git", + "description": "", + "homepage": "http://example.com/jsmith/example", + "git_http_url":"http://example.com/jsmith/example.git", + "git_ssh_url":"git@example.com:jsmith/example.git", + "visibility_level":0 + }, + "commits": [], + "total_commits_count": 0 +} +``` + ## Issues events Triggered when a new issue is created or an existing issue was updated/closed/reopened. @@ -63,6 +97,11 @@ Triggered when a new issue is created or an existing issue was updated/closed/re ```json { "object_kind": "issue", + "user": { + "name": "Administrator", + "username": "root", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" + }, "object_attributes": { "id": 301, "title": "New API: create/update/delete file", @@ -92,6 +131,11 @@ Triggered when a new merge request is created or an existing merge request was u ```json { "object_kind": "merge_request", + "user": { + "name": "Administrator", + "username": "root", + "avatar_url": "http://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=40\u0026d=identicon" + }, "object_attributes": { "id": 99, "target_branch": "master", @@ -109,7 +153,33 @@ Triggered when a new merge request is created or an existing merge request was u "merge_status": "unchecked", "target_project_id": 14, "iid": 1, - "description": "" + "description": "", + "source": { + "name": "awesome_project", + "ssh_url": "ssh://git@example.com/awesome_space/awesome_project.git", + "http_url": "http://example.com/awesome_space/awesome_project.git", + "visibility_level": 20, + "namespace": "awesome_space" + }, + "target": { + "name": "awesome_project", + "ssh_url": "ssh://git@example.com/awesome_space/awesome_project.git", + "http_url": "http://example.com/awesome_space/awesome_project.git", + "visibility_level": 20, + "namespace": "awesome_space" + }, + "last_commit": { + "id": "da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "message": "fixed readme", + "timestamp": "2012-01-03T23:36:29+02:00", + "url": "http://example.com/awesome_space/awesome_project/commits/da1560886d4f094c3e6c9ef40349f7d38b5d27d7", + "author": { + "name": "GitLab dev user", + "email": "gitlabdev@dv6700.(none)" + } + }, + "url": "http://example.com/diaspora/merge_requests/1", + "action": "open" } } ``` @@ -124,12 +194,14 @@ Save the following file as `print_http_body.rb`. ```ruby require 'webrick' -server = WEBrick::HTTPServer.new(Port: ARGV.first) +server = WEBrick::HTTPServer.new(:Port => ARGV.first) server.mount_proc '/' do |req, res| puts req.body end -trap 'INT' do server.shutdown end +trap 'INT' do + server.shutdown +end server.start ``` diff --git a/doc/workflow/README.md b/doc/workflow/README.md index b18f62a1fa..7e996dc47d 100644 --- a/doc/workflow/README.md +++ b/doc/workflow/README.md @@ -1,5 +1,15 @@ -- [Workflow](workflow.md) +# Workflow + +- [Feature branch workflow](workflow.md) +- [Project forking workflow](forking_workflow.md) - [Project Features](project_features.md) - [Authorization for merge requests](authorization_for_merge_requests.md) - [Groups](groups.md) - [Labels](labels.md) +- [GitLab Flow](gitlab_flow.md) +- [Notifications](notifications.md) +- [Migrating from SVN to GitLab](migrating_from_svn.md) +- [Project importing from GitHub to GitLab](import_projects_from_github.md) +- [Project importing from GitLab.com to your private GitLab instance](import_projects_from_gitlab_com.md) +- [Protected branches](protected_branches.md) +- [Web Editor](web_editor.md) diff --git a/doc/workflow/ci_mr.png b/doc/workflow/ci_mr.png new file mode 100644 index 0000000000000000000000000000000000000000..a577356f8e899d55dab9b9137010ac23320cd762 GIT binary patch literal 40065 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
    |gPG`$@Y;?`gci2r9>O8`012Ob)Z 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 literal 0 HcmV?d00001 diff --git a/doc/workflow/close_issue_mr.png b/doc/workflow/close_issue_mr.png new file mode 100644 index 0000000000000000000000000000000000000000..a136d642e125a782c8058417ff808b98159c0899 GIT binary patch literal 146292 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 z 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 Cb&u>|DgwR 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 literal 0 HcmV?d00001 diff --git a/doc/workflow/forking/merge_request.png b/doc/workflow/forking/merge_request.png new file mode 100644 index 0000000000000000000000000000000000000000..2dc00ed08a1df18629be937740f32ef35abbb87f GIT binary patch literal 60597 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 P_QzU3_mWeM)OoNyoN>6L9M2;4Lp zVaKU?ZevH5HQ(lv>fAJNZP^jXYc|+A?QVdt2$>iLUgzo%(%F%(nqiktF3S8q+l`g4 zc;*vPaTFTz#2VBi4#S>TWtzgC9#7%0Kwp3U#oPD`zWED(0y?TyN9Y{V>mG!T47}68 zQ|uU7sEg`bLd%WIsh06v2c-I?M@xAr#4k)3A#L~bFI|^KZLb9Iaiq^*=m{kRke z>Zv$UbS{)I{z1Y?-ZX|ldcbUoOEp4M#r{oqhoe#J007$la6iM9ox2(|I;&j z_Pn#-Ir@&jvmE59U#+`ptySx~FCg@ppS>%hw%gT&4>fnpOOO_rZ z

    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 BNcR8$ literal 0 HcmV?d00001 diff --git a/doc/workflow/forking_workflow.md b/doc/workflow/forking_workflow.md new file mode 100644 index 0000000000..8edf7c6ab3 --- /dev/null +++ b/doc/workflow/forking_workflow.md @@ -0,0 +1,36 @@ +# Project forking workflow + +Forking a project to your own namespace is useful if you have no write access to the project you want to contribute +to. If you do have write access or can request it we recommend working together in the same repository since it is simpler. +See our **[GitLab Flow](https://about.gitlab.com/2014/09/29/gitlab-flow/)** article for more information about using +branches to work together. + +## Creating a fork + +In order to create a fork of a project, all you need to do is click on the fork button located on the top right side +of the screen, close to the project's URL and right next to the stars button. + +![Fork button](forking/fork_button.png) + +Once you do that you'll be presented with a screen where you can choose the namespace to fork to. Only namespaces +(groups and your own namespace) where you have write access to, will be shown. Click on the namespace to create your +fork there. + +![Groups view](forking/groups.png) + +After the forking is done, you can start working on the newly created repository. There you will have full +[Owner](../permissions/permissions.md) access, so you can set it up as you please. + +## Merging upstream + +Once you are ready to send your code back to the main project, you need to create a merge request. Choose your forked +project's main branch as the source and the original project's main branch as the destination and create the merge request. + +![Selecting branches](forking/branch_select.png) + +You can then assign the merge request to someone to have them review your changes. Upon pressing the 'Accept Merge Request' +button, your changes will be added to the repository and branch you're merging into. + +![New merge request](forking/merge_request.png) + + diff --git a/doc/workflow/four_stages.png b/doc/workflow/four_stages.png new file mode 100644 index 0000000000000000000000000000000000000000..2f444fc6f7967772980948dfec8f779a204bf915 GIT binary patch literal 20934 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 literal 0 HcmV?d00001 diff --git a/doc/workflow/git_pull.png b/doc/workflow/git_pull.png new file mode 100644 index 0000000000000000000000000000000000000000..7d47064eb14176ccc23e472cbf1d9ea34f90d91f GIT binary patch literal 167056 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 z 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�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

    wW=lsbI)-}wqA4c?V~Q) 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 KXr-`$|NjH{z@=gU literal 0 HcmV?d00001 diff --git a/doc/workflow/github_flow.png b/doc/workflow/github_flow.png new file mode 100644 index 0000000000000000000000000000000000000000..88addb623ee0e554345dfbbde879e464befae85d GIT binary patch literal 20600 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 literal 0 HcmV?d00001 diff --git a/doc/workflow/github_importer/importer.png b/doc/workflow/github_importer/importer.png new file mode 100644 index 0000000000000000000000000000000000000000..57636717571a17ea17ee5cfdabbd4408597fd668 GIT binary patch literal 39335 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 z

    srMzknqEbX#-InYc0?7`P3XJoDH@LG4!BkNX{z7 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 IH1@ZhVi~s-t literal 0 HcmV?d00001 diff --git a/doc/workflow/github_importer/new_project_page.png b/doc/workflow/github_importer/new_project_page.png new file mode 100644 index 0000000000000000000000000000000000000000..002f22d81d7d07eeece0deaec177b0dbc70745f6 GIT binary patch literal 46276 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 literal 0 HcmV?d00001 diff --git a/doc/workflow/gitlab_flow.md b/doc/workflow/gitlab_flow.md new file mode 100644 index 0000000000..0e87dc7421 --- /dev/null +++ b/doc/workflow/gitlab_flow.md @@ -0,0 +1,316 @@ +![GitLab Flow](gitlab_flow.png) + +## Introduction + +Version management with git makes branching and merging much easier than older versioning systems such as SVN. +This allows a wide variety of branching strategies and workflows. +Almost all of these are an improvement over the methods used before git. +But many organizations end up with a workflow that is not clearly defined, overly complex or not integrated with issue tracking systems. +Therefore we propose the GitLab flow as clearly defined set of best practices. +It combines [feature driven development](http://en.wikipedia.org/wiki/Feature-driven_development) and [feature branches](http://martinfowler.com/bliki/FeatureBranch.html) with issue tracking. + +Organizations coming to git from other version control systems frequently find it hard to develop an effective workflow. +This article describes the GitLab flow that integrates the git workflow with an issue tracking system. +It offers a simple, transparent and effective way to work with git. + +![Four stages (working copy, index, local repo, remote repo) and three steps between them](four_stages.png) + +When converting to git you have to get used to the fact that there are three steps before a commit is shared with colleagues. +Most version control systems have only step, committing from the working copy to a shared server. +In git you add files from the working copy to the staging area. After that you commit them to the local repo. +The third step is pushing to a shared remote repository. +After getting used to these three steps the branching model becomes the challenge. + +![Multiple long running branches and merging in all directions](messy_flow.png) + +Since many organizations new to git have no conventions how to work with it, it can quickly become a mess. +The biggest problem they run into is that many long running branches that each contain part of the changes are around. +People have a hard time figuring out which branch they should develop on or deploy to production. +Frequently the reaction to this problem is to adopt a standardized pattern such as [git flow](http://nvie.com/posts/a-successful-git-branching-model/) and [GitHub flow](http://scottchacon.com/2011/08/31/github-flow.html) +We think there is still room for improvement and will detail a set of practices we call GitLab flow. + +## Git flow and its problems + +[![Git Flow timeline by Vincent Driessen, used with permission](gitdashflow.png) + +Git flow was one of the first proposals to use git branches and it has gotten a lot of attention. +It advocates a master branch and a separate develop branch as well as supporting branches for features, releases and hotfixes. +The development happens on the develop branch, moves to a release branch and is finally merged into the master branch. +Git flow is a well defined standard but its complexity introduces two problems. +The first problem is that developers must use the develop branch and not master, master is reserved for code that is released to production. +It is a convention to call your default branch master and to mostly branch from and merge to this. +Since most tools automatically make the master branch the default one and display that one by default it is annoying to have to switch to another one. +The second problem of git flow is the complexity introduced by the hotfix and release branches. +These branches can be a good idea for some organizations but are overkill for the vast majority of them. +Nowadays most organizations practice continuous delivery which means that your default branch can be deployed. +This means that hotfix and release branches can be prevented including all the ceremony they introduce. +An example of this ceremony is the merging back of release branches. +Though specialized tools do exist to solve this, they require documentation and add complexity. +Frequently developers make a mistake and for example changes are only merged into master and not into the develop branch. +The root cause of these errors is that git flow is too complex for most of the use cases. +And doing releases doesn't automatically mean also doing hotfixes. + +## GitHub flow as a simpler alternative + +![Master branch with feature branches merged in](github_flow.png) + + In reaction to git flow a simpler alternative was detailed, [GitHub flow](https://guides.github.com/introduction/flow/index.html). +This flow has only feature branches and a master branch. +This is very simple and clean, many organizations have adopted it with great success. +Atlassian recommends [a similar strategy](http://blogs.atlassian.com/2014/01/simple-git-workflow-simple/) although they rebase feature branches. +Merging everything into the master branch and deploying often means you minimize the amount of code in 'inventory' which is in line with the lean and continuous delivery best practices. +But this flow still leaves a lot of questions unanswered regarding deployments, environments, releases and integrations with issues. +With GitLab flow we offer additional guidance for these questions. + +## Production branch with GitLab flow + +![Master branch and production branch with arrow that indicate deployments](production_branch.png) + +GitHub flow does assume you are able to deploy to production every time you merge a feature branch. +This is possible for SaaS applications but are many cases where this is not possible. +One would be a situation where you are not in control of the exact release moment, for example an iOS application that needs to pass App Store validation. +Another example is when you have deployment windows (workdays from 10am to 4pm when the operations team is at full capacity) but you also merge code at other times. +In these cases you can make a production branch that reflects the deployed code. +You can deploy a new version by merging in master to the production branch. +If you need to know what code is in production you can just checkout the production branch to see. +The approximate time of deployment is easily visible as the merge commit in the version control system. +This time is pretty accurate if you automatically deploy your production branch. +If you need a more exact time you can have your deployment script create a tag on each deployment. +This flow prevents the overhead of releasing, tagging and merging that is common to git flow. + +## Environment branches with GitLab flow + +![Multiple branches with the code cascading from one to another](environment_branches.png) + +It might be a good idea to have an environment that is automatically updated to the master branch. +Only in this case, the name of this environment might differ from the branch name. +Suppose you have a staging environment, a pre-production environment and a production environment. +In this case the master branch is deployed on staging. When someone wants to deploy to pre-production they create a merge request from the master branch to the pre-production branch. +And going live with code happens by merging the pre-production branch into the production branch. +This workflow where commits only flow downstream ensures that everything has been tested on all environments. +If you need to cherry-pick a commit with a hotfix it is common to develop it on a feature branch and merge it into master with a merge request, do not delete the feature branch. +If master is good to go (it should be if you a practicing [continuous delivery](http://martinfowler.com/bliki/ContinuousDelivery.html)) you then merge it to the other branches. +If this is not possible because more manual testing is required you can send merge requests from the feature branch to the downstream branches. +An 'extreme' version of environment branches are setting up an environment for each feature branch as done by [Teatro](http://teatro.io/). + +## Release branches with GitLab flow + +![Master and multiple release branches that vary in length with cherry-picks from master](release_branches.png) + +Only in case you need to release software to the outside world you need to work with release branches. +In this case, each branch contains a minor version (2-3-stable, 2-4-stable, etc.). +The stable branch uses master as a starting point and is created as late as possible. +By branching as late as possible you minimize the time you have to apply bug fixes to multiple branches. +After a release branch is announced, only serious bug fixes are included in the release branch. +If possible these bug fixes are first merged into master and then cherry-picked into the release branch. +This way you can't forget to cherry-pick them into master and encounter the same bug on subsequent releases. +This is called an 'upstream first' policy that is also practiced by [Google](http://www.chromium.org/chromium-os/chromiumos-design-docs/upstream-first) and [Red Hat](http://www.redhat.com/about/news/archive/2013/5/a-community-for-using-openstack-with-red-hat-rdo). +Every time a bug-fix is included in a release branch the patch version is raised (to comply with [Semantic Versioning](http://semver.org/)) by setting a new tag. +Some projects also have a stable branch that points to the same commit as the latest released branch. +In this flow it is not common to have a production branch (or git flow master branch). + +## Merge/pull requests with GitLab flow + +![Merge request with line comments](mr_inline_comments.png) + +Merge or pull requests are created in a git management application and ask an assigned person to merge two branches. +Tools such as GitHub and Bitbucket choose the name pull request since the first manual action would be to pull the feature branch. +Tools such as GitLab and Gitorious choose the name merge request since that is the final action that is requested of the assignee. +In this article we'll refer to them as merge requests. + +If you work on a feature branch for more than a few hours it is good to share the intermediate result with the rest of the team. +This can be done by creating a merge request without assigning it to anyone, instead you mention people in the description or a comment (/cc @mark @susan). +This means it is not ready to be merged but feedback is welcome. +Your team members can comment on the merge request in general or on specific lines with line comments. +The merge requests serves as a code review tool and no separate tools such as Gerrit and reviewboard should be needed. +If the review reveals shortcomings anyone can commit and push a fix. +Commonly the person to do this is the creator of the merge/pull request. +The diff in the merge/pull requests automatically updates when new commits are pushed on the branch. + +When you feel comfortable with it to be merged you assign it to the person that knows most about the codebase you are changing and mention any other people you would like feedback from. +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](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/permissions/permissions.md). +So if you want to merge it into a protected branch you assign it to someone with master authorizations. + +## Issues with GitLab flow + +![Merge request with the branch name 15-require-a-password-to-change-it and assignee field shown](merge_request.png) + +GitLab flow is a way to make the relation between the code and the issue tracker more transparent. + +Any significant change to the code should start with an issue where the goal is described. +Having a reason for every code change is important to inform everyone on the team and to help people keep the scope of a feature branch small. +In GitLab each change to the codebase starts with an issue in the issue tracking system. +If there is no issue yet it should be created first provided there is significant work involved (more than 1 hour). +For many organizations this will be natural since the issue will have to be estimated for the sprint. +Issue titles should describe the desired state of the system, e.g. "As an administrator I want to remove users without receiving an error" instead of "Admin can't remove users.". + +When you are ready to code you start a branch for the issue from the master branch. +The name of this branch should start with the issue number, for example '15-require-a-password-to-change-it'. + +When you are done or want to discuss the code you open a merge request. +This is an online place to discuss the change and review the code. +Creating a branch is a manual action since you do not always want to merge a new branch you push, it could be a long-running environment or release branch. +If you create the merge request but do not assign it to anyone it is a 'work-in-process' merge request. +These are used to discuss the proposed implementation but are not ready for inclusion in the master branch yet. + +When the author thinks the code is ready the merge request is assigned to reviewer. +The reviewer presses the merge button when they think the code is ready for inclusion in the master branch. +In this case the code is merged and a merge commit is generated that makes this event easily visible later on. +Merge requests always create a merge commit even when the commit could be added without one. +This merge strategy is called 'no fast-forward' in git. +After the merge the feature branch is deleted since it is no longer needed, in GitLab this deletion is an option when merging. + +Suppose that a branch is merged but a problem occurs and the issue is reopened. +In this case it is no problem to reuse the same branch name since it was deleted when the branch was merged. +At any time there is at most one branch for every issue. +It is possible that one feature branch solves more than one issue. + +## Linking and closing issues from merge requests + +![Merge request showing the linked issues that will be closed](close_issue_mr.png) + +Linking to the issue can happen by mentioning them from commit messages (fixes #14, closes #67, etc.) or from the merge request description. +In GitLab this creates a comment in the issue that the merge requests mentions the issue. +And the merge request shows the linked issues. +These issues are closed once code is merged into the default branch. + +If you only want to make the reference without closing the issue you can also just mention it: "Duck typing is preferred. #12". + +If you have an issue that spans across multiple repositories, the best thing is to create an issue for each repository and link all issues to a parent issue. + +## Squashing commits with rebase + +![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. +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. +When you rebase you change the identifier (SHA-1) of the commit and this is confusing. +If you do that the same change will be known under multiple identifiers and this can cause much confusion. +If people already reviewed your code it will be hard for them to review only the improvements you made since then if you have rebased everything into one commit. + +People are encouraged to commit often and to frequently push to the remote repository so other people are aware what everyone is working on. +This will lead to many commits per change which makes the history harder to understand. +But the advantages of having stable identifiers outweigh this drawback. +And to understand a change in context one can always look at the merge commit that groups all the commits together when the code is merged into the master branch. + +After you merge multiple commits from a feature branch into the master branch this is harder to undo. +If you would have squashed all the commits into one you could have just reverted this commit but as we indicated you should not rebase commits after they are pushed. +Fortunately [reverting a merge made some time ago](http://git-scm.com/blog/2010/03/02/undoing-merges.html) can be done with git. +This however, requires having specific merge commits for the commits your want to revert. +If you revert a merge and you change your mind, revert the revert instead of merging again since git will not allow you to merge the code again otherwise. + +Being able to revert a merge is a good reason always to create a merge commit when you merge manually with the `--no-ff` option. +Git management software will always create a merge commit when you accept a merge request. + +## Do not order commits with rebase + +![List of sequential merge commits](merge_commits.png) + +With git you can also rebase your feature branch commits to order them after the commits on the master branch. +This prevents creating a merge commit when merging master into your feature branch and creates a nice linear history. +However, just like with squashing you should never rebase commits you have pushed to a remote server. +This makes it impossible to rebase work in progress that you already shared with your team which is something we recommend. +When using rebase to keep your feature branch updated you [need to resolve similar conflicts again and again](http://blogs.atlassian.com/2013/10/git-team-workflows-merge-or-rebase/). +You can reuse recorded resolutions (rerere) sometimes, but with without rebasing you only have to solve the conflicts one time and you’re set. +There has to be a better way to avoid many merge commits. + +The way to prevent creating many merge commits is to not frequently merge master into the feature branch. +We'll discuss the three reasons to merge in master: leveraging code, solving merge conflicts and long running branches. +If you need to leverage some code that was introduced in master after you created the feature branch you can sometimes solve this by just cherry-picking a commit. +If your feature branch has a merge conflict, creating a merge commit is a normal way of solving this. +You should aim to prevent merge conflicts where they are likely to occur. +One example is the CHANGELOG file where each significant change in the codebase is documented under a version header. +Instead of everyone adding their change at the bottom of the list for the current version it is better to randomly insert it in the current list for that version. +This it is likely that multiple feature branches that add to the CHANGELOG can be merged before a conflict occurs. +The last reason for creating merge commits is having long lived branches that you want to keep up to date with the latest state of the project. +Martin Fowler, in [his article about feature branches](http://martinfowler.com/bliki/FeatureBranch.html) talks about this Continuous Integration (CI). +At GitLab we are guilty of confusing CI with branch testing. Quoting Martin Fowler: "I've heard people say they are doing CI because they are running builds, perhaps using a CI server, on every branch with every commit. +That's continuous building, and a Good Thing, but there's no integration, so it's not CI.". +The solution to prevent many merge commits is to keep your feature branches short-lived, the vast majority should take less than one day of work. +If your feature branches commonly take more than a day of work, look into ways to create smaller units of work and/or use [feature toggles](http://martinfowler.com/bliki/FeatureToggle.html). +As for the long running branches that take more than one day there are two strategies. +In a CI strategy you can merge in master at the start of the day to prevent painful merges at a later time. +In a synchronization point strategy you only merge in from well defined points in time, for example a tagged release. +This strategy is [advocated by Linus Torvalds](https://www.mail-archive.com/dri-devel@lists.sourceforge.net/msg39091.html) because the state of the code at these points is better known. + +In conclusion, we can say that you should try to prevent merge commits, but not eliminate them. +Your codebase should be clean but your history should represent what actually happened. +Developing software happen in small messy steps and it is OK to have your history reflect this. +You can use tools to view the network graphs of commits and understand the messy history that created your code. +If you rebase code the history is incorrect, and there is no way for tools to remedy this because they can't deal with changing commit identifiers. + +## Voting on merge requests + +![Voting slider in GitLab](voting_slider.png) + +It is common to voice approval or disapproval by using +1 or -1 emoticons. +In GitLab the +1 and -1 are aggregated and shown at the top of the merge request. +As a rule of thumb anything that doesn't have two times more +1's than -1's is suspect and should not be merged yet. + +## Pushing and removing branches + +![Remove checkbox for branch in merge requests](remove_checkbox.png) + +We recommend that people push their feature branches frequently, even when they are not ready for review yet. +By doing this you prevent team members from accidentally starting to work on the same issue. +Of course this situation should already be prevented by assigning someone to the issue in the issue tracking software. +However sometimes one of the two parties forgets to assign someone in the issue tracking software. +After a branch is merged it should be removed from the source control software. +In GitLab and similar systems this is an option when merging. +This ensures that the branch overview in the repository management software shows only work in progress. +This also ensures that when someone reopens the issue a new branch with the same name can be used without problem. +When you reopen an issue you need to create a new merge request. + +## Committing often and with the right message + +![Good and bad commit message](good_commit.png) + +We recommend to commit early and often. +Each time you have a functioning set of tests and code a commit can be made. +The advantage is that when an extension or refactor goes wrong it is easy to revert to a working version. +This is quite a change for programmers that used SVN before, they used to commit when their work was ready to share. +The trick is to use the merge/pull request with multiple commits when your work is ready to share. +The commit message should reflect your intention, not the contents of the commit. +The contents of the commit can be easily seen anyway, the question is why you did it. +An example of a good commit message is: "Combine templates to dry up the user views.". +Some words that are bad commit messages because they don't contain munch information are: change, improve and refactor. +The word fix or fixes is also a red flag, unless it comes after the commit sentence and references an issue number. +To see more information about the formatting of commit messages please see this great [blog post by Tim Pope](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). + +## Testing before merging + +![Merge requests showing the test states, red, yellow and green](ci_mr.png) + +In old workflows the Continuous Integration (CI) server commonly ran tests on the master branch only. +Developers had to ensure their code did not break the master branch. +When using GitLab flow developers create their branches from this master branch so it is essential it is green. +Therefore each merge request must be tested before it is accepted. +CI software like Travis and GitLab CI show the build results right in the merge request itself to make this easy. +One drawback is that they are testing the feature branch itself and not the merged result. +What one can do to improve this is to test the merged result itself. +The problem is that the merge result changes every time something is merged into master. +Retesting on every commit to master is computationally expensive and means you are more frequently waiting for test results. +If there are no merge conflicts and the feature branches are short lived the risk is acceptable. +If there are merge conflicts you merge the master branch into the feature branch and the CI server will rerun the tests. +If you have long lived feature branches that last for more than a few days you should make your issues smaller. + +## Merging in other code + +![Shell output showing git pull output](git_pull.png) + +When initiating a feature branch, always start with an up to date master to branch off from. +If you know beforehand that your work absolutely depends on another branch you can also branch from there. +If you need to merge in another branch after starting explain the reason in the merge commit. +If you have not pushed your commits to a shared location yet you can also rebase on master or another feature branch. +Do not merge in upstream if your code will work and merge cleanly without doing so, Linus even says that [you should never merge in upstream at random points, only at major releases](http://lwn.net/Articles/328438/). +Merging only when needed prevents creating merge commits in your feature branch that later end up littering the master history. + +### References + +- [Sketch file](https://www.dropbox.com/s/58dvsj5votbwrzv/git_flows.sketch?dl=0) with vectors of images in this article +- [Git Flow by Vincent Driessen](http://nvie.com/posts/a-successful-git-branching-model/) diff --git a/doc/workflow/gitlab_flow.png b/doc/workflow/gitlab_flow.png new file mode 100644 index 0000000000000000000000000000000000000000..1ea191a672b7cb177687cfa6e93dca43af8b8c55 GIT binary patch literal 90883 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�#%$!u;}Pv(HVqn#sDb z3Mup^yntth^rO4rg>y}CbeU=YTQ50uzqzm3)g`j>F|oqg2prOuBO9cUGF>X1s?lJ7 zrWsL@55d7Z%nGPSGw`S3!?p)}_=`y7@nW|FoDmQ7zof1cq^U5km(SpDoNTZKqz(MT zmWy!`^P|@;fI+4qJrURSOD!j`(GLkda{ZCtHWIMhu;XGXHDTtEOeHHOk|3SI8*dIr z#pKyp;(JJ~h_Cx&XM6Z4;fCibmr_gOHv+nk{K#8qxNhM;zpTarzcu(#(t`zYkbx76 z3DvmO1b2H8K+>C&sgb%{xC1I4espXX6J3VU2XD<^billgXy=wA;|}A`znFQ55CPlb zub->GS~GFhA0Vl!_0&&8?NH9D%mAp7K=SbLLl+PLhldMnXnl@4%sHde^j#{#&%L0; zIpIXY5{M@o#`D6e3a67lo%w~ao-cxtqRDuGTme+lWiAl8zcf^)z8_r?T5rM*q457U zVW*z|Hr}3(G8_Yq!q^3yt)(P^8SMKs{2H>LQ`KaP<+V)qd9t!E@@W(ID+U;`5bO{G zmEg<1xR%*4s`+MUbwsH7sA%UcjB#`Od9UwBH2|AM1jiDSFl(hl#ZQgX1TdY9BPv9UR7AohCssw0MGWzT?@9|0x>%zl==QRZ zG6=Y7*VxvZrPQrPl*Z`6@fX6|vov7x-=`!`&wE<{FU5YG;Lm-_{-5JQ&vO6KEPSBe z!^o2g{zaEKX{p$02yvXbU5J~}vn2*8?Vak)_DO2^p4evGSJhjmiHlFUviQK7s(?S>|Ur-|&4hh~i!T5p!l-0l`#g zG}rW9+^5SKQmB$s#U^DkQf~c3&do%(WA2gtG7%sX(N4)h)`4?PQuZ$BAg(UDo|t;? z#K2CS0~B?9tjX6bjBUGM*ZNXDN)j$pf(o>qv8!58`ZHy`_I*0uJSTH*r}osm069~9 zukc0OD3_i=@awiRFO}5jc74<2{Ueq7wyvCM><1hi&ZwXavW>FK`Xxq}q7f1H&0R{CrkKkkH;L0m(4UwH`cv}M&}}UMER0W3otlZ* zUKp+OZ?Ww^e^D_~g{WwAlo`eeoSo(fFkh!lk}{cEEvk^g0T>r#X*iK(hpI{9NJyZG*W@bg|>1#OY@J zS{5q5uEFKVt1*Do@!mh|S0)G1i-sCE4&*H6e=WhBgW=@JCPNqp=5kSkyms5T>JM_x zG1v2L#2bnT=B^vh4G!jxMCB>Qz|=I1O9>0PGJ+oycEAPd%cCy zkigw{e&+n9Jrb@i^s})jg9jM<^PeH)sxcP|`4e_O_6g1oCbcGoX5B(4&n z2~R&+)M;Z$@&QSJ^WN!Cs4;+3#qu3KJtQJ!5NobIAE^_B0 z*3^98;JA+rzgfKO$i3Sb(6)y4;o7nL`BeP$#~PDlH!2`rNDdfZ=+vvEe0vY#_bPz1 z8cz9}h7#6(23jQ!&(CL|Fn&EXTyZvD>G}L{r~CSn&T#PRY2vD0Qh62n5va$ zs&#dX)QUoeQxwMHO>3{{`I^wi;5;JDnEgn|4Wa7O%jDPLr;Dx-O6#}f_|Vp`Ptm*| zB>cN=H3o`%RS(lv$~~{b+7U`30 zj1crUJc&pA|Iq6 zZub%pYJ-eK>u+$BK^zcjGcX+CPy8ub=;TqTrLuI>zlC}a2&H7(YYzTPU;o>>7Ro^S z3wI2G(lN~G{)s!vbXaG!g3)LnahU&l@e-ymQaZP%*=n0)N-t#t zkZ(-HpL)&W{TF|V>wucju}4yrO-x+8%;iu|I57Huf+#ON1vHrARa~f9ZhjL$N+|!% zFWVIZwixGvvfnWV6_Ss0f2Nm3V?9EWxJCvrBpi=12sjM9y!AeMP9u;y+5GfK2>ucWRXLLkje6jYtotQo%EMdcyW8 zx{(8XvLxi);`t86yJjTMALi)82SZ0U4wQ1ehwiq~J=GodS^L=}n_kfHjBR`CnVbpU zOQQ~|>LDY;;DAyK6O6sf22_^8i%Y(hIABCCD1b;D!n5re`~``|&u~jhFvmblsrKv` zz0vU&=KO(SJt8*w{}$319FDa>8*nLpC3~v=Q^VE%t?_q))Yf!>cu?1In)K^7kC92( zVVI(5R;=-wzx(`s8W4}{C#+g^sva_n*=ca#+`~U5Al6Mfe6i#E1z_rQN&{R%G@hZd z<0a9?NVp}l$Fn!Lx$8z8s<7V}5$3J;fN(6k>b)fB3wf~a`9i+0Q%&Y@)hzU~kO^$2 zy?UXNUQ_|{yHk@ja4ib3*S2d{E~{Xums#m76yL2> ze_8R6)ILeDI{ta&#_kH_n9++4r=d585<#$0(9*kBg}%uP_9)E-i``I~1Q6EJzZ4f! zCk&kYG8R-Z8JMvj{28l^YBc&cpM@RWLlm|A#fd|1Hmp35niqekfWTWoyz*&y>(;V1 zC2jQb; zbXWO#Zp6HTD%ObrbkeFgo^gzwY3Rt&ZmDQtQ*S=@F(^yADy^i0ucJUwgE_W7{&pOd znFg})$Qg39e!rYv840KK<$u5c>6iNWA_6msF0<}Y=r(@Ur}&w>=yg_~DeqK#ZD-yW z9(t*V#;>-p)E0AXmNMTW8^p~KStvWW^Gs14M*AePW9bO{o(2-@yNL!T$@Gpe72UFlRFhJ^KOd>oAl|2&y%g%Kiee^Y-KCy=V@1PDta-} zISyZccq0;7Z)PcDbtvhDi|}uMI$uPr`ElOtwBkB)JUdmV+OX>KP}R){NV1oNsy}msuI{+| zTCVCd+)~42t|*fAlfE4JP>f%fBl#g-H61hdgj0#AxKMJDAu#@vQ0%{Pnb>=73$@=| zFfx+1nJZ8(O%f}_iF|XPJur2$9^!n|1$3FXtW%_1?$v z4omBvOQ=@XQk#;{v9vCwUDmpn=JBA1BcvQm5YDAdYjS8k=F7X@%{im#d1juOXP&cv{AOo%W_RZM$9})>*Z1}Qz?_Z3AcHwd9Cfr< zv)j^eF!`YIdz)cWh@R%V%qD8h+R0Zd6tRwh1MtmP1d(h_RMi1S+w+YBzQp^5SJl$ZBz`o)6;ygzEBM%C9gv3*kMJ|>IYregd1bA=og z(lz4MH8PRuA=f5|}Hr{uvQ-w)b;h7r`@2Ise z7m}yNLU!qlFf}lnHEH@~9nfDDqukpUa_$ugStpk{$doKbXb(u8h40vE`be>FdOGH@ zdBI3}cq_WNjDBk{3w;)k(&W}G9O6nL?BgaJq>Zcy&pLEe(kc|Q`(N!rDT}^@eW7P> z|5UL1r0mlo6BSymrXd&2h|k6(WS`gDTQ4mRa!zndf4oOqV8+pFbGBY2T;(YH61WPL zhUho1r`O%1RgEb8k#*kiH&F8p{=We=F(0zm!yu;`!fE*`p!`(!m1GnMtM15Mak=9^ zc|?t7QQ>(kC+9@cPIig^7HM!RQF&qOHmwmB_HZ0RcY9|_mI;2U^T%Jg-I46oqSdh< zyLBh0tQZXlG5;z3y@{)EEmrdAu1-dE#cRfs#GH1k>9@Ez}?Kig) z7oP4;Ab3hSUshAu8ykitJru@~XagFi@9Sp}jQDrr>D?a&hOU8V0nf*yGizpV>|JJO6X4FmQJk z>K+l7;yC$2$Kc`!qmkNn6@+^+s5m*wHtTj3UevxcYe|{o-l{rB@>gLBWz|VBvJ=u8VNgvdlg$xb3R# zgX|R!RJ2|y)%9aZgEn2_^ddK-@u90SFEUD6eLMAW!rp=V^fe1BHkpmoy7^cYy4kVb z*GnQ}Af%ft*{e2V0jKf3 zQ30^Krb31JMKh6)0r&A=tib1>3uZ~WBgc#{fB0#t(+6!&o%v25X)_kmoU&W%P2ejW zO*Hn-*bqfOXj!)9UgR&eSFo08dbhzpI#B>qO7;1fz*}+|uZBh_-I<~z--P6D;(c^- zVpZ{hS4>@UvfN+5D|{Vzk;rJf-th)UX3X4)(xeygK4&8yMK~6nPjdEyfcqNl{Y*p3 z>DNpO_5$drp&QhXl|wFv5pz_L*$}hxP}p49DApytJL$w&RsqZn*Y%r+zg8KtVxefH z_&93@EjZu32X?$lTO<3PbmqBm?T1G}D4Q>MjYtCSg~Ogw!A+Z_4*AWLIbO?#yC zh}d12F*9bW*WWz?oRAgQwNJfpYMl+4hnqpYe8AkJB&9ha+_ytG6kbjUKY-$?5WHF`t%J4dq)LMFn-1&3?H6<_xEtW8-rDG zB=hLz{b9&*1me17mG-MDs_|M+o0x*Wclig>r#zs5TfZfB+*!=_aCBeMFpHlrI%;zmR*i!hRnFcCAAqNVGLR~$p;Lam7rA5Jmzq#FDUp> zBumE!YUhCzkY)qU?(8)Y%Yw@IgNK?m1;Kcks0Dt7=6o>vuey>P{r=9k6aCA+#3$Ft zp`I%j+%!3z$sJl<3Bm~k z+m)=rDtNS|7lVVx7p1K5Tcs?JRl5bH($5JS?${JtLsdKKsaG`a_?kEFFFdH5{G|JU zx^voS5c--)d)YQ{y_f>*L=*$l2p*yRC=oS-b^Gl7;BDx%uiQ3xFyD?t zcC6w}^SA%#r=4oH6O`1$Pu%(5_<;cXv19O58n!xDa>Z?(P$Jfw(~2-Q9(_ySuyJ;Q;63e81qXd)J!P zneOT7u3fu!&E8MFoe;T?;_xsyFd!fx@RAZD3Lqfh_#hx)Nl@Zr@$T zP|hlA%xNs8F2so*4}+czL%kk?2hN0N*H45N@W6lQc^foBH5mlIblCVKA*Chg^Ye^f z)yb`E`t`~U0IfV41o}TOpRZ6$V&i%gl}CvX5JHrhL{DZigjLfqd$rj#3_iEh)XQQ#jHtMx z`muU?5egpv(;4_l;~_l<2S?_oD&ra+uqM)f-RwJvEFmQ&l=m_1ooYk689Ex838Gn< zc1x($aw8k{y5P9HY)UB;ocqO=&;mzM8EmIOXbFchpI~TaQ zt}r>I;-YnPHNpGcLRMQDK{WY1TW>a|c!@|Wv^|PD81KXl$m4>g9!Qmf3@jf)w_!?j`+})gp9;5;6Vybyj zZ2Lf0*K}PtmCG>W;?V83h3z>tzvkror(DrL4|&_MRCv0QMK(R?_ezwR{b;)>`L>tu znR`8NV5JT>)0bb;n z1gSeo;V-b^eqidk1~Fx6mM3+d5shTSQH^9NWGL>{X}s*=Q2LytUDfs$%jJ5J2bx%<7$zxWiU%&-(K+=2!=^1i!lsOLc^_tb?T&24qwiN&Lh_1-BUAN%-7#21%Yj zqR>;*0qWprd*201%8DS?f$eAPxZ%#2FIpU5N^fE3+P|-Eo}I=9jvU^$TV+-^$u4ZC zcJJKYW`4TuW2$VcnnIPGIKVGI$ZNGG_q1R*>KdGR<3)OHlT#e`SnfG0Z;0#WY_ksl z@kX>{*fzv#znL@Cq#0|o*qp%adgdL6;k+}u#4udo%${#>&{SE~;LQBY|2}ouTkbVH zaqr|k%ClC2J8|C9xwNd>HGYWV21@l%Q}EENGCYmL9w&tGV|^%q7Cwa(ajn%?>-;(0 zzkY`UNim2U^RwaP+ZF&EAkUtrfPgYR5*CZciS-0s(T+K?KlNLhaSpns+hfP7;yDV2 zX{UZ&Ipc7h7A7IYRaGa(y2TdwB0U!F1`V9r??Kr91Ysuau$8(32oApHpc!4aamRF7 zbtLF3A?)h>Ds1DixmvNP#{-5Op_)VPAx1O8cwk+o7yRUHstm)6ubgF++bEiyLi zObw~P7CqhsGQ5HX4#0;!`P}i4F)>}jquTw}?fO|TIFCe!HN!U%w8naNI7pnSDz<#qM28srYjf`ml-AhUoOgx0vaIb!`+ZeRBHFLCY6}aa>1FUDs36{~SS8206gSpekHuF{@wqoQ z(nMq_=`3j~iQMbw#P+|v&F37_b1uiUQ@GvS<8)M4$#`r(ut2#9yW3l1!6?lmIlum- z!NX_En!3I2K6AT>e1FY(g^&z3%FHURK5X{s7Oej2s>AbR`>jq7DQ^?LU+x{T>Gh*T z-EjzY&Z7i>)3=N&yO~qJ&%O7Z??@5M`FJBwywjnY{0}Q$Q?JGsA+6XkD_#$kH0x|T z=5*g53#3t+A5i+OC&havGumE3_u2pyT__JSQ->C_Zre`;)GIwG4|nHe5$>&mKT#U& z?hADfaY~;mj;Qh7+ExiEJb57#;xjr8%B3!}HXfb#Jto<@j8Y#hN*!J7ABWmY+Df#1 zp%jMHb4eOftK%bJW-VUe$Zk^QXXVEU^v5*%?6@D1PVyAnrqbC5^Xy02aqOp_QiY<; z27SBzP1kYC3$5DLSC;U-=8N{s{*hxn90BYrcjVz^&9RT~^Qo}l0`jM-Fq3&lkY@F# zaQ}h6nAu!#w-skX9$rmzsx#{r#83Xee?riF)sgX&5Y`)1RfyY$bcYG136pM1y4>zx z#gv+NlgxUXN1?@KD}Ct>xNu$+5N0risp0cP{4_qJbMg{lXydlx*2w+o z56(?02BY%`F2Iz#gNo1BIdo9usL#)MHcFaB zu%s5!9T^ec)8q8aSZ@@|uG1DmGFBr|?BPL(88&p>d^YCkY_hKWnVT^$C%H|Dq8a`lK23+v z=m_VT&3zb`hc~HN{wCoNKW)!gOa4P37Gs5Mw`)yQ?R}rlrQsg2tX;Y06*9)%5b+dN zBH1$S0tIAmpnuJ2!XDNC%4tXM`@Hr63ivR>6d$z$Zs$#cIok2V2T2{h!m?sA zS}6FgZF)r{`puq?*Yy0&g*KX)#Y;`-+Od4G*i|5;+Q@puoYc@m9yYA|RP;vkS#8$N zbj3yoUsSiw_Q=!I5;o?kDVwDblH9ywGe|herMKkrVe^x>@|3)<3mLes{4G_t16lC` z*kZDF^w5YGKI3*_J#Qr5 z5r6z4){7HW`bPZhrGtaE1Os(|L|E1ShL*cgu$j&3^?t~oghof&U zhBx16MuSdTsGfLc&^3l|y#)w0KX7Ea(uMBupyY`-#Ph5jl89l-OLcREI_u|$CNY<{ z8t5{<*k#y`V%al#FxG|@?8#*pz@cnh@1MG0Jc_VJq$x$-iZ)B-UY5Kd7)bw_pYi<6 zNb(Hp)6c@?n$0-9tPya?B(Do6A+xV^=?JLfG4@!cC!c;rqe%8NxHbT#O&zjv@Qr~#%+(7- z8yVC0B62RoxtogXfiMj~iE-HKAKNn(r!bXdT9TcLphXtHmsN}xKFo0!1=VAOiHxuD zT<)y}ff42`JgU!#;lJm@1n7!pwLTjCqDDU*HzU(CwJjMT$t(&;BSt&O4)|Il z;^xNkFXd6)SAp-cM{}HvPdWVJ`4?a}*mQ^5uCx(NZqj6Q2*YZ{uNx zjYEy7b?nGo-9-77dqQMb;zazE-{4!GUVJLtk@R3d%B<(zwh~1uZ)3Ky7XmZ+HVt(Avlk;cWN@m zKfkRl_{-sUUi`pp|B$VOg-DEai4|FtnpIj-`9+sK2JT&9SiK1Z<*#aMykPHaOkc#^ zeR@RGBiCIQnN0kaqNlC-Zdwb{<#tKvO!*_V1_R@|TM09`)U5KD1B44&?GzbhMTBEZ z2xqL=+|&mfS_;P|NpM4*ihYIX-~cj^TCx{kDquWzoK#gWu0%S00up=1lA zQjyA_3~W~|EqxE%vhdVKm4Tn0%-V*J-`;1$3x`D`K;&2`KDqcp+BxYbH&^1ZTfKvvEs?himg)YaLJYcz3jeyq(?Ya!WvPA z|8`Xx-W;NZw*QtnyUi^&m~2BOr|E4ThnGxedHRLW5u}bZa{xEN?YYQMGi$e0|Ic3y zo=I-lLw*@$Nt`qHe$;!+DBV%Pc^*qLG>d9VCmJTQ^hoL+9{k7+Z0tf;vG8>jgFHAv zDc4O|yw6ZZ%xNC-9|0v>fV=BcpD@YuJ9Rt#_;RQiMn3e9%6p=*ysg%gZg>eFtdmy! z*XI#9Q0l}lw0%xMQP;m#H8D01(wP&^c-Wz_A1^T(W>OZ@Q|R^0_UTP-+d?#Xm1&ij zPnL2M5N`M$u6hT~ZB>~}Dt3P$!e>CHHD45EiS0VYeGWXqpD5T+{ z$6p9mt=rOdo6{NJSLexXt%M$F7GL)UJMFvwQdfR31S{ZitzCTtvyzJk;`x*pj z;#R%iKy%}w)rH)}*in>-^mjp#(vj4t&0C*nh2mjXM7Vip>VASI>EQwy^RA#v@9j-5 z1nCbXukZL%S`0%Ga(2S;Qzw`_xL%RFWP%#jO?K9BnhGBtlAqx!Yw0@>=(Y{WQFa|- zXzU?Pv%5Qbq!FU*nO3eCEl_6OB!LXWS9Im}*dZwzjJ!gvYu)Utf(<#ZUj~uVoZw3u zt8(JKjRDJnig~B)^@W)df?J8C0cON(En66o8|Ac%1?A#fi@(Syi;d?K_FW-O_SV27 zh3kbEVEi%*LS>1fo1l#zdMj@51JQe{d06VEvjH#38)mdccDkMrVq&3%5}DY^X!3z^ zJ5!&8ErxJa=;x$JiOMU%0m@t9;4;DCEo0r;L|J0a$siIiLmLo+#@mmEcyr|U_@d84 z1-N{%r<2zoky-70JMv;b8^9eY)M@kb4HAAFu|uK^*)qM@@@^)M+V~d0f84M~u@VAc z1^s$HO>rK|vA3>*91)20x z`n@*ycdPHQsTAH(H+BU{=@M3Oo4O{pj;%=q(x*x&I(k87}b8HzmdH&|C()3Iw)%@z==OSuMNrG=D)?`{2^ZW zfo_#3CL=7BM-sHoG;l|Olb;gO^};%&woqYUG{GsG$U?p59g{Jio&ANJu93frnU6j> zA?HOJoOZ9XG7iCZl=VUsvEguZbsDLC$?UHrFU( zSA&hKSmrlu{0^<8>hR^pL6JbhDx`q|nVN4wnB6hKGk(kV=q5G7dk7ZPyh_etiekj= zkOuS9rvpYqaPBVLuE`tA&W*2j=p}Qk!rBNND_h$>4HqwwJdzx>RW#_V3^mfXjzgvi z{rgA;TWcDIVHKWMR*6rOy%yGNZQs+_isjNoE(+nk#!=J-948(cZJ(8CO&7*yefj{k zXZ^}&ezi5_6Y&y>;J8|!9&gsu-au?G@$6P6e{Qcq^-Z$0;$(v+?26|gs#GM-^9L4eoshBE>5~ExI#rh zYK>Ml(n^=lU>_E_G)#JTRm8e9J`LL%lr-^a?24{4emK4i*fOoN=m7EQZ(BNCLCcb+ zHjQQcJgK;cktNW?wZCXhxdT`Y3728)`?9p&q>SS}?;q(&(O(hH5V7d9rfOc-;9+{`N_<|{(wuL??^!E7oR}2 zh!Bb}q)ixB8=2jeodoEO`2`gP;z)ojYJNxtxH&W!pOJS^ySf#L%gf36uzdcp)&WUq5u9IS#3CwQ zQ(h56Vgtmgu5}^!3w34ljwRnD|9ndTjE#IOTBL8fNHr!Y2>@iV(+&&pxxAv{{-53r zv9P{>kGbmo%A1;+y0*3^5Czz#L#F%l%RwQWNgRx@y?%E+t8+fY&}QP7qg=G${R;R_ z&mju>h>~6R-7vgfpKsslvyG7b4-jsov{|&QGZo4)=!=w*vZ0zl!*^_3 z82Y>_vZ&kJZ>jA)iP~SK|#S|#C8J7Okbq(%1WWdTFdbobJc2#`AT0k z8J6FHpJ@dJ%sBA7Vw|BUcmM=^zpyY^U>xXLS}s%V4oI@$gXPI)hyi(iAkGHn!#-symm>_2-hV&KXBK7u)dMEOz$nJKJV0maF36DBw$!Z$#d0%ow2@>c;F zQ2>ryAhy^2b`ln=iCjY8XYfD6=4Xir=WuYQqq+5N1D5BapVW~N1wiE6NGyk&E$qte zbh}M0O;ac>i{=|)E{&%TfCaC7dd}KEC5BLFwfen&p{3js2`*>sW$@hg zwxTpW_JiHi{?R5PmtGqVwq52~NZ;g;$ha?;dk(=nKF)h@X>Zz!{HP5#OT@v?HC$M= z4_sj#^%ta_mSt(Yf$P<`Sw(5dNSWiqvtX*N0zleI$TE^kW zW#<;`Wd8uWJg@5Qb3kfeFsD02G*@oE=V_!hOS5eS7HZ znc}=r=&?HEj-m}+*)ict6HS~^vK*xM=?)g9(viUWLuyEqwz@yjp;~yFGnAlLty&A` z$HUoqXN<%a6e9h+{1wHMT8Ri(@XN02R=wkJN!TtVU0i@Xi|S@BXzZ;+1$I#d2?La1 zaA))M094erfz3X=|ViQzGGt}sizFz|BgA5_J6rR8ji$C_}r&2efqRB$P%3Dboq zCbTX_%v_3sQARm!Lj!BbGmO$jbp*Q%BypZ zuV2pm??SH)EEv3$Lw*Ag=s|hs8GJe5eJe?Yf^RChh>To|_c+O5uIRLn8SZNTxqS#a zI1H*M#S{VR+_5LOXNYYyWcA1fqRE)VVFx82L%Soa+$56!L@B}^SbVzIrwyOlj!as$ z7)~PuQ~gd%FC)~Ou!Zb)>ZPXBG{ zA1%Pm_Ln+F1@pr-6rX!!`!Su@N^PkcM3vp!jd5E&EDDm1!1Vbqk9!)BOEu+L@(HXBk0^}7Uv_@(cxhhwSk-Z?q}MV~;;cJ&HGP(b z9%RdFQDNQlq4*<2=WKC%!j@WdEi2B6@8o%P27|?h5R-is3fvDYc2m4`uLJskP)f3# zK9d01TX1H5WSm;;R#?j?|Ez7`MfK*TnOCOTm}z*pY)XrgA=Cf z&3NHboLhWgH_)k9Zi(h*hu*y}5u|}Bd$`+-vOSt(g743GxuWEGzM3>yZT0->Q~2C8 zUTtIlJD17?3xFfzBb?cR#>Qu!B}WA$5I|jf3Gom$?%BxvQ|HUi;V>S~phI8nxWKow zasC<>Py#x~1gxLYyvYpZN$WP!`jWq~u^o8yjZDjB_!Wuy0A28C8=evga9 zDyZ}bYB!(Y9?Yc*PNW?XRSFh-7sPnLt#k4$2~e~jVKKU$;`3guyYL5+^G#^d1A;T% zuO%ga1j_U9m?Pm^!LqL#YG6w@1)u=5(g~3HgTM}d$fU6R#x`7;7|z`f zzkWqop;(c|*8ve3y#Ciw5rV~Boue61L$H{GZSdK`JW!J2QN*LAG!Kr>BI-+a;jZMB zat__0dumj9_iG6mdXckf!5V8(WB5sWaI;d2FFfgLR4B$bVzh_eXyEB;w3hxA=OffM zm6BZ>eH12}KbGquYlk4sv%5r_2`|&ng{;(Zl2`X3`sfMz!xFqOPLpzJDjXw|swKL> zkj1&-EL$q0Hs-$bI2^AcMmCtL@j1egh&m3h)MGwJjZRwltdfug+=uOw!fNaW>Q8Rd z2fNULgh3vrr-|b3=Coa`4C8_-FPoo;730m1&EZvCd=)W zxeA8w&ybLjHDl}!4HlPI!bI;^1zUigLYFfPwDZCY^2*w)|v?kheS4vB$;<+6=xqrp^f(GBlB>x zG6ZPjJ&d$3oSpYCW`#vI5Dsvt(_)m%Lj3rE%#mEqQHLLf$>1xgSQ2@&ERT#oF(U{e z9ZsPZ8am!uX`#Sg24~8VP0D>w2b~fsraK1%!MQkyUL8$iQl{TE{57~HEnu&1nF$D( zj%BQ?2c>(e^;pJ*hxQq(_H3H`wiMa-<`tKS=T)QK_P~eX!o(0ODD|W1gFSjs&x;42 zsff8M6^*P|a+tU*z$tDgS{SWEFe&H-FhzPFa?>Nn;4^a0HcDBz&-T83N5K{wm)G(3 zwqzSlHuGKPgdB<9@53ZITkGft(nD|LWwMCHQVw_Y>V1qUH@Zrpd*Sxjpo(BQO&9T zVL+P{M*?OiKMdeoQt-Tg`P|+<2Toz2F((8A>Nv5zTzcegpTYSH_xBex5k4km9(*sg zCTBjLqi)35!~oAe`mBaHOb^}z%GSaI$ReJjFD!7dg>AJiZ4r-<>S2}ND9 z2j^XIL9I5n)mRl*c8NI&q#V9%>3nhH0s@0=)2cDSf}f1UX(mcaRNwUa8NJKfjK>JQ#`maLzf`qRMr9TC)HooF?lqn*5fL}q z)wo`b7`z#F`#skfV}xeK8kcvGR#rg~Lr6BO7n`Nx$_?F<@RM%W9L7EIsV|qKvylAw z3gom2$oMqk>`Ay)O0B_CTj}upefDbgBCe`<<#wmy64+!ULXLDHyNVv79K-H!DoEZL z2iJAZu9e7XbXfOlI&ij1HZu%Hd)=9gq77pgm%ci7ESh0#&!8_Is94Z_h3yqNS0Tqkad8}8%I6hRqvp?fvzQZb#!Mt=cH3L7RDofmMK7nWaIRDzn@&W(uFO4~ndh`_h_Z7sTv%6Vn{nQ=(1Vc*P5UmN*&~yT^0U|MnQ2KP$R=&sa6WA2 z1l&t}E!cG!R$iAsNTl}9>$+pL#r>;E`=fG$zkQU2th zbY4foz^y?R0B>Pg^zt$M9fGphHIL!vp}pE#Wv@Ij$tGof^pPyD-B0!k5e1N;k-gQ) zsj$D2203!M#kjnvLc^y|u|&F*(CNw+Y9Ae^Gq|o6BsmlZVpCFzc1io4d#z!6!V@Qn zK@~ntEPOm-vZOukmYP%6HSF08m074Ke<(?#RxQ8;g0pwnU4vwDu)qbT87N89AM(Kr z#rkesewD_0lx8n;fA7SC4CN3)q1RUb7qnVc4JQ%`8~qs$UCX z93J5Dw`~E}?}2|G3`S!{gn{}s9?f~0-*D%rR#OQmmJ_cTugD4nUhAKm{2IOR>A>Uu zI-L(cKYy;+?ce#iXy1(De`yCIfb)LRs4smIH&VY1fsY2B1+K{|&E$Hz(t^cdLsVQ+ zLf1pKAPB}r0{mW1W+sS^*FE{$fiRIzzoCK;gadG)y#zf?VYAhbJh!+AL{KQ!m}O2; z!1i5BXXXMC0jJl~x%kt=VcFT$Rq^V8jl*xWP)-E=Dn>OH{7C%aY}satb*pbm3N9W# z{{Fd%39N*>G%}Eu%js_dwUgp*ZjCo>t)7qBMnj2Yzotk(^`k(~ca+GHf=Rjj1~P## zGIG2+ukA7ozFJtgwS2)(tUDSyPoysK4Bz;zWFz{ ze{m{Z46sBRuJ}2*>@GDPUf&vX&}$ux|L|KP-?zf;z=AwXk-`QEZwrUS_P$y<$QQPv z4v^fe-vR!cc}+p98*(9<30~ir;mZd^a3+CiMiRW1k z|6{#w3^Rs~+(cexUq8K|9 z1#Ar2(1gtyxwr8KzIrksw7s4~8b{*ztb;Gn)E)}G(_Xa0$jRdkjhwmW4D{GqruB(% zyi{i)IkjL;6w@j0Ua~W?puukX%6>CYKZ+FgcwSs)Uox)`jc=4O7ljU8!f5UxJC}Kv zI%1$hkHYR1G}T1dy@gZBlX1qWl3@Z!VqFXi-N#vVB*wP3lm&-7%r{$ZhOTdL4$82` zoEzL7JcYhQ(kuJz47m&l9KfC8bW)UnF>`FB_5rNJZjVI>#|+TT(H(73*jbF zpm!b@bPyW68-+i{28vXOS$mMjr2(KlJDO=L?iy?lzEBW^_c**ZrcK@2X}D`})J>T`-I%j~G#cR7Jn8!}{~Z09`msffb?(dWNGuf3 zJP$_Ld{QR4?+pBI3XD@X?r$h1H{ZV*tRFD2B8m@OOkfe-C7_xmq&;wgt*T@j2t9`) zq;__Ha$o1Q#7lQ9^IA6s%_)YvxTbQ4C?LXntp=T?iGWV9yQ! z*1BFkqw4d%)f>X9!?BfFuE$_$Lmx*zH?bYptMp!;xNd2-*oXKs*b{(mUar9dOl2S6CFArSaFN-xjs5}3)7%v2WA#D7UvDt ztM$rdf_sNOA4ZkF@)@u$^of%WT7i&=aqIWRMn&tMLM;+bAUkVi^c9=>LfOpje!;0j zwa*?kt*vWWB>16P+Mat$RH1`k88-MeAF*O1m)kEo)b+8{bOp06Y(S0dQg-U028Azb z&x0PXyZZG}u+vYSgk4kzQ0we0Fc;ouQ`P~kM6cm5bJ>$yRH(;-`vK>gD*a$H_!63% ziKPRBp((ZN6)nXc%CEagY5&@;TJ&YqF8FmQwr)k*U-6DmB z819|Aj&N#iU@;OFhlJ29UWIdyadYB!Fx{&q8cM_5;|_&I`C%B_W|;feLJ}@1m{1x> zzK%T^RVuE~V+pU{)rn9DoV-0bnosie33++xhNUaV9SY_UPL%20uU5g65AIfm{7>8K zk2FV%N)CInzLVd*aB08X5LKeK#t1j22B!?{4z%dTJ<#oh+(D(Hk*ow)eZhM8PC@12 z`JkT)fJ(StC1M|f(R)n=*+oMJ*YFCuY)NA}L>B+%72`8a3Cl=JhQz?*(ok-xfhqoh za1NQ{6;*rD6XOKYp?4CzG3WDVNYo~VjR?-ga>b+M+hPCmwhM5fZMkDwz$2T%!n%K( zW$yv4MzPux6-rn3_UesV3oFzOg=~k0wKwAAExPUli~rj41&&I77a!jFOZZ2LyA33j zr)WSsG4C2BC!?)i3i38FvEU}R#A?bneq@ancU={0?rsaI_8&UasJDFqQG9&_h*#}b zpw2{2lSxR=;Jc6niNf~hHrZ1Vr+PabJYVDBgRW15@(4`>O<(WCL{eZe5Kqbc1k?yy z>F+6tT@H->0-y^V%R8^40pnc{c%3t^KHuD&7p5iXFl5}Qov$EsKq3eS1Vai4P3%ZWEVOu@-(Q_j z940FEmz5m(2X<-M985A)D5({BGR{9Bfz4+06esnPX8OD6*eNiXS%J9?I5S)`X?Y_% zE1x+GyYE9^HELbFx7r~--Jqt`y2eK-(L#D|i;@yg+?X$_(Q$PW`8@$mpcr!z1t1HG zuB({O9uOcWs{fpz8?3BS8botlF7rgnlhAs=3a7&n%f*R>?Q!HmLtZ6<*SgSFldMFU zX1R~M1v4z&_UPFU{g~ri?TfynfgB68^J$|8w0Sq`=2nj+Hiys!x}t6aRQEfn@N+V- zee+HdbUisU2-IJZJH1Q*Be5B}44Xe?hzO4S`Y56XaU(kB zpQh*jO!D!0L9<(?sZ)-Wm$nZHW$IYZ0(m^E)1Q5H89al3RpXFX)&e^>FegIH?91@( z&ExsK_bp4Y7S7J_0~kJhJ*}$3AUcQO{m!$8mOaNhPPCAmp-{m zvteb2xLlJJl@htnz~@V2?X5(=I1=YG4iqc%jKNP5o`ys^&DtIqBBZ+4lXP9JSpg|f zn!WN2&H9M4T6At0!(!Fr`JVYJH40)N_x#>|G}~*^c#0kp$;`5$wL^z_RW0#7=se`c zJO|-zmP-kY2qWK{n5-VDZBYP<`S+o3n0=envm5T&+V)?}w(HW;em?>yFb&1(HJ;^? zZ$op<+;PQ>@CL4~a3QiF(`l(LufpV+prrRw_Np%!a8gu$I+2bL_k@6qFQw)}6O$#B z6i(7j#1&6;jw;C9zz9i-Det}Lqg>j8P@VSz47;)Vol=a3}RrFGB_o9hX;A z61|NWaq~N}medpt9hW{EXf|w7HlO(B82^V$^r^aryESk2HQs`@qWC?k^tk9o+nito zy3#_I3j2;n1D^`(3>LCl+*S-e;lNMr-;m4at4lm;Nx+!5Zc6UQKVP4{qXz03h%Fuj zG#Qccdou10?MnyUw$p+!qXwaeA0J&fylX)x+Y%rg$pu13at%KMUX0fHq91m1*!A6- z;n`{Nee3P!3fPnPB#>iBS3>newpOJs?jG!Wgz_+&S#es9V52G1vzx>8HxB_e2Ue0~ zn6~-dDMnk23EFNi0l()1!!nts!3d)Afg057=L3}AzcMcssy|wsyStxHj<%UumZW?l zR_e5^w6m)jb)KtfkL@puwig~vv@w~4xuoRtocgud zb(2Yg2dmI9Lq>E-lIHlG67GO677m3M0yoXl@H?N?d}J3>fhK+Z&f^W+RI?dSUhZn4 zn{9iJYEQ&YvQ){yn>*v{;{?<`W+$WQG{%tG=;gvg7O6yG(5KjjdnC;n5$r{YC&{Gq8joG+6mXE z+DwUr+GO1rva5~D(wQbHkMNX9pdQllf zkJe3N-rB~r#TMq1_R(X0&!+Bd;hs~zMo6T7Izq#YQ@o~mQOFzUvSf-*LGtJ*1Xl>z zkUCPQ=g(@yuDAz8tDB1^BbRH-ZJ5RbdRJ4J=V~^LxGQ#dT0BHW^^l^UO!gK(9kj7N zc`Z#3UiB8(lNdXZjQ>DhpM2=V#;n+=f#{we(7Vw&iIpX~Czv^)zB7o(!+7$< zbJKnjLO6X;w6Rc_?NQg8`6j-0xByd-;YAOI3X|AX-)QC-J4~7xwH2I?oAjZx zDEk8sW^VI>SC+e~(#N51E9`e`#|#yU7yN@i_JPZ`Zd@Bdq1etB@XzN$v{>Y)=@PFP zn==_cRb=f9_OBh$8U|r+82DgrjhR7A>f6vX~L(Y=I=eOmF?r^ywiRJ2z%sw*c+1@AkSmNkm`gS?F! zQinRj>eWT*&Q-U>Gd1)Y{)SJ@)Q{?+?XZ3fq&&0S&_SFmAjvSIFo&sqeuzt&DIT-Xjnf6X{{(so7rqm^nY<_ZrR?HD10 z34`BIPgcYtz(^ z6dFC|Rcvd|nn#9!*ScnF{YQ!b0@`SvNLzIRNTT-!lG>u#0o07M9G}_WJ)ad_D(Gnq z$jdJft0&Yt*ZCgHGV4t8V0P)MBZwCcTAoq!%%#iT!J%&Vg!gJRrJf!Ko$W$Ob+)E= zZ8+7ze|-KHJF09N;>z$=KN>0*W?t&!;ELm7cbK9BX_6ENqW6k;XHoat>vV)|Ju3Fy zahEKB4En6}JXr&)-H5GOUF)1Ot?!ZqhibD1Zgs2)Fm>Z@YW02W> zLX3b_W5#GdESPZJH_V`jg43y`PD86f4JA9vaLb7FO3BJqunTfYohI5W!wTw!TO*Gl z!apP0fM|?W6rkRq0nEH*0GK9y>ZJDYVA*I_NyM^7<%+fz@|BlUBG#&h$&$jA2X|qP z_>2G$nG=h^qg0-`YBS{{%W*f#hKsaj)8^q*E8Y(SIIf(W>cy=kA$7S@z~^+`ArMKe z{%8TVMs{oUWo5$Lq;DWGxz^^1oIO`i-m%JC=^UJg<82VqrY9-9sNLgmt1Y(AoKaYA zuDi}FAp5;1fjX5wlxql0KwO>~7%shb{Pq0#!&wHQ5Y`z_vu}b)+c@UO-;IXa?U%7YMm1kZ~wWqub2!FBG)$qZ>*KkW}qS*6kQm7Y23WZ`E|K^SVsLKlJ@A?Q2Pp_z4n-UJhf6^o`Wp~4W`{p0LlF1aX3Q$

    WsjAH2i zr{{rJ_Z9mH^aeYITFa#HvOYSYPUHI^jp?^~H-FRVF&|)rOzmtGh+G81O5gU|6fJxk zZvES*I6swg3)bo|@mvn76vLKDGDSVIrVSFIK za~vz6fKde~Tyx=Z@bx*4?jPJbVH`WP5@g+mEzYJYd2 zBarFlAn2J3<8x;3+QkEJ+Dv@lL_;owW76Pu{cfS!)E_vRlZAJ9%bN0cU)26|7W??% zm2(kr9AC@nsltCVG&bHC%M#AcnXm=wHs|K%GJzu5z4TP#$J?goug6vCMTi*Y`6H@v>LcdenF*ZQFr~g8YNp}>% zGS@;wK%K`DqoEr(Q^&oQ4*9md%^H(+(o9QuDKX8*8Og&)c&XkNe8f=Z>eOm2B_A=y zlpjQ}HSGiY5IkON#;eZ{7v_A1cga7#T4$F(hNJq4D{GeVIg|8J{L;($_I?1mf={*X zPhx^)z{bYDJL^KUz1$g5NL&F{A)%n4+C6WVRS%ku;_2>gO9|I+mxf&0YY8a7runJ9 zw!o%=X9&w-UgFqox}YVBW#rGN7FnLcA9)y1NAA=H*?)G1gU0c@aSY_;MyGjcfo~gh zx%rW{PIf?aISj2i@Fsbc1&bm4;Ef?+qGNDi+$w9hdpnGV_VfE64g|hqM9!uO5mfgc zovu{c2Rtr&{0#MwliCDRDC~QwL<$ zoDJ3F7z9?1KuJLJ?TY)}Q~~Nq+pAY9k1I-TZSCE0-72vAAT^s^kV8U3TrLJEZAW=; z(Nc7r%+Ow1j#-2FFkuic)30f?*PoP{kt^Uok{av9yW{gbjE+Jk?~k4qDHj4;%bCg0 zv;Wc8F8WlVB&%p~Z!Ac)f)aI)Z-W!g{yn`D-^PsCTDz$3S#{F5G@5X$uMEq%1(k-Z z1D$aZV4DV7` zt{_B0?bbb$#8Bs?zD!5plW06KzF-BWMyuGuJSn7Jqw@XWwwX07V13dO(NkJY9+Vf0 zb>gMKSx9bcRU`fisSxA0u$N-)M+ZET9E7#d1Bp=2&x8=tcM&Cc#s(`|fvY^8KHXXz z=jPJ)H*Q&4H_PDXvwFFZ7ZwGt>66C1t+FsQry;04a=q;35U?KcpU^4%9kAL9N8p+VRnKhxz4>%=r0tt1grP-PDZss zsLba%Z)afAJUzyE*a*ZK4pg|@AxR7+patW+@n}f0&r>dS1A>CZyQ-hA;1kC6wtC4eYVe=C9eiTU!z7@m7_mWT}c3g;RcNpQ$ zPW()nXx-!|&k){ay}05dv^!Ob&3ql$eSCW**iW?z!%Nl!zeD*a$@~5m2HRI_)P2Mx zoGnE^)gn(U!i4{kY!oS9w7mOx+7^}J^#EG>v_PcJAxGC3y>lHe)&K$>_la4&8J^ z#4;IEnwI0Z13#vStZTk zuqDpv<0P&lYt1v(!@4KY=?r&Wvf$Xqh<)^-lOAtiIN_w1w0l!Oi%#X{#~A?!1RcsO zv3B}F-Qfu-7}y;J1py{GKyZF1y};f1sk>=2z7hKH|FHKJPIY8KzabDbxVyUtmq2g} z8r&tgyF&1duU-{J-MRzk%yjqZ>Hf7{ehehq z_4PR{iTvh{=yhLG?c!zh{c#(zi7{otDx&{+XXqLH z%EG26uk0#xs%&R(&uq~Bx(a|`$|cn@v%*GH-*3CuPK+5`%Py13)>I$k7}SN~QNd?| zr-0V{iG=dQc>8o1WKTTrI|Z@w9%7M*fj6PV+1V&fOUOJuaWR`B&F$siN2r3$|W1Aim& zrHnnl9yE1Q0IGEe{t&mp{zv{98nLzwaF@FdVNQ60uql|NjmETVaRq`53Me39HOiWbsy6#YNXeG$dG0<$&kKv zOMYhz!vgo_6YcG+y^X9E`31i4r_k}bMe)VYIrGE1+jKAysKYl=Whls6+?vA)jpN4q zc-KoMFB3~8TDs@2$39k?e9k_LfX0-;2g#t?{#ZbQ1w2YUv=BqfZ<}VG4r6*apA8#& z61xg-*vExan3H9fF_oc{S+saf;x0j-E`I@l>(7NnL3__|ei1Daz9n+4Mu~Pa4o#Us zC$ww-VJWC8A?|EzM5=Sf?ZEUhnae)dQ$@jH8%kz<+~%!7-S!L9Q#D~w;e6jun_}T0 zpiF-lkyL#@&Nn-n3)Tlj#_TlFyJ z0sJGHMsgLs7@+z*5CMuFdu~Q0QrB&`L)d}pBuk7Lop$I5nX%*RqM;uOc46$dtIvKa zmeI#FLY(o6_NREkx-d61Uf~SGiUYQsV-mb14GC{xL)%b{CX1L3*6N^>O*n&)jyX@P zurES{MP0eLZ}_{2*B-!aFer#2DW<)+ea+V%D*9F8kvh#EB6dBxKjIhnt9fTZvyrfn zMJ2g#_haHyaN!4R&XOcQ(zh36J9nczk&ROPqX|v6pXJ>3)pNj=y(p<{muq1cD0Auz zLZv+JA@oiciy+vZD#5jCeQ^5u^XJO-$+{dDAx(ki!~KZ&qdmDW$gRHwVGlh7VME%4 z;RbUY?u=1peNlzK(|S!0E2{N_6}6c!rOqR5Xnav>xGN?hLA^2=_j}Z+Fpx`@4V6_6 zfi}4|NNvq=6rb{DrkUhrP;1}#1$GLXEa=feyB_WwDT95f?RpO>sN_dsh$S-NTB2Wo zG%Ypv-1W)^jAxjbF5XhgC!@tCTM|2TnW`P%_@`a->%-UV^vjS?ttZDvCdbfN`^`?= zuZ$BH!6U+JdRuHDgHGmcR(h6KuzAIH6Z^yagAlAuIXXufo*Z72@bQvl$WLP8f5$HN zWJAI1!ey3j63P8mN_Aey3uL-tlVAA;ZBxA`!;}S`hNCaJywc>X5)hnWFo{RsP7UAe zYQi!8s3T0!hh8=lDp;04{=}7T`3s2o0@%Q+x4StxJB+Awdrn1e2(=0o7ya|Aq%d7T ziIE_XKt868(&YVkV_d9ODN0(@gtxwd!2ksXIu(I>eRAL3(J0`6*0qIs<&bYf9t*qR zYLiYDQTu&5s;(sJ>!iRur8h@hzku2X4#e%1fWRi0Mf3v1jJv_G&YiACP@Q*{oC$s3 zCh2{XZLMXHZv%1hmeUQNN$=a$*_<3Pl@0;1bLud+8>7-qSYHf*1eYjxXxq@xr=YmU#C`OU( zx3)PY2u5DIRx6M^1bvmtqTHt6kgO+)F%S+pw-rP|>o%a79t z)@}2rG8m57pO)naIVEBiyCxv^xznN495E18XY*l8U}?U zi+iY8bu|1y@M;rUwK;&U+2&>zuf$ zum8sr!VkW`UG%?-BQxkSB$;#?s;a6l@T_v$i)t+AbA!S_1a9JI4*9%@u}6BAmSK^k zV(nc(i=;(2&hVcQy*{HtR@n+%*fOW*eBzA?O8i8|4N&?4n?qkT@_sUnslmA>dF78*=_RlW9lH`mnw@z_8s)4<*#m6I-lH+|VDcS>TOq+#16rChK{t98xkX0}WOE{Jl2Jm})iu0P-uD$wa4 zsN&rafQw9o>MYhgUK>255RhBfDk$gAmu!xdX}G`6+Su42rly7=ARw4?=YFn+qOa;7 z7${Obbt%Czh6e4TQNfHvJtU=+ENNPXVC10qoK+wFl$wcq{1%mqHnJmEYpYb#dXGE% z1v4ef|ClM!=zR@CoDdNQm*G=fgi(pqh`^qZ%&4KKirvebHOB^G6~V9JDt!`hg-$}7 z)EIs0Q>P4#nXf{B7vUnuP3EDnZd_|1-%zswhy$%Wp)?WW)k68~xL z21LD0z5ps7=jXSeu?+wvY|HQip%8_A`?x+jtXkh}o{w>9aA>zF7utkZ&T~)BTS!6v zBnVqWp8TnD3+a6Kqg90>s?_Yi`f!@10UqK3OGzUEJ?lc@;R_qGUq5EL$Lq9 zPy?W0Cjd8egxc&i?fnNt33;!;hiWB?2R(StKCWL3)gm~W#w?Bc?>!)}Z zMbrJ87BcPWv?ff&Sj10)1J>Ix(7T8v4OAJH_6=E_Y?IxiCY9X=%(VID{Sk7Zfxo2ym26 z6<0U!s0sY%7C02*?h?{Pw;k}8U<_o^|9~scH#-0E1Q^ z!Io4|n)eO7jPp|X=9IGk<>nIIyUiS(6CLNO081&{kTfXxKXu(e*H=VfpEDavl;%gL zbqW%8&!Dm>?}^R%27k8hYyMA>zP#m**0Ai|cmai3{X%b#+LFJ3s=0GF?rTqGju-J0 z*jgxeRpNe<9Oa^06eoT4JoqNN-xKVo%$985GNVCs$zJxYc?+@AV8?i;ZmyIiPWnit zcELJTXfV!qCi#?Q815+iVE()HcVB3nmBJy?zf;*dw@o85vn*nplIlh5V`_?)_@HZ7 zBlNzQqio(^Hh?~zD@*z8K86}7}Psp0ZUc24Z2{$Ey;eldB z-Y?3kUZE?-bR1?4jF9kpymU)cbLfSWe;qK8pdZL=PbGYo+XZH;(aot%aOiq$HraR6L|}}leQ;|@9v9` zdebh*_#7e*eqJy-$oM15A`Vz{H!wwgIJ5>&v9&2|zt6atC~$I(?h_sMTAP|$m^=#K z5FM6V`XFM4(W^MjBnS4Wp0Hm#M#pCpw&YHp9WH}|O&M1ZJqBOS4y}9n$sR(@!$C10 zJb8Uea+m7N-FBQ`S_IQ$A?RohINSePH&Q{w{qR)j%%H2JWJ>sii3x>DC%aCMHHyZ< zMnCZqpUpHKjynqtQgQ7kxm1B4gZde@j%z`uB>mYBKOLC9cMocMA4bu@ zksms-{cMnmkF?y=z@a#yz|ax4?yt@USqO%g%$xUWr}&zm9$Jp(dM8^PyAsOXnZ3Tm zbCz)>5Y$3tk$P5K`o6s`)uPqpR#7&c{uo3M${s-w^AtGiJ?n#Vw!Fi@Jb(5b|s_Yl>4T0i zp=HEROpux2US5h03A@^llq#or{i&Epte*vNL0R)02Hg$34?M;k@5ArGKQCMo@B5;T zqE7pbjovX`wMe;$-QL~*ByVoDT1p^&aAZZ5BX8%hIEn1v3kZYlUf*hIi#kok3KL4G zC=w50T5vPsF!DZr`-TB)o{;lJneZjLhE#=ZoAJzSDe4>^pU=Urd#zIfvJwCMuT%DWn6Uw9%0_lz8>mQF}YxSwN82Wzg~tjY=9nX zOkFbPsryRX(#aqZQE+5?q*bfmt?D{nKdVQu^F4!+Su)x59bSFjp|7H?35ap z6ZneTc3J}Qn!!Xgvdc>za(u~@J-Ps-aHuQH}CIJ9hl5C#|S0e=}DWqIEY!} zg~NpG=LEbBGPWe57v1JYQM24g4Tra`o7&N%I}Y1QIDKX%6|pRN zN<^H=nrR-rH199%LnU}~f{G{)O~NeumUS1oIBXwD$5wefUpp_%!bO9sd#-f$k#HIH zPSZeZxv2bWCG@V_uEx&1%=x$oCHF50mG3d^Q=8x{hw((K&Ov;>#~atFM(b3asf?Gp zxhD-`P#2;}i)o~8wJ4&cE>tV*ytX>DyqC-Fa)*~1gTY9)HYS8d2i#DY$Z$#B&RxMO zs1@qTw~t`p$HG_oO|*lt&>zy$nmQ!bzcp)$losX7I9~$C7b3qOf-5ZBLcCf^b(^j1|idawpX2@ zYI;+SB-`iMFC%r9C#F&uo`Mqjl^pr2d=@0}&p0X7hm64sfA;I+MX^Zpd#jmIFswy* z)ujAR#qjU8yNdDjH!1O>7kHrYClESMPPwa#!3?h4E^jQH>MQGF_aWS|HNUvP52+S*{b4&waLee(^7c%*lpElnl`<6k zF@zv5Ui3zI4hTrn(mTB&4C&DYOD67N%H!;rhkWJ>3Fq7{Z3%ju^O4E!m~R46d}K0B zl^lGj%+Qt-A0F&cx?Jvv4#QktXyXct*E#gWzkz(<^sT17lpwx9IzRYg9H;FFlNOA1 zEr+*mOnTH-&X+=V8wIi_NEs~=8VVG;s45mJdK41|Nm{xIN?HEkZP=9~c>jlRm+lxhBhK!Y7 zy{2jxM7twAl4-#R2i32w*O4d>Z}7Y8onp7gpt3yATT}V`u3D*LdR5&Azc^`vA9g_% zb_;7j%_gRE<;}J1`LXw0sI|gNA~r4zbshcl7D|8hY>k6Oe;lCjvOlK0zXIuG-2Y%y z9&&JMASjcD7JN!gtsdsjqnG-X3s6AM{T?CjaFp7(qLmu7OqZvFiJw=9={0bL*ndMT z8}4Vm=2mk8%)g^r7$nS=%gbhJ9aOdk2hGo*DVxv*OOpe zuBwE&H(jR-;q!sc>acxb!<*8l=R8a2=NeI!x%^3=$4@8RSMhraXDw0iD;R}|XT}q9 zV@Ye8>5-A8GP8t~e!iU1rf^fU+POT+ZAAdx^?s&J3C9|4RSF%lk9E+63+nleC zDpPvBb4RLBIr_m&l*WeP2#fE*29#}D9UGG_cH9UV819KuANU}XgQBE$HGccl6|Esz z`rcs$&4~YwtELg|X}U;EpirT#I0zxgAptW=gDRDgi!2gNjmLn5p#8d$r!O#!N9Up- z*g-X^wKXvlBRJYNuc#$UbPZj?bR0p+{u>ok1>w<%N#|fbXp7$3S2%h-&RLHTd z>PYxgQ;Z@Y-v@EKy1%Vlz`!M`qk(AQcY`gXlWy~7NOUrK>9xKaI!y_gW$hg8tK3)w zDZ(9!kTKWX8>9V-+B)^(o^W*h=5<_CGwM98gBWgNmS*-O4lnKSdW24n#ECV`EiK4a z&h`R2IKfcz$@R?|pWnj42Tw|R1K6-a7%bA|qL_k-*rW1<95&9&aJLHi>>Pz@i@g=Jn8VZltK&m_yQX2>)oVsXD+%#@XUJM`uqr! z>+~y#Fr3eQ&WnL>&09N@j~IxQ6=FUwMwrg`xb8>zu~oRlOh z+)qC%tgb7|71+4qHW@oV1l5-vpQT)_#YP@fDE~N)OSbQ-R0!lR)}nsg^0g#cnOJ!1 z=V|Q~dPA9mKN~~Ti93@}2Vb;NiIU)JR_dD=$z6FNHD;^it;IcITB9tKzNE%1Yxc5=HMNkH{DYfPAm>U}b$r`K zKY}=Q-Q;_R1-B4dFV-BiHh7j)LUP9N4_wlQGnHm{@)ZQq}AjHg%G1=O#`_X};$XVMjE=z9FDo8L^>AH5J zFfh&Opv(LW?|TyRUfMFdh6IAI6Mj@F;GqdVZcb0)zvA=YexTj2eB({ikRdD)RQM#P zU&sn&J#-_<4$E{w!eb}Re9%kINabN)2@U@NKRTMZ;m6W-c;vIr3{Pa~9r^nxUjhj^ z9ZSY_K|S#)?ig@Uk3OcWt)Q~$q3L4hxBe1l+)J_XH?i@OmY+I#)ivBCw6P^6H-;8Z zwX&97tO|F&D3+cqrersVTv#3Hn^H6MBWPHvt29fk=X35xKT$iPPCB`u@Qj3{5Mb;p zuA^?vt~s+o>%`bkCQT0AnJVu-DDO z9hZiI4Viv< z#{z&sCKSj2W84y+KfsZ!LDruC*7Xhv17h^`d!@#I*0wUF&HF`?KM@ z56GOJw^ecEKT0%eb9cYZ-eqi?TvD6VLwZ~-I&Y8XA(GQmN@Jka>BzG1(ZQLIsI-{O`N z@0SGm7?G`*c%g}iu`txcEmdbN($RainDGCSq)!J(Hmv-?nH^43+98#lV^(n(90IJr zic!VC8^LfU5(0gAE2YLR;q9xEx-O0VFn&vT723=2O)+_gs+d?)UnlOlA${Ku@1lRJ zHFYX5v9m0o+3>bt^{dLVbECf5*w4+zFIv-eud8bOk*iR&l|2WRGm<VC(TpUV+}2Jy zYR4eq?k}fsK-x?#e@*l8(l`Nqe>@v);2R1Pm0iU-*Q@zAnJZpjmw+!e?q40J*1(#R zxTR-IbL_0_&d$az&>6El1ynb}jddTONHVyPc&z!x8>-4y?mx0#2oir+)9#pwnnV z_Q!DqkF%2US>E7%D&i6{?CNEtgVbCcyV-PeNUj8XAPo0iJ|8J(RfJH$pO)gy7UBFH z)~Y5M8FdUJQ->&g{oR{%C&c2Kj|rorNj-zrn1>IryH>^`)-RKMeq_x@RX~=AR&Q}k(DlT_W#B~t_ z(tQu?uQV=GzKe`IgM%Y4$STD4hwEQZ>SR^tff>tePjgpa1Uwy-l=HmR;9o95{VXW9 zy`!5mb;qG&*1wtEO8D8Gk|UW*Dcf^nq8ru6+f8ncx`SVtGVA=(=ldgTGSv>*OACl( zyVkCN2~Q>jY20~eYz_06{u_zj112=&j)qA~AplFB`x4NElJZ&OE)^Q~q$A^}?3WV3 z_uOo+33(tFcTo!)6R!~Ge+74-C`VoR3k^S;+XCsdrYcQmwWJOo=@egddT$!5vbKwD z(Cc-q)dxyRP8)SHn&9M)gno+PRO;etMB?Wlz`fcfMzwyTPGF&geaku|7w|wWk#Nzk zU-A~~p2_mo!FJz6E-w1>c7ow?SVG_um-&e#O8I`JjRz|SOh9^20{R>-^+|@f@qBF%yPL$v*Q5gq!)u>R~?!Cdy^W?Bf+FCFW@i+T-C3L z6WHk?+xqLi;dP4j5cMFv@D?J*Aj>hk1P$IpSBjpS*2GS4Cy68<8+tE;WPAxb0_UH? zCI{Fv^PBl89woXJ68-&Tf2>TYD3l%^h7z)S29}*Mv<-1UM+|8Gd|Ws1u+3)^q(JCi zGW71DwoSZ4xbSsfCH9~v>6moqYgPHq-y;^gBBdLrkwXd9Q6cjB*W#j z4VQeV&+bXSjRbNq{6{gGM^vtIEUL!?s9kJs_VI#vCl6Ir_I0N=`FF)U8Oznhc|@Pu zxRo+<=Yg$DEY3QPPVs#{;-buGCLCK@1-_Uw9{8?KZcCe<<<1rs6W=8v@X<&8aHBi& z+ElEQJKU3lONZq884REUOM30{LEQ~(CdE+Ai&^G>lxLOh%dpEV_3Z?W)a@_l**Q_E zAGt5PLHY_&oC&K^ktpLZCvBvWWRG8Ky`f(&T#-s7%M#%wu?QkhKj~3XqwsmMyJ6<7 zqT3!%I`6UQ%QYFHkZeg(iDXg`uX>4N_GoiG5uZ&PG{|y8zU7^^2!1$`UtQs*>Kup{_S0|6k*k2lP|BZ>m6#fTW#d=u!aZmz^`sc_(>4RHjarkBx zSwfa;(uF6IDE8g3p?eETQHTDvY=OwpCz9zq@PeHNo5W@>%Fjs|xgr;fGf5ly*_Re? zSN$?pS4plJh|jEPEAB#*cx}BkQ|nZ8Rn~NDR}`$@do&TU2l>(`3VlXzxwfCs^+-8Z z`G^a-UXfaV{c#V*1@V#8>u6FvIMatvA-dw|y}KVoy?)l9JMKs-^zj}@<}jyssQJ@y zZA}lqa&QNOWtgW&0&^mne0)UgIO7F;#0?RUKz8??pQK6$YDcj}gW|y*0W&hgPxve6 zJuMsPiFp~X=piCb+#kY}CgMjH33p715FsjZfr?yA-?DQ5vIilw&}`htkep z(>W~Rchu?fey0LxHrB*^R|x@eS0{C>7xu_;v@=srA&iPo^FZsdW7K&aw3m?f`H@s7 za_PR@Y)j#sQ|WDU|NU*r$>@P7G~YazR7n5N;z-+}Sld@a-NRq`0@24J89p2A)UZW~ zDhCJ`*s2lveyuUXB{`RqjwTFiR6DH@CBb!5*SzS^b+{ZkjCYGTSAck^AgsCfrE z7qaGlqoa#%;RQXy$ml#tW3qQc_l?CMis)8N|0sUXQj}>)`{C@P#cFQ}F*n^TlZ}o0 z{$qn^ni1u$r^_01!HzDmYxFYr{+EB4h4XM+lapzFlG%Z_a~uubd#)VfD7M(1k$k4- zkBfv^`@(&QSNn+?j+uYqlL>v4ZasUp0#V?L-UQU*YAZE|>kt#w>>x;a{JY+xp4EyFys9s}h+RA5QmCEFZKk3sBb4pYZOD5~u_9`Vt7q!8Y`yb@Y%p&5(mxz45 ztY>@VWJcq_A${ffbZxw_C@=y~GxP-JZlAJ3N#GrR|8!96EGn|2_H%zZtmN)=H22Dr z3GWt`a_+o4tp`G!x`vzP7%`|Ak0!FE=N3yutPUt3o^9Bci$p*#D4%%x-srAa)W01C}0U60ytCZ!3y|skZ^E_5>+wr>By`L_!q1G&PGVjK{Z+En2+Px#h~PF%A@N_ zq~ec}Aa)wQGz?UHg<N&sMm0k>tB5G$5OKeb+Koj4S2|2idjm(RGfQOH zF;$C1piY$0q(<;MSS+-Aip=MlY!-TaUG08YIvJ%;?uaF`gklJN=rhPRK$N7_CQ+0p zClB&y#^|g@g_LShb1%>qpTA~Kx8>Arn!_>raEMr<$F~WiOCcSa;u}Ac)LhU&rWc2o@ z36<9#M9`@+q8d2_8$SFI-w|nU6=p%ua|_#XizjL%=_Jnc z-rOpH;8#vQ9$~1)H+-?lt-4 zcvP_hZYDdIfg#g@w&56WbnlnXdj;x=F*Iwb0qHV%NVV^RP=a)`2YW(SY}M6eulN12 zrf8t-^)Umfj^o5TanKkMM5|~ZBNKy7Pd~o3Jk&1<5X_O1X3ka$QJ}&t&QU2y451z; z{gT3|qTuvXm>(Hlikw@2;f>1d8YWtJ0+q9{2^%rZ+X2Fm>iobKo@wENSyc)rT8-EQ zV3a81dX%`B6efN*bb!VY)X!!-Hf;-)CPG5Vlh$ao&qad!YY(_zV!R}GiQ236Z!1q- zOFY;kFk&{0^@q9_=NkE{I+XDCN0ACp>jSMh9^mgzX{-jNC1j|^TrB)iG`vuVSy7aTK%V%`dcr^A;;~4@Fi9AsfvciP zHFh-^<;QsLNrblh;6>xV7n7~>6*GP7NVW}Ap*HJ?C&#Jrv>RNnZF^ty?X5yQe9kvp zLAzbBbq1|MDcM<9se=Uad5rrnpQ}ka6>aZUdQ$ydxmC%r9)HdA6JJRL5QI7-ld^2R zfy@pfp7*b$lLLcJ?OOU5uI{x0aWTUt`q60k2k%YzM9*#Gx4UuPr(}z9oC`s;x4Q_x zq5>&6MFe|`IOV=k_8rg>%w_CyFx6;>?G^`Ri~8@1?8gGrl3oJgmPY{vzf>~|p~*P1 z&h18n0{nauf`EypfmM)UkU&DdFi1i?a2%fyHzd1%fzdZGD<@F?h!EL$F>yPA!PFpw zzA$%Pa>{4Jd_YpFi_2MXZ_2IBVMAU%K%sBh&dyTnZfM@obNU@>$)&YL-nZQK5ruKv z0<;{OoMOj4H(^W~IdvM6u0Hei0uhG%S{TJFE&p!2lsI$#sk&(0+kGULvE zSPO}}Yr#Xim^uw9^mg8PvZLfUy9#%zWW{hcouZh1qk`@u+teQT`7u0P==2n!n$2dY z#1+?^uS2<%m`SUuY`0-jO zylpsO01!}z^4k>`Uok`e%h6vqE5U=Z=oz!ZfJ)X!`1)(%6`X%A`svKqX6w*qFyg-&N2{)k zel6SQEw_4)TXhq*ucB^?LTz5V_I?%~>%7;*;b91!O?#`0-?I`!1K}ZbdC{JbrbXL- zFtY~hW&+~e!v5g4Npk>Tk!Cnl>z|@apIa2*QTh)>dSvJM1%K2Uj}{L2x-b;WdQSUn zoJn4;r(CgT3>Ltp2Ez-@M;#q^yBEyWcci((fR|>xm`S@wV8>_ljaETwe~m5P&kMqB zh-i-)Yf)j(rLCI-01(=DT~dD_wRC`zXIcB24l%k1YgaZujnl94jZeNfJ(J>W za-d%v?_;uNx;^4TN(|2wlor6|I63T8m9$alV<7Y!w?mNqK6X!ahk^fu9C=(C2vOBR z%8WXybl%Z-q)_f9MKP@NW7>_U>=6qaBM{+K%c;LdwY3$=k_YS9-ixn)0*1q(+c&~5 z%Y6*PqnzQxQxcwxOvxyl38&Xm1-eLy7f60xbUn_kTE`Ee)jT3Btwq#jW&js;N8NQ{LG(H6DKAyj(nazi*;Bmllf1jz5ht0Dmx-v9`dKRlXf&oXhLC zP<P+m7_g6Wf}v65&nIFUBOiwJs3ou7J!N>x*?xqJ zVxN2&?E5Ph03QiiU&7(MAk<@F)H8d+@!TR#4M5>?)QBoS{6hG^8lMrqm=rPYDaq`0 zX!Rh2#|0C{Vz%J$;f}QSb?~|Vmyd{iI`<3+fxFwMD<8J)g?|(YJ2(&)Km-I6DC*vw zx_HnQ3V9MvZBS}ui%a(a7R=aG@OGW%owk5{U|l7+Tou$A|4gvdk0q9oS8MZ~A3vIm z!JlPWjjfWg5mczC6D{{@%mODzRDaFzxAc<9g*@YuPW0T82ol)_&BEMP0ITy}cjexo z1bw;BxpW2@ER-(?<#JL)hCWS_VrQiajDW+qRX90QPZ5+jHE`2cqr#{k_)%~-gaEcH zyg`FvutWaI3$>H2YI0H!FP!k8$Ntj7d??9wK(HNsX~pRx7B4Z z34AD~6=dAWa}sV!{Al(#q92VwYQyZSNR(811F4Y9_(KeMrqg-E2t>wI8vsMQgcf$bBThTk^^nCIDy7`)t%f~P`dZ16x z^w!){44MSF1Z@>_WznW%EHYm8-ek;5z`P2f(5`Jbz@LkE?H_*Y4)JvDzjk#W^NF56 z>84zs+7ofRi{2X0Xk~1p-KM5KSB`|&;H4=y49ZsSlj2Kcsg{g8$^N-(IRTPm%YDik{{4I*gidKNp5Nn+}QmP93k?9_zO!J2rI-KP)%5M_3zgEBC?>CgXi>M^I@|9j zBKv+$QwoNX|J-BQEo&|4&jq=^_IqEp20v=@^7%;t+x}02whzwVkGewtzg-1V9;Y0R z8BFk=x3gVW`m^fa)`{lX|8|$U*}jnoORvHJ>u!wGsww!F-SO#pEB$O=^Omt^i^r6e zO8Z~Q=b;WvKiV48ZJ&!Udcsux#A%+%73yF!#F6+3AZ8e3*RN2*Ge05`M7dosS%H|S z*m~W`uUIlb?PnfDB9z{UqS^o|b}ZfHcggAt7%Z6giL5YE@eKZd#afzP;Yfdf)_A^a z{(qj32;%)`p|-zR>0A3Q3x*Rg1x@6dexVUSh!-XS4jAJG(m{Y2MFPAn_>3<5-wFUP z_MLQ_z}RX&t*e_iz5i9=dS2VgTSTvKyHrEzhXzpDJf+#z83!~?br|e z#MUM+FYohD1gR=(Z0arAlmq9WqRxX4!CN=9!^6Yv(Tt8s>ty=hO7Y7(I0{{9n|$Jp zt*xx8DzwE0NAosPi-aBECfB> zo7;2K1+TjUAgmVKah$nrlTgsY4zAQr3e17)>gu}Qh}S8VrnHFt!`}T*-+}ROWrjg6 zuC81$D{>#i#`K`z5`_LN{v7Sr0J?|k4O9xv|2|O9AHE|zzpmTJ?4W;B>G^~103Z(* zu1+Sne_rfBIBo_E5KW#0VTAv7O}6+7L}KYj5G;RN`G0NA^IU8K72QkxCy@6Xd7(!I zT8(eaX8bBV{JW2cE)=lizAg$E#Gg|MtWs_Wu(*bor|Q4a`2FXeBlz!0K!5-9uirI5 z&x?*YV^GZ>F@D|=A4nf8_m%jG^xsF11BzE>PWMOI|GOhNu%i=G5Z>?cfxr^LqrU+j zT_Y8L?qA*lKq)7npr`|ZBJ%%wV#h+X+-&0R1A|B?WmEkkA`l@F2||~e-J}0yx&ZPal}t5e^@e^~VPR!=)T0%Ov$af+VvX z5zSl52!y^Y5VmvjhE>ibc;OL1Tqlj8fS}n~er$8?Xl015q4uavMs7J>3WK=gH_;{0 z8|N(xb0Kt4G)zKnOOB9yDsmk|lq@OTere(CC-Yh)PruTs%6vdh8M4aEu-SS*6y3mm z4QK!DMzEfN(5kb&P?_}tEHm>3TuPuG-(i^fPZ}*p-mT@{`$tv;;}jvQCD#%qMp?v# zsVw~4#W64~4=W2rSr8&&w?V|)T&+f%RY3>{2*sM{ z`G6CWe~Fb3B(e$4ScNBsXtxzwhhRCM0S31@MI5o=E>Tky!_(_D5VZuOLB;Ks*gVpB zPXnq}Tl zOU(=C`T_;L*2shzW{5eNEL6t7*#uE?UpwSu-Jc%t_;0&lf8j9&%D(f z%Eg_SnmXu-OpT0iO5q)C-m+fZu#a}~v>I-;`8oQux=?U-e6)<+0dh2zYux=9U0!#0 z*m$z!el*$O)ftG)>9~uA!KfYTepsK^c5wFc-;bM13~&}4MG3muIchUuR6#{lmS*ntX`N4dp+gTePu?jp>K_RQ z#WqW`u((G*_=!zJImE)E`4F_iB@|<(j0uy&Y$QUG`tK zU??WsFQrMj$qFk3GI7?%JI+70xLETZro@nuDvd?#^oBC4fcHze@h1~F#CTFgQd<+T7RGi`R*;jueNcP=nQwVYEaCG>a}uG(Hc&i8tz zzLyz7Z?hOFJ1Hn!?y9-lsk@DTin12&<`i)&7oZ!EKZ98aai@@{un8yO^2` ztiC}U7#J!uKSiD7%7lk(F>UMnp8DqF6uajIzE@!v@OKg=+1W87_-;*74mSVJN)T+_wQ)Rx;D6HF)?-T<;eBfjvDP(i)su_qV+Q&-&{Qs*O*dd_^5J0w^QaxcOcO1yd2p*mg4wQ$Yp};+A29;L_~12 ztfyEnrGlRrZ5<=+ixqhabvs?kj$P*ZJ_E!uVM&h54al#<)0;*?7OMweJM@v%V3On7 zVvFQjb0I`G2pZi?(1IlDvlxj4!}%qmz^6MevK$)ljtVYPNb(`P9o0$(RM1Gw`e8a4 z2`59)%(cigwh<_oU)bT^Ed{kjdeL`&u93(=vDIx69cUL+9jD+3%-3)LyfWuSN0pL2 zMP^q40kGSvBSiFz@)mz9>?yz90>`*;Qy^JVkD9(z-Ax~jQ&qIl!6JX5&lG|SfzZ1Qt=OhR z4ry2B=*Wv^HI-^mvBM#!gu+sMZltlirTtJH!8Ik|SD`Urs3c~TL1c+Oj6?^G6~-~xTE7)RaSY}jI&yU zj~|mYODz&3z>%-ahL2e#_F((JhTP>`cDeT}kRc2(2WQ$8tFt1fXxTGeJ=)4u;B^fi zy|bvyRcU572DdtNrsQ@!2;3|C@Ohnke_$pIaVe~au~oAjXbv0L@&F~dvaWJ}x_80p zSp346rL{xHC^Ub1@y*9xq?ow4sXa|cg$RPTbv7m_25vx2wUoq%W}l5>4GsrYI;pMZwSNk+aE6vpOCW+(PFsAo58YIRlvwHN!uI ze5gpS$bF|$1rt3E!O-A47_!Qc>NxXi0ivy}BIkmY4f5DY5e0Xur#`DpTL1L>!_fkk zrH8a?(J@$0f%@V#U>5g78O6Wm zTRYm4O6lxXEkG|gc`zi!kIl@ILw)hcIMcLPJ*jCD&liu;2SuEP;^5 zR80Zeo5&M$IIccxr2K_LTnx>J$Yx|%cBxynqKBY5=VZ>#N0ti9G3eQ^MZW`d4*_j& zOI%6u*VAraq&%SoDO;)sZOf74P3w)>M-cHds^E$m%QC+A-0}{~6bjH%2)J)8X%9wB zEu8kWEfEG`h$vjJyg}TNs0h@eYoDT$t4l zsE`F!=FK4{HWSzfpyR%3+aSf~>ZUhQ8gr8yRU8b(4p6?KRZzI3M?Hmd%o_??a2%0lo-GJ{st{%yqI-8~!=a%+>DG_r7F|jJ4@r z-Ce7zdd}HZ>ClZND_HJ#I6oKloWVXHQK!OL4CDgL=SNo-^`TOO z(&(rrWnTr_nX=f1(1S+LU4UyB6Wn_4LqrZ}ze#dguw~6J=m=S6NxT%h%m&z4%p|w@5chOiy5^OTiqpGb_&vPM8O!^Vbm#5Fj1B^> zilm4VqAlHH5lfAmBU%ZYXTP}MnhWoB&+sDAvp&nlhuM*6b7GEBbB+=WJtTCI?4q%6 zuv1e#5)^5z#Vozn&^)QQ;JC^&oF--=aJk9tEv{`5r!E_>AH@6YO3(t$NjS>drK)*yauaqGno9hx5k`|%o5LDuFDH67tc%6U>$9tt4Xrn2YIq zWyfQQmT{?DENO-dhxh${~{x6=d4O!ztT41vS$%4PZEbLNv=q5q&8Y6w6W2bD>+!K4HUM^$y8 z`s|4cdwjTS=$SF)N27yP)Uw-s))|2NHvm%;lZeg2HtnXmSjwpvDS_g^^F}7Kwr{ri z@q5DFKOh93AQ1Cl3P7Ex7vsxUOGru@p;1h#dn*>qmo0v4Zfct4%nx(<8&U#d2FGzg zw83+nQuJ?7_YaT5@?KEQsK77ipQhi@2G93WY7?$IMgK(`jNXBkQ3W!=@r@Vdd3^xa z#l_KGuIAduw$vtg_i;@jEMr#S1s=5isB57^zd4sXDJ-hzO3}&MdTCgn{{M8r_nt(A zrH%}`p|WrLXuRua9RL0(1jgmO^De6`$WzN1t~<>M;`~05Ue_xq;(KT zMj|>~zd9AgO2q(${U=e#JF*l8x2y@e!@E_yV0n*dd9m3VwXt<5L+`2#bsB9V(k8?> z?)k*tPn0n&JkZu!MPS6uQ~4po^VK%se)g>$p#dffii&cguVm-w8`$3PI7NVNaEBnB zn~8N3(?S4&g=z;hWSY;<3fRu2+4LnV6-(;T+>c-Zs?Bt@Le#9(ABPq-5x8|t2zTo~ zvS^ISAE8g|w37cUxnu3|7C^4h!O-f!+Q!%c)|!p=z{a1F-FLBoQ98Ei|2}R4Dn0Puw}Exa+ZnCdETsSDcSc5a3-qZkR?4<&z@V59Oov3Bl8a60>$7z z#Y7pqSh8rx(I1@?e|KX5 zky-ERj&zgOHzmn%_kyBn!q_minW16Vjt;&e;97Z%?BkSym@WM=94B$7GkrxlY5trV zf-~6wlzcM#?M!e*`G1tZ_-b$_(y-~0WJu%vNfPeU#)&h5to@Doy5V(Csw8PhfHj7w z=63Tjqyd2E$^#^4%?lnhWuq{Q$(}t!J2v!UI#aTnh-z_OS%p`V^l0?1bliTbVxoVx z&pNviGOB+}8P!Ni&4<9B_7m^|GsR@XDZpMtnw~UClaT8Vz|Z0xb%T}iW61vfL`#3z z^Tp=C34%o$pkg?8ulL1&_VXEFn)@GrA?xJ-=dG2@4}IB2|_q zM}HXUI<*B|c6fOzQm)MN#5M0XMlV!KE<3-pd%XUfy?j;ayh0mmR|9ro!OmpWS`Cuc zPe1PVs(auCPnjloBCcTJA1_t+=}YU(Wl05`trZsqFbVB|YI0LwjZT${q$larN^YQ(ZPf53RbF2!z-uOvpk!bNiund3A2ZiKxkVvB zQQmp{^$$=~*o`k2Yb21_hg{~X*q`s z>>YE$+$vkf?>V{izgg+CwdWYmSL!P_Z{h`9?VeEW(8hZM@pXSD5--FQ@^(?L#Nr(g z9k5lxPJq55#vwA-QGUw^be*8)PZ|PVC3iND$C2#m4h&_Z$!A64jJe9Wx%y2OdmS4n z85>jRx%XYGhZN7Pj#10q3h|O?I_G81o^9=MBpEYO8F3mZ5s&_*>4FIUbLe+|13u9c z`(KGWdg`C9jbPDnP$K9-Y2H@p#rf5elPk3Met{#>)1w~}*9UL>WKM8s!FhOdS>jm+pCSjJyum?-Bs`8t3ES85SESu9P zc-(pz|E=MC7^6q95CMlAfyq)+q`r^KBIk5%goXcw+33px)n~Ja!e?|OR2rd{j^xQc zUJ!cOeuwJXA1>ZqmlCOuk577;b?j?EmWkn)AGOQojRAU)RR2iKOI~oT%FWWz!Zrh# zb>F?-CJg1bwR*~NTrS9ntUSjc=|~ZssD?>^nWlro$`#M<37t-)?W&r36u9Z5Yn1a? ze+}T$y`a+NbLK6oTmHnPkm&LLRdQ#8*urbR+~{vB)ezUzRfM}=U@I1FB?{FVRVe+- zpE`7D@ku7Xz3^N_GgIHfoh)JQ3@GXMmp}`B0+CcY<8ti!Oex3dFzXPV4}&x;&j=2Tg;Q}oD|O#*PC!4u#0GOq&>MMz ziD{ED-aXz7AKW(qE5ee)NmkJs*iqrEyggHq41H95JvHPtF^Mr!k?gN%{CKA0J{3l1}%DUT0vrvcQ7N6HxH9BVl*ntU=egk`>kbM_^ z0zk*Mv3~c5To`$Zam{Bev56zUq(l+@0hkFwP1pXd5O@qkKfb;4fN;d^xJVU+~ILR zv_+Yohdl_TjFBZ{zp?HN^JmN)h`iK7=VS-N0Y6ZQZvx%Q*R|DQDWF>TxV}-1J~sh+ zFce-T=rDQTn55+0VrAf=g*}X~Pug?W4JTbh1~J2{2lCizpc@zhf1UZ=ndF{evw5$& z5SVc0l59YEg)CQ)^Yw&%ut9I+_@^CFg-WP@z9yZA10Dz{gaSuiNdzd=R&POg>2X0+ z&(4NOnHW2yv3Rz-jNHCc`x~~>Ddqf$si)ME1b_AbLfebdm_j)SRT|S50idjs4jsBN z{Vr|M&!bXKZ6i;+i3w!O#r8f}68wSdG0rqaX{o#~4knSknPzs%< z!m)1m1PpH9`=(85X9q587wUBZO8Ev^`fr<%3YK}JLR($X)vO?PsRYFMh>K{WCyK|y zZQ?IVVk$&#smR|Sw-+^)&Q`^e3`*@ekWH0NkCM(y<@5aA>bQfghGRG!FN4F5@f# zcYiQp$7L&$%ef=ff^6jGHmSak`o~zbAwk&dzU50gqM1VuoWS3ho%%5>iC#wbZl>%7*l_4Mr z!s0pV;j?YQwek8~R0m?^)&j9msepc@aosZP|ZSB=lF99^6- z8>gsDr6LeT^N%h(V=SQZQ?C+2VG34@a$qA(mCNlmQ0D$Y8*Wa6WR+_8Rf=3ewUnsc zEGiGDJvSfcN1^gbXl6jK{>w!H+lHlmqfV@l{^*19YCXicU!L-VM9J>}oE9-V3^m{ro{Uosj!wAarXRF4>`tcv>{ zSl(`m6SRy_V{XuP0#$z?YJ#Cr;GaUPU76RXerCGX6LM z&Bp^%gfRClNB7&)zbqmfhaO559<%A z2t8sMKmgJO)GKiic$J)^6TWDWe*nfzM=q|@99c*|PVpWk5U)Nk0`ug*I<)U@Zbp&1 z8*T{})4QmfeKy-G!+K(}ZyQS`qh0VPhEd+s$<8p!#XT%;U~4DvDzRYhEZJo%@oto0 z4|c0isqYcYfwvB1*MATQ_4th;6CGc^0X0L{vOX58Rj-jTjwRpxTSBl>M=BgglX_Uk zO=8*nayhoUKt$c%V>j~%32-T-iLF`MFe?j@HSh2lC{`UDE|NX?NUoKL{d8fUQ)Lxo zxLMrm>$;?AsMS%uq1r`(7VH|vHYzbWyG7fYTdoz0CvzcvAjq*sF{P^oA)6lFyiKlE zbwTbrh7o(67us>}qRP=P5kQ`0N3U9Z5FPQwXs{nDQOIdWC91qHk5w*@P^OI{M`cC{ z)jV^!`Z17vVakwe4byHiXJ4$s=}e)q{r0{@CE0tApjcj>$dSV0R?bBNOla=J1SF3H z`buF)Yk(PIrF1C>PI2p&+MAdIP69Sm$d78)LQ$mOY!%_v#r`WEi~0}wx6vPJC(-#& zOI+a6XdS=K>?Lyq!w1cdsx_udmBic<=-cJ;jXp`5?Yc)yP?>7yP@W{A1rf1S>_4ui&XoM@NdxJ%gWF=y(Tmm^eGbmp1Ia_LKsH0?}*kM9mMmt7O zw*}f~bUZ85166FXr2Pm;go$AVbba~yLn9hO4h3WbKE?RG%~FeRQ&NW~cR9eA9c#Ib=XNdh*&Seo3bZP`26 zJbOtoWlVy~yhq>8+z$j$ey_m%yyTAcB5h3#{SfZ(wL;dzQq`g8V#5bARk(FDhd@v7 z6vqPf)fd2Uk>ln^GlfPCDh?NiP@*`4pK}5p`c(zDG%rdVGl>YkyIDZUs1mXbjoQ4M zXgpY^N4q~+&whZ;dK!x?-b6rS{c|ZWsbosrerC0mAWHaA&afJObB0{PsyQUb>9X%- z6EF7Sld)Ig~Ttj$B4n#I@H$QN*pE$yH}>%of$k65XWuQ2`fx5_$V>@*H>3Gxu?DI+bvMuYt3a*%}^kM1r>iHyWZW@5y^xxDta z3ut59xHoF+(4*uE>I`6inXY$(jM5@t=)V<}4a!>$>8Yqk+{gEnXptYlPex^irf?iw z!KqKT5>fYVW3oinxMZl)yS|c}#6~0)Zc{fB>u|<*6K@|_cm>4Z7b_3r%aQ{!96k2j zzhT?BP6$+PT!cU#`)#Kx>-7EFmV>z@8SlhrzaH;Ef4fu>|H2s+0kifqvRli=wT5w* zdXbbFuuac0iMCfezZrodN2dNm#BiGSv7uM7?~(b3l5<1jXllT3jrPt+xmJWuBH^%! z8?o_>J;P&E3)$`pK6Wz<03tm#w4l3!vjkd%54#^5m*aO4!%6Q9y z)6Z*|c+7y|0vf#;m-{dSl}izV%d%OAS>|7UffHhY0GR#}8ad7Qg}BEoEj@DwK3~)> zc|@eQ>KVEcWpiERsB*cHJh}8!+UZ=8xtY?OXi9R6U%&K&#}qjghH#n*(iro0MVAsW z3-pHczUT!gCi_HHutka^zJ zEHVIMXXr^i;cS#_pdtV$=K4Y2&VNG0gi0$au3LK1j!Oc;$!f{ID-M)3CXvO(yi4WPb?AD~}QZ3YDGb$UQzioY4M0tIA65&n55S~D^@ve|K_Rqu^m-i`nv6p{kYD#4Qe>{3gcqsnFkd7_4s;XWI`Gj&j%TV2_f04VC&OVSL%c*on3MX!L za7~On+PV6J>r#b0rcE32UES*?*CK}5!>CK>hQc_qUtveU)Lcn;#Gqke(fxdqm~74C zaS)B%sJ8}Gd>_47QsL0tFA083qfx0cDdWrYYavrD90~(1W6{Qb$|aWS0_4oYoZMZP zN0b8HH7>+22_(1Ozm;>dSu|&`#dM8Q99jJ#Xa*r91AqVOck}KOv3;I#jLDu`D*{Ai z2o7H~BvZ)(H9->G0sJ3OQI#t8I%*_mX9(J|^9@eu!r(%YiUjPa9VZ`CW)4ZD_E1i` z%o&wT^p7a-wh$DtWEX?BJUw~O%8l0UQ3;<3KD7CX}J5p34GAYv9U2`zG49gl>> zYOY;}B)HLr+)!7SueCak7E3%)RM}~_nwYx$+G~BJ%@-oebl3b+v_vtpd&Tt}C>?M8 zF|o5_(xSX%>vbq322fL0x&3R2+qi>8TDCQXJYeo*$J)tqAGAdVv0p1--_+38LqV(ESCrh+mZ)LvO+QK)tuf9Z!!by>-oH$}UXQ*z! z{QYF^CY>!MUzmXeW>XQ@@Br=~!n9){jyV9*v5T*TWZ$puU}c zwVv$xHDgBUHRG8c;p3#{;)c_ssigWFmk08=HlcPOjgxMcg6zsGt3fvE1-%H)4gu`bKJAC!y1`(>FwqT8j0}-z zVmT`2*U;F+%h=0L?zR%u<~JNVCMt#jg{DSI`3w@&BBl`E!!UC#jeh=lZ|$eoJ7X}R zVMp{I4Q&{>PWvk>gmO#f0E13Ntq2KM+e zN-X-{%;PM;4B$N^g@Gdfm1e%YOEWW@e~bP3y8rj@Pm*^4_IKSR*?)V)1@?H$n1l`W z-=Px$hn|{-iuqr9=JuVQ@!E|g{4ZOPN(3Bw+>Ai(e|!9Yf3i)L34ic<^LPPeWuGDr zSUVqJwcHMf(LV=$(j18UH{De6p4-ZNB&nP=mhbKL1Yjl!~St5$=jR4V(~`SxNBe2VX)_4O}>1Ruye5Wyc{_Bk|> z&S1Cy#Lmvn=6VTKnQ#`ub^?`HO*@I|8{6CdwHFuieboYtr94 zJF*T;$xO8W=!$*KfeFgDmlzw4aNuybLdwdhqzzo$-E)D3@|Ij+prLKHdZDBPuJ@-v z+ud%=5Fo-LKW%Oe{bO7@Gw-(S=4P?X7Prx~9mzRauFe|X|1Pjz_kQ#A4}=g7ww&ND z|3_Es8w4~M6G-X|b#cCY1y5iWq9RF~sY9Wy|&+TFoOOXPC*5852 zk;iV5Cu(XKvJ@%nN(P#hpW>g}v7+m^vAnXs(2owhn6XaW{$mMQL+{iehS}5MeyVjx zSKyoRnde3cMeWPm1kpa9?_za~>@(;p9|YhY)+SX@xtBLIP7M_Gwd)f@0v+)eY2nu2 z($eyilRDq1?h}hfA>&L5iC?=?rIeG=r0Aeo`N7*&Vxe3=!?SeW9Jj-#*|Y8Wh<<}B zdvUvwSaj!|a9cZCy!W_OJ6dG5CLkk&e0!Ylq=Gv&=6%A5#N~y9fPi?uZS1r}3;Bu& z*!khYz!md|8wTI85G89#7Amsym@Vm~|3)?8w743BmS8M`&s7a6i00+<5|hhOV`>!+ zMfZ{9Cs^`_FFGK1@gKq?-NRa+91;@}z_qotizBxSGrelqsvvM&_)5;mC@2I}R8)XM zvao%&PFxwDJC>WX@Kx=+?%MM6z6Y6&yA{m)if2Z-LRj;pRe1} zu8@pqWZ<$Mh?LKtUAnP+LeLT+bkoYoE2>;qxrYUGeo1KZpm}Cty_Nxuybgd0i`t8yu|&2Bm;1RP?y)16wv?Gn8cY_A}jR z=C1E?zPXL#5Jd8uH)mC@K%l99T69bk#4M&qVXyvbs(k)+MbMS-LQIyqi~}fC z$r)eOzRA48X80e|_s77n@P6j>d`g=|kzVQWeH( z*S-3|_%PJ$Fo%z$Q0vav0>-Sjj91okp7>{pQk z#l5=`0>k}L#|0MN4^1A}69JX7LIOw>dKtZ#({x)NZWdL@FZTffA@_^jv-%;|jsmV6 zbEX!wvGN54#bi5W$jVr02nb9_5qnXD6dov{2&jOQ6e15qpf&$#XF80)fh>G4ZAr|2 zcz8e{mnTRlE-oGdGCj4mJxSVLIp4XU4OW?yEBTIMo8GO(7-I z?JDe0#`!H|!*`1!eg9neuv#H{H;ZPPp2e(ndY2DdM&{=-@+!hqT%y0QXe$YncJH5` zAMk}2J_ElAmhF-XLlZ|w$E(CO}4El43Tmk*7a&N=qlHkieR#kC@Mil8g+lL zFQ_XXn=xT6?*9~zn$|rhLi!#0QaFZ)V>TYMUYcCa8WGUa?U)WLFi7-(I@D_VK+~Ja zDypY)R*7))J3QW?FDKW0%&8&hZ)0gv2chCPP1J#J*Pb;ASuLWVq@=g|E%q6MAy3gV z7y%oNR!hj#lze4%HGj222zD?Xn;XyZPOfXXCW;5?xR6C&*mRE&$K1D34Wv-**L8lP)9e-U zIV9|n^q_4yiiFp3l@nxuV4;rviUJe%x-WAMcgZP2cc3}PsX@Vc{a^b}GM_TZ&zxLJ z_rmaHr=wrg$Y^Mi;OoGUk5d=36Nmof0ty} zfnNj?lpK81UG)V$r&6J?{z0kfe%ASAnnb4K|F+RTww@Cr;7HQ5?ipsl0?wNp4v@w( zd80;>Xt#keLL7|;P_IsBv%&Wni{-QbC)gS_n|(JF0+Bp90FQ^tpQwn3E`-lUSdd^i&tj zlMQ`4J~=sK%O8$?02VB2vfF_ciNdk0_S(iF+&KMbe+2p(Dg>RWt{MNo&F9Zg)%Png za18PncH4h1(7R6p{-?^dbu>ET!f4mbFhYQ^GXOJa{yh3atZqNFa`_>(4Y1YkSW)!QrU|D7tXo7)OXAF{* zORl_~Wqh|a^o8|6MLkLVYR?N#_PRMSnc=RDXp`^GXIxkkwaz(NXD*|yNw1~cfxl&} z^w!#duywPj{wbvQ8k&_Qx|X{cD^GK=)sCd)4OE_iqOOD#5r-{>Bbbua|4qktNZ@G6 zrx+nkMq-dNgVZ35*;dK~rB|hYj(@D6p;&2Cg-=+wD7uWbU82N1qdq@ikP3^-jh5Sr zaF#>Ms;pNs4Z9dNCqLJ&0guUu{MDfM;yL@KceqaY2%tKzc0;zjuIw6D))KtnzRfIP z!(giW_1a9T->^nZ?YxF-WvQx__6#kTLJ)(wZEaKB;K7PYva5fZsBpg?T)dLF>XGs1 zqK$q5u3&901QJHB9(>pUXL&eY97??u;8~i=X}HzhbbYEjDBe4 zBKu+wCmuVY4n&5iK6*qx{#ZtR{d%K1xx#G?$WSBGS&q)4o~js!dFGmkr~vK)dgu@oy+2N4I;Ilp3I zcU(EiZ}Pr1OneQrjiFCIj1D$jzQh+Hgln{wtkP>K%-*X^(j&dmRv}!4N>HZL(-YVf z@cyN+i%(9BrFdfpGj8P@qKX*V#WW5(!jN@&$0zKCPc)L=j?m8WsoW2v?t`{S4k(UR zRi(*T-l_#LOa&n=JCrIh^Yr?$f>wwI66o8jNVu_dvv7f_B8AIhQg)UAPOa!UL2uA7 zh=RN;h`9KVqF4azO&PjSWQ_c2MWPWQJu%w!bryTpZHXv*V(WWs;RiA^?d#X({ysooeR)n7Nkzu8VC}MWw{7WGe922UPMQ73ooS33wSqrj+ zfU!~a4bCpU%<80f|0b8BE`i^&KW^`i)_^e;`>?G!tiCU6(zvicPC z(Up9#vU$WL&v)Q~_74j>6$e_--8E&@Fw#duL<=eor?~yW!nxXES{U!DtG~1WpKC$V zV=`lcVvoFBf>Gm_zVro zt-f9?EUZu~kPf=%1CoI$hN!JP%J-8aZUj-pST{8j;b3qO=7+isDxZNe#}L>ee&DGX zzvQ`lSKve=YP60@<1Rzex)4@gk59^BK1FX!_Se^j+4;~sGaLdqxb_uw{H}y-&!gv=Tm2hnBkHuN93H z+6Ou!Bj?W6y@H2?O~{?X1QgIg{CKECcl@S5@@r5j22N7HVM6VDod8;mJ9t1^vlje0o}=MoaOxa37-Q}p`j_^`dg8X#~O+J>;Xi4t*G@C2+^n6sn)(Wp{5{h>h!CPVv z9%t7VKg1$}+u6SUiUi-+&08)UjC?@H2)_nd6btqp_l7-6K+x4%I7TX~`7Vg-PA&4t z$P+K>imCfo8^j{GngpT-**tL!q!=E(>IrL-P%>}H6^o8#VxrJER_vfDyP8n2q1BOY zCStIMu_xBZ1?o;>=tEVgg(@%+lrWs*w~jMvBAjwk-HuOvMwyv&HsfmHAV|%{)&Ueo zngio~mARurC23zBgyQzkqlJQ-FR;Q;T&)ft#1R3p6XFgQBlFSGumcLb-fZh-B7Kgc z_f7M|h#WT>Wl&dmEw|STg}VAm?4do6d;A~$?)gI} zuOL+r0GFwDu3ghLKk=s!X+`^^x9K~14! zb0BKr-HFhnEc=PDvzT>O5B{8~OAK8Ksh#n@NqyDKs!q%h3}n&cIozV$EdRmG^j5QF zLD;uRZ68$Y_*}elywZzUWqK!|CLKA#d_+8%#B5o0W{Ls6*9iP+3w5hVrMD7&lFIG- z3t>P2J8s#;W)Ei{}I1Qa-3dIiENYs~c6~QfvsNO+x@$cyKt#zn?g648xPq(%y z>>0hr=?Vlu|CJccBM-GO=ol~WGF@Y{>~{4PT5dcSu9z!CP?!(7+BP>7BNBe{I2gw z+O6`J?kqVhyAsx2>Y;FPEZ;>hg63g!nE04ux5@krf0ILLK(&P=fl%#511%O0HC5!Uzs@56B}dThh&vhekX}wYoUKG=FS~mU z1~lx8SacDYK#f3X^yKpM>PWQ9aF2y_aIW(WG<*3nQc12EV zl#B3x4U3mUYtsW>gR<}+p$glLkmbrP7Fye+aFJHru3}g}J?IBt-DahW@&m?uI1)f_ z4e#g?h0_2&g1_TeOgZ`ZYG$Cb6+h5qvy{Baq&PzKB0YzVi^f2+~6sOrrvg zfSex2(MorLp;!*6fe+b%yr>e@C8<|cf`_P7UEpY}oHc3{3YKf41raVtzE`e`qYlmR zj4!aS`-*%ouj}=8G$^qo;t1}i$kaYIKj*;Km|E=R)yagKBtU*L^dsyb*LbRtwFFMz za!D3RYNVPx3tiUSURU%6r`o>o0}A^>GJmtmxWY{J5LtUk1ihpqD$(b-oL_R>unD1H za>FW3vc8?su3=>NK3vzDS*oJ+fvN2l}!Dx+ES-|yi?gN7+dp+ zXjYUfmqQQS;StZtv9&9~KABlP@(o#_V^i|XnROU+lu69U^eF)|)oKM7(K$tikDton zt<4fKoo^}-4514{W+_x2x&iF=S3YqoyA$}DWl$7b>4S$JaTt)~eWOO0BaE4Nk?EUi zn7xHWP33N4&ppKDWXTevAZNUU*TEF(zHYvSA z=-i}x*87Eb?PVdon(JMBwejP_mnv`|=3vn!X`w%WG$~BzM~;bDqP{=nm$;^Z>Q--& zOWCQ20)s6PXg9mnI}STY74IyK6#N<~I>xK8^s7ig;ZAlwE%7&?0UKsN*U>H&C4RiF zgFRi`gv?J5ox92a^yDzUE=Gu7k|D%-0qIcw`x%8(r!X^a z9cMzi^U+y!2s}c6+~lNT)S|4f8 z0aZPX3*wHo%95abZgIgBfpL43+A_ZZrf8N01la6mAlBTFBUYS<-$!WWrk3J@u2cg5-Es>X7)ETh6}jaJ zCB{y17QeG`_~{8#LVS9zT!UJqdkJ|E%a8r%7;F&|%=L)^s9-DikGpC%G?|G+t5Xg> zL+3AQVMQ2uG+{>hWI7#09Bn1SkBK`Mjn7+IgX>%MWHGFi{{F)ltVf4K*UktBvf=6G zacaBGE)4_1giah#IFCj9%>V!kKB-x6nTi zo?t-mfeSwQCjfC$z&>3<;)x^WRiLTD%NM$qu$WFgck1Wb)M&lPi`jviY}w)dLTrWN zSD1(183j?DZ)uiYpUQ(C^FU1OZ&lGts}$qE9^KK>ur^5MPBpX?yg&{)wX8G6u`C=q z5?JIIO(yl59?o)9k?vD&*5lN5`E+vKk$akBoP_L3ybqb%3Pj#*SmEJIjiv)t?{Y}k_GjFqZ6zwu^1oR6R;Jv8Nh z8NcPs>h<^?!yo*BPdV_+uX}`J@YIU_5%%lJ$jANw=FW@yUq96-baXkwN|AB-wlYk9X9APzl|1S*X00Z1sS^}|L5&oy4 zqj$W?(X%I!{%^YEk5i=;KhRG#+7QA6oz}mfIB32%RAIfq@Lzyxk{TH2?W0=#=Ks&n z>3l0Bfep>yz^eEEjRybmix%*Az^yxi3-W&&a(u_MS~_$`{sXQh^ECn)h+Ori7>KU_ zX@~~ckmfwA1;+o(9qRks!LVN9{ZB*l?||IVV-U6e-<-`Kzxe-b5#0OnDzw_e2t^|| zw)(>FcT=s;2MMEeCbRg_Ii0A2K4VNX;+>NLoxxv=tK?z(m70>Z>4iCKz5Mc<$NO}D0pDxpBeI{kpa$slC$gtOBFCn(?}w?1eQH9WXyuV^+0&L(S8f0*YCf!FyV6h*U~u9zb|~Upv4q+ z%%Q{Uv#SR1Hs`wMT{;t(=HX10ZqIqR_H%cM?%c0m1YBGiVOAE4HQ=2OW7c_2R;w+6 z#FB{{*4|H8uiLz@pvDs!fnx1Q!RhG$r;{acT3XuD$KR>{StgDQpdm*ZFXgIHQBn%J zx;7Ir1i=BI(a_LLp9SbNnr!%y@G~+pltO*j?RUj2EtVT#J0IsNQ<~SBYy~A6Ioz(v z*4&P$$saG)-(J=iIy_s6z6HQ~zg}ftTxGtsi9Yy2{Ck!v-orBGoA0m-`CqU*UvDZe ztlw^}NvK~Rk18*syq~ut1B1oV!@|O11EeG*!iG%5g@wUCeMa};@q859A|fX*W?wbC zIh?zHIrFv#MP3y8_sn^}&s=8|r(pm=zJw4O6%?H20LLc2T=r6`8L?2$W>0WFiBh>* zV01J>?KVQO=GE?)KN;IRfS{d%kg(^N8+I>Bf8h1%Tk$o`!3h|=HZuqpM8jCw1q07w z2LwuwUzJS??IZf?luI+D`z>~G+!>P;3}(>fiWD|gdtlZN#!b%2`@@FTZOVVmFfiv8 z(4=baPIFIkGPZyg1qlt^4IDUac;fT1F>lYs_Q2-GhR^!zZR1Ie+vD{CoAYU!gQbTD zSHahGvr8UCVbyBAz`DSSSNt4Y0p!pQX&^}}3n@dz;5zO+1R<8rJY16=u@mCPfGsST zw6XB2YQuo~Cy$HxZR%mAc;So{b@?FW2QIeYG1+$Ma$o@MKOAbZ&lqrD(ygs%?F=$D zH0+w6SC;SwN(nNlFt2`s!iycq5C9WLP?c#00h3XvAu(^=@6dXaqXc7#iuX1&=VY~I zW&JcrC6Jr$^!AS18$Z(y*jp=jQeccp;edr_*68m#p1VJL=K#kca zJC7JZE0$Lm4m*6gEmfJ+vv32fa?6yc&6RY(e0RFP_yhD2r)aoo!A^LJw&7w<iF+rg>MWgx`E2;ycnFjpaXil>#%rX|p&HBG%>;se6v$catU_D+cb$@)52t9_oD-QCy86Dy)g}@@NcrZdqaE9 z7me*&evQuds1$MFi*g`O+0seWfm1Ddex5nU$k&=;=a`i**ih=?M}|6~H)Mof|7?ba z`=5a{2>AfBv$>YSET;VULGH;aD1;-F*Q22sTvUv3pr{A43RH@mB{Q2VwTe_b9w~WT zPl=XScwY3O<)>G9-ETs)U5#^2vvNuSa()R}l$xKK0FUo9S$%J4)+HAhP=U-u@9-)@ zu@cABreuykLjm#>J+gT49idMS+8%FckG(nB7C*V7s4~d*wNUU%A_AbPNAH!s&?c&PfWwzg`X~UjgnIbx)_67jtuSW9qvtEiEjCDhEP-R`X`qd@Z3rZr-%O4t87hSxs$o+U(V9_i%Q+??*taDp6@@NqOT51EP89>tWFeO`|g zK3gAim9}_j*xMCavV<_81yK2!5I}vPoD@a6@gv`zXv#Joh)RixX0jmx(8L^w0DKl7 zEGL%lQMNKv!*YU2w)|hJO$LJ6PmBi9{tW5<@S9nb?-3G8n~pzA2qtVbmZG5{u}CB~ zxhKj>Q#UpB~W+-O#Ee?UB+38Aixf!R!-SYA>At|Y-#03(kN~pDa zNM&@m-wiO8;esA3H8X(HbY$6-1iHyF2F}cAUx+GgDYe7J>zmokH-|}h1qUx9>*MUJ z947s(7SND8D!QTr6Y)xppioCnI`W|2P%=DiB;TNyP&P4w00CUG?>CmTI31Dyim`zA z?16XHn@Wx_D1`WJZL8y#h#Cq|WIrcd)^j7eB1}b>)+v^Sh~3}civ_MbyM-)80MoI_ zKaK}q;7^z6>FE{uJTaPzQsR3FIS4(smFE&q<6E1Wo1ra#S}H$m-cu465a?7{aVDN~ zkeZrvC^A{z;8;RZIh}IeH6rT!6#6Y}EwNcX{t-bZpN#0vAtwr)1kWP@0q2t5VOYXK zogMah^=AXuz}`R6i8_#@?Llq-6=;Fly*-^TiJ%SM>G8nO@pQ^L)FO)G3I+-R*9(jf z=u9T@O2L9(>3CnRiQ09=QGvu7Cg<-`;x^-0acp_%F8&j8Z4J z-(%4Wd7qE}6V#5shmiIUc1QmcO%DJM6I0uTA3*<#-2Q+-bwJYJgW$c(onQY<+j~!Z zQKZ0j24p^d`oG`()f~rr7(c!Jis@*q`A-`(5tFYqOc$NueJj2in{ZI+x_dUVoG9j$v8ndsh%z?^)?Q zbA?9uW^%+Vd#1xXGeVi-n498^)QW|FIUrBJ96h|6Qp)6$P(mr_722{YsPsw&+-R)B zN^~T>o2^YHaE_Yhdz(9-1o_k>^X{2uIoNtOjR0ncRv=DrHjc8JyJK!e&qjg?N#AF* zrd+IjCtj+jgev=nWW~rY`ZXGkh7yZO!M9tN!UwIC_>2J;uU*J&h zRk*`g9_MxTC@Zt?ah_!4(>`OS+`LYuiOiato&#%0={=w1)5n7Y3EJ}*zCznLRpvYW zcH^^$^gd2)ehJkN4HmG$giKa`^Odh zcazvS1)gJ?IK4a?rT@hOPj7wYF!@-Uj9Si9w_*PJLmF;8VCw%LB3KP<{Up2Zv=7F1 z*c@>!RrIQ6rU~Q5zR_sC=V@dn>HpW>TSdjywCke5Ex1eL?gVe#-6c3df;%0g@!;-| zV8JE0yIUYwW5G2z!Gi1VeBZa$|F69-_PIG{oQt0eMvpnF-m3Z3TlPHl)}%UbAZ+R; zRDi@yfUShKy$@pePN}?3qW%Cp1@mVg^Ge#OZwPtHaQOWt*x;z7Tf7KitnP>UyHglU z?fm?zNW`6(4ViLmpr*f&sW&x1;WI)m5z59!n0{?75%+=(b~T$!UmcIl>Z*pRZW!>u z;BAP^@VgFcQyZ2WDko3ezF{~f;=8kA{uQ(iJEkW_bOfC%O|c0lJLza`41>Y6)8)WI zRDK7rMO#B@qotJhnyMlkQ_8P;Ei)s_*Tuk}W{XX}5s-*}#}%B4PbaqfHAh7Y3QvUd z@}81A_%WY8skcmh;Z60HR~(Vm>0J3(Z%Apb8)Cs3>)v1Yy7W;*J<$|-#Lb#Rx;jpx z{2d}&PI}^gU^wA1ivhPSzu^e1qMV|tq^?5jCBINAA$9sdPj`3X{#1&gWrl9i$8ioB^Pr43*@BMhCe2ueM%7lP$4ahm)kp0c;b2FVl+xje?6c6z`oHm7|U{mo%*;QXgrA ztl`6ksfBxFGUAID1*vV%WsPp)he$VpkhcnAN`X=fBx7h;cm{E!9iWVIgo}>65V`ms zmw^zZ2$cB2NFAss3rjKgiMgLV4@06#re&tGi1p0V;EZ{OVTAjUHRm)PCh3KaUbRmZx|hIA zR&?6lO17{yV#q&Ya+Mr(p!)F}#c*~JK1HUcc7H)7pXQ6M1amj%7%E8CUzS9%>R}UL zpl+u|XQ3mDvI5)BIk*gi@R^vgLJP8GBjS5$>dN96J(x1wf*dk+fAr1(ar6*?bv~hc zt6j3)=!h_lyxIZBMdu)VvAwM^{WHe3zHc+~O`-Mfc5ycUd}s5k#)IhE*tO#*naZJ$ z=nB*PB(uWJRs=tUQndL8N@ z7jV9<@c+#pBoMUFceabH$@7^Q6PZBS4=0se~pIuYZI^svaz0fQ6>9L&!&Hw+FT? ze!tmvW2C6|oBxuM3u0^^6)C4Lw5VCmkYplZrJp>z=k=BiqZn&Bvq~?AB-$6dEQuPx zF7H2xGb4cW%CdD7)179vbn-hbc0sSvMSS-)9zpeJVA{Z32bS~f&P;0`5H(7}&fo=B zdeEBb20>EmQ}CFt!6%_ord5V%%IQ3&)F-kgdi-tUDvQ0X9~$z}%RO|<*eAB}$1RLM zf#FoVvUEi`-ZIsJYO61lEJutyPX5T~ucdQeR1`p;tc0OH*fd6J3ue~9#XKBdZ~5{{ zEf}2Qmf;?Wz@LW^Et^Z4&@FUh6EgZ=X8{lpcun-uNu+*5goP~GWDWAr?#x4JE2_Sz z4jtLP|I~OQ5(CL}Te<)#zGV)C9zS6Ipq|`Ks*lh({o%Rsqajz(n?w#g2o-fgiAa8} zRY%w^(VgLTm}}lPmwyMLTP4`)Z2_x^_xwnhzFnuap9&@F@89S-gdD$mtuhfaeJ0YJ z!_`D(%#>XX31e^y_Ll2!)mlBLFfDv9Lja4VEC~JjM^3^m%PvoR8*;m4DT~NM zS<2)xrdL5KT@6!LEQK;_wiE_gA&`uTtpA}qyUPC`y0cvL=0O7yf!wbHzuUrT+~39R zo7yH+mE*(x_0oCGk+0z?+iJwHCwC%^jC~rr7e5g~ZxUdjB`w**vn= zyZD?IXjGFCGWu@Ojg{VYM~0W-aH|o7@u}U4SC~vZ`rM#!kS>3P8x}Gl2$~>P(p;x> zWuz$aoR)0YyB$^ssVk$B=JQFCc91wLR~B8?@i4&_q0%n*M*?;%TO%3M)lyTy^!GV( zhdPCCv*(L$nO3yT%4^L#%**<`R2oFK*y1$!o$=c7AZB$G1#-9G9VR^8a!&skhK3*Z zuFNbbpF?R!<&gy}*|@(uLAo0$M33utmbbcJoS@T?|A(T~f8K?l!V*Y_KNcGiKyR}nbz_g{-Bd<9Q@Q7br~iG{xX|oCb+Ni@ zuJw+X?cFF=rys7bO>DK{rJQ=Aow)mebVP`B*_nZ{GMeRk*QA1uIY-BSGOyH5=PvTW zcfGcH-`H-TL9)!5Q*A4f(K8Po)K4}F?%=fjWqo930|BMz_c*e@e+m=HfIjI38d_e! z!hRJ^p;rE7t)zieG@0gOKu`jNK=@|n(cC_q%L<}Sj}q~kTrdw+!D0sIyWUlH&RePs zGewPB&c+QN)&MJ1n&mEMfrVrqgBh(V5o)Y7=XK;ch+VbmM^PYmbl&`={z7Os(V+Pfv+>E)E>53#7K6Vy9Tt;XX z79}FmuhB*hx@>A!x2UWWP6nVyw0wA9;rm+lduQX}nJ#NxZxdzFBU+E+9THmo+$-K& z5p`wr9;4NCI%vBz;Q&tDnGWZ0s8R+~kz}SGix|z?bL!{o(*pYbbmo1{KT8`X&l;|q zkif?L{`Ul(#DIcNrM(9o{VC33_@eCK4*v4eE+>b{?5G?1!lW9c?nV6F(7O#4NQVl%`i~GbNODO=Trb0TKH6!nuo$4g1FTifRhRtA z)D_^80t|epi4{U~BG9V}LmjDRXJst|IaL?0zRAUb_BqA|{KC*AMbr!||D{(uss8VJ zbtFQ&Dk!9JWHw}Q=`MyqiwGAfw7th9G^6_wrcY^5q6oLAc44Lq0D;7u|p&5Qw9g|{;AS&T zQU!EN(+~FWEvdw*p#&EG;y0A}qj1;2*xOe7S8ud_;$1`637<*uXh7X&O!sMf-h~E) zpdCigA)mVqY&O7?6_NdVX8^C(=9E>>j+VqM#H<{TQexzFpMzY)Ij1hzF8|WHmKBB_ z*(Us(?-p->hN~OHBqf|;bMK?1VkCAWvZhK*u-KB$%_HkVzS+m&{H1Vr1~=4bX@+E#eG&x|`1uqZa?Te6 zLVvgHEa0ifls2~36oBIwTD1bB$Q;IUjaaP{5iPPz=pq=qip1_9AHg|lotk1a&s8qm zSb$uFZmjqqJse_KHEWz`&CYP9p`h8cJ8z2s>Yzh-W-g}_)UQlsQdW|vb6xxQyobZ@ zB)|35)l`#wPY>zo3fq-ZB*6C-dN)r3#=tO)0!4VMt9Vovkx&^ke})tzT>ds!R8V5l zrZV^D5k9t0kZDRya9pdOe-~u^IVFFG%ZZ(u(XpDAJ7|~8Pfjk9arCC`-F%XK%sD6x zemYyM{&d4?`pD|^chKhVEhB||C20m3u)b>6=RF5B=Jhv|jxi8!FC7kZ?aWRXQup#w z69)oEPKj<9j1n#E0Q41UgY346Eq7hHWnYFVPxD=;C|>NchB?zI2Y%?@Yc&&?DqyD-8Da|3* zI6H}N8n%*-_nVLd$`#X)Cw}{Seg@`J_UJ_Wm8bkb_~@2`&HkILbC|9Elpo){N1C7a;zpx-e0fj38vY~6w*GWofd}{Fixa*U5STpaSL9Ra(q=g9zguc zHPza`NJI2T`Zg|&nJm-xD4n^@B&v5 z0#9s(Qy+0X8iIOtdHIqkd4?C(`Z^NVdN@p2*megubKQo<+rXu^F^N2~!TvFJE_Q0a zq7i|ocuJjbmZKv+0E~F^(+~jnAdLzaN_4R%x-mqVJ~*ArKL-@%J}5(g6hjz zczAhja4d=&Dz|QA9qrLRN}5 zNp@gz=ZW_+ZT&~313CVodYS?j+Suj9(Z)aAw|Sb%&6wuS&vB@mdC*oe^OB$+1ICT) zdHC3jmDu_rwRFZx&+IUB*hH+a*%or=)W2xIa-0xF6ArD+2N`q+*s3^Lrpp#Meo%Q> zm2R4OD*oKit6Pn0hRYk=#iObe&Q$!XQD@8S6j{WRvDtt`%E$1u<%l&_18D8`+VRSn zN47U}*VSjf(VMh1utW3{A1`v_LL`+bmvfn)F;c!5u6r$8xyl#kmkvA62dgjKN3AI5 zj?arW&F_8xq8W0(|6!co8~2AT(!wnD?&6<^9$?(wd+-*iR*W;}U1p0rl#Kp*IMWx) z1i_H{1t8mu*4OgUVSeDx{K*z(i2txo(cbdU3A=rgKeH&hSPlMaSRi7__0B7czQW7W zc0s$cz&jU&ISFNK!}&E8C0`EQeP3AWBDVLQD2Syc@7gZ71}9Eaeg5I945^?4x32J@ z$G@?te_}C6p^<|e`DC8 z!^w&#^nN*vj#IuB7C4|X$c87w=RNmL|G#AOKd$bO6If7CFdm=R2?y<~CYrv0f9H&$ zt5)a_pyt4P|bXbcvk$^3p)8(*}4&n6!Kr#qqksNb)FC0YM# z75raZ@stIWNv-L-`eSsc`F}9ZmwJDSauI|1C!D_!=<^2440@7;R>a$)2DQq(~>P~A>9@IAx~frqq%?pTi=Lnpm{ zp+;hTwQNMvSDE}cvZA*B~3-RhlU||1h5w_ULUK{=K_t4xU zLQe@l9EwJ`v9$%8s)!&Y)b8UTadTKzR)zu%yKI4~GAk=9Kou{jo0+dc|NSw0F%|B- zlso5ev5IxOiEw?ldbaKJmr8N-QT)RPUUL6ivx9MA3V@urbR1O2_IXV%+_FtFXCDa# zN>3k(*f^rI8N@7DwOuJ$AGAUz-j)5AEP#oelAdlcTdF~}x&LmSdh2}dt!eMfOntCn zTBZdQCS53A;`P0Te;sT&nYJVVc%sy-HuLA-P7Rj^ve2tx^aO)%=J3e-MwH;N?kgzn z8`?XY*b4*XF#+QJ-$sy$mRu{F=8i;!*7brXF#&Qr&9OvWwurFdhq#^Ax*$8bEBs-X z+Zfy&$H@+xuY-ATGDL#jZ`z{T?V~X_mB!_TNjWds1$gsn&-tLo8y*E$^t2-4)l7PE z^E~^{RF54cW&GJy({v@>+xhP?i9=B?+c!11^|h=gvVy9sPmODS8jfQ_Bggw9uxWq@ zSkQ*5`KO`!pn=la9_jS;`z=a=zq+0fG3dJEN0-F|*7vx$dy4KRxG9|b6QiZDYWMX8 zn*Y31M^JooXZ%nua!Bl9i|+E_l}WakrwyTK^Ij)d9=-+Ba|{OHjKi?yBJ{B3ra zrEkBWhfxPK05jL)xsIyj+IJC(!qmIar?i89rqB?HSoiu70)WXA zkdEgDS5|`1wLwx#lDBq>>_#8=w)NvD;yuKzI~tR}u3j^9P?5<|1KlcU-%J1$iv(AZ z)F$HxEa=0QoFJ}8NWOC2*5u%QD$rVv@;vNe#!W5sw#9BJ-9r>7Qj8Cc#~+#-hkFlk zt@f>AeRD66*IW$KeD6I&IxcQoYu3`z5!5Fo$XMDzPdxv91dwcfI4#1O{Xj5Whnahs zB->WKj^)W?Tp=xn1DJ$z8dtN!LA#>jO1L+WN%J~2rOTpFMUjj8dJVTSiTgyYmuH&} zF#1U^_E}3paB{P(Ijy2=(sAlq-8LD<4w=Lb@13Do=R>Q7Ci0>h?9s{|#io`bO(yPL z3TqV!%NZoj!UZ#OAdq{pVWTY;W`f^k=c{k^43?>)AR1@X^GeU&!RdB|0|wyfeC+Y_ zzNrtYV0e^EEVaV)?dlxz=VQ4io~;Er3JLBvuNdjuNf0%pnPzjx@1uMz_tM0FSO(lc zkh70zirQ9N)a8E>F#fv!*baU}|8SM=(6YdR%pp?VF_=E&1`n4L*P|8R8-uB`_$KJl zd5JJhT5hJ<+GV2$``gJ$sKg0-i?+U!Xo5_*ZF_irq`>>f0~P^vFxFf4gWUxz=;w@B ze~N!>74(qW@){Yz?_}56`a00cFN-)ipod9i$E35r0P{0#dKqk{(M{!I+ZM6pk6W{$ zPX#>bSPd9f==fq-=FW4E9zar$u(B()kRrhp{j|B_h42=iK*~3msVKV2UN}5{8jaKK z*d=8*2@^?UmJU?9xpKOSd8j~jR)DhPb>bccGLksUT0Em9AY}Y?t}a1Wn0K9jl;chA zUsXpUkp9Buro=wFw@=r1MyY6VtYz7^CWoFwad{FCNtW8&M*SJkfJTUDX51uBOCmk0psR6eWU5lrH}tXN3A;>I6Q(9^b0;IsbdTI|Twap-(O~#@cr=oKs}v#?=k5&>4rmpjN$`+9yW7@}3n3 zJvw{5ne$vJ9{dF_yjDWM5T7nNvkY+0r|*AkeV1~qAOX>N7vYH|E4mc;k*47l1KO$r zNSO}@FimP>u+jdY+Vf39{D7;Lh$4_Uy6>#Cw>0CAe?)i;Ha>L?p3~4_*|$^8oVub3NF+hriReq*#;f?WOMD!(G9fqD@)s9jktMhvimHm{4ha!h zSJl!NYsoDteYPL(U40F~ICO-(v@N?E=PyqY$3@N4qp;`8Tdc<--Z}=C>l3_t{gh-s zWZLlzg{uI)X53u~-k+la=iW^=k*S+wbTGhgLP)J$Lj=&olt1ByVrM|Vu!poN8g~;P zVeeyhyN}{>`T<4GCT>;J7c_GGXkWVuStTq87s|W5b;S{JGsrFGRLYo%lj^|cxzNaB z!|F?Bb^8)nIzprFAk$uN9W(HGS`#nTgcb%!)SNdz}EtUq5iPW@VlCVnn*2$&H9rn~aK9(DAJ`HWoBR*!fM%t;-* zgmwU!V|R3h&|9i`_kHgvH3IvuYy%h>{mn?FOn~vppQmX}(sEA6S__YuVh+zP_#MXN zeV(Qi{of%+=Tk~dUI9bhtDC1R{okFl7o$eFdh#X-q0^sKlF^K7c+bF2mdX1+>!PCH z(!&88UpPd9H);9&9Ud{PyI-)`zXrKrZ%MghbU$!5!i9ztJ`>{yZN5c2?|H6x*b6nQ zKhvtgwCi3A5Av4LU4q8!x!XPHEsB z&fho5Llz~ZY-`t|33|g_a`#bZ^KjKu9Pf-Mz_?^(KDf$)&m$?@90Zd?iE;O6gN)Df zC@}uVxnC1PV(a#?_(oxeCK!J?5I$9;kMMW9F-NiNYJukv?(4B$^`@ld zfA1;F&4^zarMGkDV-~|{^J;Su~3a>1`dl zW9`pq(P5Qn48Tkx6@=)Bxf`eY<

    Ctef_(X418{zhTwgmwiMuL!1{!(chHj&JpR! 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 GkN*o9#t?aU7K2{=1$3x?~Luk~iDTM=JOIizJ^o zPW%@U$R;3Qxnm7{WD2M+lKAxI;*7DE>zhw}Cf|<4hVC4l zEf2WrX2q47;;wmkA~z76mXdELONC`_UEfvhVQyki2mht$zYWz9%L3$A9Liv?r9k1_ zP`y%AN}furgnIl8+{%|1H!R=n&usGH!jF9uTX5LjJ)UF~nm@Ko;{?{kjh*29+1weE z9MW4(RdrD>)OHOp7K8rr)k&anpu@PHb4{xElN5$kt;A*Tr^W$id&r_F5B;fJD64Jd zRSQ`e+gfM)-K!XR2C5}-W|l*d|BT?jY;+>>sfp+KNx96E%V0gO4tcRt>1?~>HDDuO zGevOF>v9yyYLK^g-DqUwR2d=J4@iq`8UkDNN{upRa$ODOYNrUt^ZL zR0Wudz1~mJ-y?2>2b*FY@({jL>JW8VKl-K%VAq<_zaD}1>xcA%)wkKe=*=UR{ zqGHN>_vliL2WIep{<|G(R%Gj@Lg~Z?G)@}V!-DNT23^ufSB=ig#9jrg<0#iV2{;_P zZTsd`$p(-HKfU;X-Svj9G5QJGjSalzHm;^vC1WgfZjygxHXrCYCI~#0!?D5r_HBMh zFf3y4k@c2;n|Sxg1_CZHJ-L34oN~0Apr)j1!!o_(&@kl)DmU4`g@gZUivHc&Qhp5T zremB2h-+ZZb6v&QRA8w;rHtL}1_*&N7QI7maqL^ZUVw&PaLmT({O?fwOcw=Q;GlSp zPGg>KtdoVE+|jo~)FEN&3s7kPV%tWmEbrR;OSJv2WGk<0PTCW8Q3K-t=mkP5Li|VA zG?POLs3p?CE&i7>X3e6ALtk^@QgY=sYUn1PjN?v7G;kYuk{_WBwUWJf=YMDDzfxh3 z^mjJ9L&U|!D9Ab`n3je{sP8E9H20$TLVS$b%V7Ri)K6i^6MS-aN3B=sUPo5~<1gY( z>X2;f@&hpx6lg3_@>;CWy_&mm_xnyy&B|ltfQ{NLliqn2LRayB$I@y$D7Mn^YC9LP zTB6Kp{HTZ^()ih)l2{V=p4A7(z(gBz=B!`fN?a1J^yo|W{WOnrj`{O{2I+GWp#N^| z{CL;BGGaq-K!2+$8pD{VyeTkMa)gs-tF)ebu+4@6JwGA)+tEa2F{d`RZQ= zsEO?#ALB!X=>MUve&qGvcYwM4_OR=dEo(xG3pvuj3}tLik{1y+q5YR;yxfHA<(@bV z)v9R&;-yY+z&~ANxEs`zAh7WPzhKrTa_~#a`?SV+_w-cGv%x*-5w6zCkh?ClaXY8W z!ffDsEpe`ZkjN~8^hHW=x>A4#R12e?Cef?W=|a%k3jD{R4xp4pvroB4hYOd}*^)vG z`6sxnPVhQ-pv(uPgZzHpm-DMP0*96-**1v>hc*asA{K}Euo@j;cq(yE+H@e&u!AQVR8ZJLErQ=Ch_zF3KN(fRcYLMgOlBvCbHcdXj_biBy|gccj(x zS^hBosN8(*h+w7qE9)iiu$n3>J4#C!C;&!aP)@gX)Cg%q0;y!CU!36v2Fv5U2{UMv zNWgr!{~?|3f!Ckk)X?wF)3BsFq|QXCk*3?YKUt< z2y&0u>Okkoq?oxtTk?&+zdV)` z-7zAF^uZrKtr3pERbM$4x_&8K5j*`1@wLqj7HrRq`JDWLeEpypH=IexnOYUjOb=qg zKdBzp5YtwVgN#ea+*nsIr?X(dN`~U=>3ZZHagluGhsRPL?I#nZJbW6O3@5n+YocMi%* zDZXTbc#Psx zoRF@bt#&;^*I-4Fv0B{_d~2r}n&`Fa#FD5tuDuUOdSFcH>rtr$7~c(3$hUWE!6ndU zvOMg(2V0uF>6ky7l+UHsRHdqlR?)fql*Snnf+~erJ#ndeZiVZ9f#o~E%`UmCKUJqk z-HOVT?<&!8d?HB8A4{L(@;Y7i&1^gs((+-r;7)B2Jn<2n?4eBt*WG)Z5VI*JiYqr~ z%cw(waKts(dq5LcVLn`Wm%a5e0#$=BIg3;u?`Th3QBh(Aw|V$nAjHdT{`2_K>NjPm zyS7Mj?PD(!C4Kai*~1g)N!<~rFLdUTWqd}DdNyD543o1PAk#Y zNF#5r*rn~kFywEg z^}Yd~9`hclQt`1jjB~*)^c1N1KytbdmC@A~j_~baqZ6DTAy}pfM{d?fwBd@yZGir; za!-$ozc5N}et>7i0)Y-H!GhDOV=2SJfefyaHs_D4J!k5SR#E7zg?=WkrLJW+a*i3@ zjntmYF|`|C5}rh_cF~ERaHl}kF)K)i5qhOY6{I=)TN9KDO>?z3%#bGJld0t#OW^TB z>c4GbP_}>DhP)8FIWzi(%KQ0V3oLuF=Q{3QLF@3Fm{4miUvj9G zSjTrOqFMmC3g#&ae3ZWmrR4s zDdN44=6-yXgI}w-;bYg{3qyl8UvkGFFp@PXFoaU)zK*7sM1b_kg;i)2?|2Fdhkm=B zM?i}Dw-YgP^ZaO<&iT9ql9?3V*0ZqbE5nNlO6YXn(UV0i)5Jyeny37pF4mV7r9)6U z7crHk8yoC40~WNtN=jyQiza2+L)dF2odwzEpg`?_GExz#Y~x3K$CphiU|sFDd}L0O#GgMr->V z85)wDSxH51O2#xWm4(+- zOhSz}-<@}t+tHEr3KwNs0SuvW(0|%BbhdF}Xj>W)p&4oldu=x<1nyk2QP>+I(Iw*#b@ocfcQI;A5wB}$5Rzy*@eOnpbl;Ahgk+h@K=C9zhCuNLbGbG}u@Dk5&oFK3A+9nWkst8N}nG>BxJmO((3Vk9A({(k` zufHd-ljx;%43VL3-Y!s&Xh@Q_3=rc+TEp`;m>NQF*0~xlxUDjTO!M_!9buDj66f- z2B`jps0p?aAPLgSZp?42xXvqD8c8F5gN-CA4ddiR#67b3n`J7o*<@9-$A#pPOKu`s zwN>`T4)-J>8{!>zL?&Z*wC-@Xa+Ulk&N9b0^_g5mi+PXCc3Rxd!9EmEo6OBVbX*2l zGBo3}Zgg4XJQ3xAW_jXFn%hUSY3Omv?=qH8C~Jam)=?Q!+$#6r^VF+mu9i}e2koR7 zswscg6Wk8#JPNj(*>e|P_4U_Z%c;8ec%^kdj{6o94bB1>=Vh*mQCUndIT4Ua;Y7R< zM#%a}J$42j^%9QSd)3^phWOez%Y!UHd}KIpLS!kl#uoQ{-O zgM?R)2VX!K=hFQ0SiOX?t%xo~xFn&^UP8w4`?baGnLI8T{B9|aXJ&8eFWz}ZM(DwI z?eOy(C)JN3Y|M+ryW zE~KkduC`rItgsChu($>cQQN>=zBm|1ZBzWQUh4`WFF&;!;!t-TCxW4lcqBQd27NDjpkKgJcxLJ zf#sIDd{f2tYv9yo(J^tZN>=_(zSsyhJ(M75Crs?ypry}0YLo57{G2&DNB7rx%CD2J zBLjE8!*OK+jG=&z)Epuq1|GU#d8gekno{zTJ*4T1YeGo#l&bS2qN2m51}PT|%^-6L zJnM=tW42+LK6`JvU-!SZZ`e6R@#e@YM z)yCY<7O*4Ijg5_03x?D%{H<`ZKBE@NMrO$6xNBW1lYk%?12IA(z&0}=8H$FGJmWm zek1Ekyp8q}Rc9&6z`1-hm*gy+FNN-aMm?I@S$t+r8>8nkE||1I0M3MWzAYYy4nSzx zxcpg&(vbAPRX91+p&pqMjy7$9>u5#SY9%kfp^W<;0RXp({cr>6nN)~VG_jaqjmZbU z^LrIuxHKnJn#KgJzaUy#sTJMa_;Uh{S>_kEG`&<>kZU&uxI*iP% zCpyw-L>ktz_tceuXUM}%&or*ICg)ECI1aLhONr#k5g=0q(bX>$y8trQw2P3GNkC6Q zr9|^i7F}MOJg0ri%G8%1>Q5 zNr7$hhPXN4kwk&3)GgJ#^F1UR*)TvlJlSn<$JyVbDRf~7m3OM(fDrb%yc(36w^A`z zjd108AlpwofPxM3n8o`|JFLThPa%FLuO|R`ni%@Aodr7bR`>@m)zd98-Sx#w4qS} zetxLc#fG1HP&Ds?)U|x?>rAy7x*e7w^=gfEhZ|V*Qnln%)mt+5@}6uVxLoR6`~^*? zak)f1#mSuj44H!1koamVy-_S=L|n993b%%w3PCN>lR!qzmhuffgw#G+H3N1qp`>e< zHHk;(pDhy{<7N@Kh7as_nOu|OO~KVgZ5y@&B=$hF=(y&H??TikM<~1)yQ@WQ3eg6H zzZpOh&YDBpN|y>kes1SG; zK2B}w`y*#w=%FCY@*87Lny;RcK58yPLPWS*r}FTM!ET7OV#O7(-*Uf!W=ORUl2D2+ zA?pOK7?{4W1k$72W|2QGt-3qP;uDHuMt4$*X91yxm7&!WwL?^$p88$csBCQbEW*xl zp2mTDmpy*p=Mefbag`t-3|%!)b@YPCb(=SQv~RY}jplhmrc3*2zZHIWt9zcfXM;d6 zPmsZ2$4tn9{zR3k#TypBlIVzvLEEC>IC)OFjJ4WGV=qbqr&wl*k)#j(Bbw$W62CHd ztn((Wd{79~3=_CDj-$M)r^;FN1rjZzHdGn&9C@wvx-vm((fA12jdTs506#vU!|uQR zO8<*+MGY&8B%M!z9g9wvu{FyXtI@!LCgX_$j|zpuB2y(i>6xz1VhI!%K(yL<2b&_| zrw+jDTiWIcEqmXj!ikvcV7F5Wt;E|CB#r)PGM)U}&qceQEhj$KHWR2b`z(3OSp(O~ zVkI~qnLBuZ2980L9`gCrClG53{q|JM02o2wY1UjSan?nc>^vhMd8l%4HLu{9A4%X; zcPG=n(;0Xlud!o9@-f5LSx8-J(q~0uo;hH9foxDBi%a7iD(5SGo^4Wm4zxEioqo(^ zPwh~sJ(1bzX&2Mga2VYt}&y+W3XpLKyJjes~0H~q`y%a zPhAg~J8r!Rq=ILTFlRPKp;tg1=9BIwY)X(rt>{K6RoWbgW}h_JH+!S79oO_x!+Xe zj-EncfJI&!X7e=^^1hDI9_EBk0IL3V`U^pr@$>OYmrc8sGQwhyN?26(C4cW!D8CQL7e!;`X>Y4fMWGnUN#*?PHc z0xBydQcg2IM`#+m=Tp!4Nfgc#HMng_Q`y(C@`x$Gp{@k85@e>?PZ1qVhKtQM8hdsAX)#v698cb{fW8{s`(lN&ia_y7rKQ;( z$W4+P$v54~PWxzT&A6)C?R^a*S0r!I9nXY9E^(2xZWVm-Py5j3R2@y@*dQ)8&!B1O zAN_f%$M_}?*_vJ00x(3vF!Yuos1~Vam5S>H@I7((lib(PwyDfdiCkt|h%qhR6G>c;0E6mf zplAyE2=2V`hj^67S^Ur>y` z!Al$Jj!+@#h1sOl;@y>4dKM-hJC5Ft8K4%hd6BC zk(37B8=ji6+m>6p!XL{_3{&kQ(DGo1gdohR-fOk!grsA>51|ax)f7Rb!dtA4h_(_e zLz~MXsBGrXSxDyNTcz|?XIdgkYy<0vppN$8C%0tM#<<#N5pw{i!8<1VM z1y-?z94x?BIigVd-$Bq}I*Vg1@RWO+%V~Euo(3*^0>7PJiZ%czSU0!s^jlWQO1;pr zcu)Wk@Qs)R1At(ijUW}^$#mpV)w5w%PE-J^6CK>xbZ<_GxbBBM`l^(=wkh{r$oj4- zOn`pK$&Yc8&5%eL1pSMO;?)ZmtXc@XuIL}tO`RcFv^X!g8{;*`smL@fJRfK)Fp|_* zZkJ%3EL96+KPL-pa5Pq{K~hJ!Msb_S3?RX6$uc#Pu>4p(z2H9FYq*&fCe+#l#|?F9 zBdUM&FG8kvLn-eIw5`0c!X_j8kpl3V+HZ^&>nZE=HeneXNHN|w@j@U(5&GVcXf_Yd zd`^}is&C$!o1%?j1s*%iPX!YUGcBNbBP%ogZ#0RNvzSY@TOQ27-^w|gJkAgdPFK6e z`$+ zPK*6_JOIYRdAK*b+151zUT}&ukisFuY@KV0>>c^^@S*0lE4mrA6Ed{M`N>WH7mdO( z_7D+6G4mFy&Y*(5)UR#=1_TvPKt|OZq1k$wS`$HwuFTj-g6JYf+wDBk69!iU`c6k8 znNDT^@lv-&U=`od6e^ZYAdVt=#|!Hsl8gDSvfRA}Sb;Y%@*)(yn4Cs*;duXf(Qf`0 zx?^v0PRAbigA0sc833(JiPx~|nMAPM6Um@tPF6#uWG~)LD_-QF-w=UCW;yr!8-MVN zJ1k9t9D$$`bCkD7Pi29#n?HOHL=a2P0=|+2RmE@r&U$dWSf2gJnrnH@_cO=J?p#e) zEzbrtFq5g1^lwQqYzSK(qdSHu7h7EjC`+h$evwQjZ?jyZQmT=czdWJZX>z#fkul;5YqxZZfjEN zl2to@UwfMtQBl8;k_~&9wi}47i8>7%41~*?`1} zxKjTqykCSW4{G zPXZ>-`-3K+z}+fh{=CbBp5757Z$Zv>42w{;a$jUroxMLtD_V0&nfC_^GcGr(e#h_1 z-0MN(i7`Ff0YQzAXKE%>*&<>oIa09ZgU5z9Q7T5u}ZCPgnyev z_uT0X)w^qSq>*I>6gp8;T~fu_tCqz}>3GMVNCc6TeiLP||!a2i=Fo-M8>;QVqV%} zTrau8rzTvNeCkY=uxTreP%Fri-1dQ!LAoOQTuh~rQ}Cj4GL~OYU=Vq5@* z2LdjJi6A1xdUER|0n%OmmS5ETZU)81@SMx(iEBjUDT;cAW=5Mpv$?tKFTOhzd3jQ> ztcbDy(vKu0Yn{jVhoNzgHl^V2>5=8sREA!k{KRD|NM(H_%CVz9iLA;9mar=#ODjHk~b^+1OGor zgEtH!e*QQZW6b}Mlshhh{SUt*^cUX;3y;+sZp&MJ$r6^sXnQD~VTPDHN-@gnHu+l`P-8ItF z(i2t-7Fxx7^z-#4RcP1tUYCHu*=`!<@W-KP-CSm$Tcy-rElrQ{c#~Vvi}xOetE-?b zW2aD|&f70!p3m}soq$%<{E6{$r&#uC8ZD{)TXAE3xfcW%x>kRSYQkz9qSy$T94UYC z2Ezl6zEJJzs69GbbI#>CZ^?x|57gv<-9r}M1AHRA6ifSfKQl4em&nAKn)HQb+q4Be zO_Fzl8Uc{ogT$-~{=6Jx*F9vyqvC1Eo`j05-QTJ8d-8>PDjoq)+WV$HMkzBCpX#}$ z6i-Mu)!O3pPx_D1!)>eC2+n_Ds;s#|3@II2$~=B;&YVqwNfda7kbG;oY+aSYUk+XI$0-JYr^CB7lGsN26aim7I+}6iaIu|iD=wP<#$e|(f`$DNiXb92Fj_I} zym>2<)|78A;q+CF1BhXye8MpdDy*K!ST}OIueWpuTa`YBP`zZ*k&>E8ruwd?0D=4o zwB=zVV_zcm4!)_x&NDwk<41$L&GZcavtXeS5>}g2F~Oo55FqaPK!Jx`{iXB zS+b8k_R|fAlZ?oh64S)v!{~NAiWC?8l3WErvbtMum-dI`d221yQOS#4HJ`ltc=R3O zX|aw8c;MV5Iq~H3;*y&5!BV%=$yV%(Ich?Gb@QdAQ^8{dwFf|S;WbTh{O=)+TV^V+ zcaB-shDEm5t@7t;bm2txY#jzdy1yWPd?ZLk-g1S)13@BLMCjGsDNM)3vXNk=XO;uP z>ET~ITQv7yjN4k>Waf&uWu*r^o_1`BJtr0oo@N-rSrW9eL*4463gl=m0Z3S7cdhhX zfFuDD@V`5oRRYCUHv5BcP^ou+o6;7hLj0=SOG2v&Usb8Fs~zY2_b94H{o?V*--};$ z3W2~^dNTJ4dli1q70UUtYTyIKCi$dF$;wRTKoNTXkdaZcLOC)S{ErMJ++_L(e$+_? z?`a3iz__P={()fapl0w3@!z;5A80V&6J(rVG4i)*Jlup_hibst7I9 z4<(qvAM8fapCIAbEeKn12hZ4KI{m`l>$2#G`!H=MS#m{La~8)9*I(m>5R|wXiGw_! zC;71ZwX|07|Ao(wEH-Q3=*}*lnbIT~W0(qyep-jjCG5*!J-VlyHtH)q*F~S>#5En! z+KVd(8r5n*m2h>cjywIFj*Kk&PA!28j0^NTsWll>1B1`GaX5Kp`t^Mh1nYeh$H%Zr zo{1&?9nId5O&~sAuXwGeGQqe{E>DOEprtGLKs>_~PHE)a6{x#A4ON7`nhxA=oAMLS zCY2q#9JJcaW3G!y`cj%q_ZB+zm|QbnUm_hJgawMNXm%-17~y|Jc}a-3W>4iD3M+KN zmk|^CEB#5?FF}Q-ea$dZWc>UJ(gXOuh%dJ~e*au{O8?#s|8k8v+52vCZI)*7 zP8sPqZk}HylK}Fe9BHaV0K`e`cjNeWP`WAoDeK`Hyv)6&C{>!d+Wd)KbW)N(6}8q~ zDnj?-dNwmnAPU~6OG;wB-@mh1i1!O`)fI4zUCj=5U6#f@9)8w3WOp|$h)j;*@?rY` zj$6zpN^lnCWo2D zPfPPWvT-#g{0=h<_JZK~dS2>hWOL)2vNdV#O}Xw!eF%p;v9ENH%qd zeXG{CDi%%V{4P|ZxI{L12Y=ToN=YE3ftPLv^DdAN<~kaCk$|G~dHkV4GfBZ%s3F!$ z!L(e1ZuJo*g~Md+VT@;Ku=k|4RZGR1kAAaa=!ma!w&;Uvh@YpF?sSwCh^l%bp{gxX zc*TSAcvdJ%uclrW$1&}YiFiRQl*v^u)sVAqtgG#t5UeE6=#oNEwNaxmGhgwI&?uJJ zq2rpfbYFWH)2$j#Uma!lEwl^f%le50SB%ig;#Rh@4DBE0tWDc$8(^ZBXN@Y$#x%qY zACm)`jX(b)?!!UNY}FNrmPP13&>$b^v~6s5o;}yrOx)YuQA!6B`;iF<_OPd0gGNls z-AEjtl##+P(Il0I6E)~?5a$V|b@N*TNs9Z0f{u$YV^L9%j1jE?!F@jn=Ba-$x6x1r z$rbhtwPRIN8e)wf9e2eZ$#(dpGcX<7+`?Y*D?Kx4ATet0gx%}XC=@d6XL3f?E{Lo4 zoRX#!!N8pq&$MQ%meMm-yNZKe*ea68Xh`LbAWY;BFVPc3IIT+#SZk8(%m zS)5$;SRG6?sZwpe{y=eS6zJsXVF;(8zO1H?Q=o#u?|01}h&kK{hnhd#_zt-X0d} zw?ssjv=>r0PQoSsQ zxhSYkQPXzhAigd7m7T`#l&Cz>bq~!l^$e?5-Px~k)a&0tlv=@C_Bg#b?nI^b7o&A} zT1>XT;BzfgDAao*<#?QFP)Ru`ZxAG5n8_#XpN+o4EbNmve|(xDkC=#@>cK6RAe~Tnxum9I;`YeuL=%2M!xBXaoBAcvr(G2>oTJ1EIokiJa zvsWi9T;z;fOtw&6t1U6E_gi_(o9(mw~PBR1x$?Z~PbwH9hQ6 zbHY(r>Mgg?1AGnSl2O~@@q#=Rsp=VA&CvoOajftU#&VZZ=$D!qeHmOuEy=NilF_t! ztk|Bi_8`iuBI_q(Myq^6v+mjgMF-tQ`UgXR+kt8sQ+YTm0yE%}GLH)SvT8)!7s01_t2cQqf<1R?7Q)6^-kC`J{mDGGlbCO9inLnjw2XeAqK;lmzOb;kYPl zptHXT;b0|Pes%FK>Fw8^1+U$0sR``TAB$kuO>epw%#|S>rPsniUh_NN-%0gg6QiCu z5GWZmRym^OqTTxQGrK6Xp=^n@qpBin(y28gp}7tfv`(xA(wRAj4y;j<_VyrXQKy!g zx@gN!re*1@R&U3^W~d-)pIrS2=4VhZk9pV<@3Dvw|pS3NroYh!`xe zPL?Y2M#>|(cne%`SnqzLJQ?S|cRFClDxz>S*hsvb)3nLxa#<4R9?7cb| zrI${u`Dz)G+Y2zo#_rHNA6f2ghG2L#$n3cZa9Rmxf5Rl~%bQG23AyYW#s5w58D^^1$JoyGW z@bDIJtMDyipAxN&?K_pCAakREC+}(mnN)mrU<6y9#~Y!@1^qp4UwfSY5du@MMeT4) zMdQ;#UGr+_>PeO9mWiR2z#tnSg9#qzTg29}rG~(A*WKg8vEpF+SUC_gn0it(+5~nB zrKs;6F~D;NjBE2`2ohEIqkvsxDi}W9pcys0W|+Z%QMwHs#$q1)HuJ5c_#uAC*k+~iP|<&t z&3lWk|3_y-m{OHKq z2d;jNK1k>^{U1e@z_fduAP+ogKEZZXky;R4k0w!qj%BM)w@OP2uXKe~*S0POw{&$D zw_iy1jdTh#f`XN(&^G#5kZteCPMLUBqT356#w2&ZgeA~*HFK_Ba$*&ruJ;H@t30e< zsjwX*fx6U&3V7iJHG2AHpGfW@Z0JoN2gQ6}Nx$g|&o6kGX{6)9-~i$7Stg7=OD#q< z6K|(iXvCdCql-c5G?j>DFnI55vOQ?y5O<1fcUwjK$%jsz%nTD%Ljm5cb5g(hP34jw zTPX!|vv*FOp-!FA)s-q%A@-cHflfFSB|9*Q-4v?dLAR&w+&3RL01VxR@J^IktsRz? z#7SH*-S-!ut+X=PAF0AT$XJOAZMcC+dQPJ6f52K9<6Q!o+`-f55tVrQSx^KPg z3j>P~dw9mx8#b}^nfn9qh5k`z&gOut2=v5Re`)1VyLWRMr{Qw?EiGk$(q{*k2@iH< zRq;dy|{nKHzghg2{eA>eR(#d45k*`22nWt17K(@RC zGR;QN5s$M@jC!!*DP*{B2PRVqZ}5E>1(Q(93+o6!JOe)9(|#$h-B|3)6T!9my9L1Z zhiSZr<1v5}2(-C#5*3t_lj4xks@$to#np1b^C4>|EKqm)rm~=NFJ6_#UoQ=;AoE0E z4z{zY(J@sFoJ#KTJ(nvdvs;M9ojGjhan-Q0xviRd_)T&U&)zP8@C1r905VbOJ}7mq zp*2T~6n}Z>e^0H%0-{Se@C3*zP6X*y+sw%gSooA$TVZ7{~8H-dfYEHk~&R1GjD5 zf=vw^)^^8Hc;^rg!dqyY2*L3`8%f-PiWMN^x`aB7eBfCNP43941+>*xm+?zYS=_xQ z*t$9|dLuBXCXrFcGsCs1u%A8+5(8e35m~A|C}=9hx8))kjej(TE-B=V&!w;UI9IR= zW3+f)#6bE9ko69S#iM3NMDFK29u@c*wL6rhe@s1ATUpN&;A#)lLOY1M6!K7w8n6@pYq}vh2LHB5_NZd!)6w!)Jm0)NVAoeVmuh1C zXb*c@_p)^R39MHR?(*Nh@afE{Y=3tHgO;@bJE$xeJ1*Jq$$byQ4P+kcJfrc)0fCR3 z?TrctKp?=*_b$@1+|~jfl>TR%X6y3V3!0D4Os$mZVQYrt)Oym*s3USb?~2-RJ~1@Wj3%IiZ92f$B9lvoUO>(}ynlFI-TNNc&GghDN~Gg&)|={m2q zetW*({D`H)4yrv+Y13mxp=0Lkcl@ud zi1x2!gjHE6OU@nGoV0K&#<<_OJJ!HgDLVAG)RN#63Q>9F!p{*5s~U4-hi|{_=TvP{ zOQ$`fswP6?&w8I*ZYwH0wgLgscK-8U01HY4jKo7nk53aPhlhhX--jw4dd^PRSAQ=n zHXPr%IcGHKbFr#!mBXh>I4@mZ897gsO9uRQE*{6~*KV73;tE^h3A z7~a?OO0NChWvi}SHoh0;sIl#l-yprc5r(9)Bm9)oUSuM)fsg+^$B3 z$%!a)@_V|WCq9PDL2iOF3-72+XD2gcB(;3B^$N{st;J|82Y&n4N5)b&QFcf^)Q6$A$Vx>nm;mE!M!NqRAQMe>OJrh zGwx7G*6=GXSApHH_W;BOL!mk47);kSZ)nTc=4Z+0q$`aft?&Ss%u=!@2(*5Y%POq^ zm8J@_FcwX4;0+93#DFSg&z}KrK4a>qzUybp_}3kf_Bw9FYAeshw#{sE`)M-Lvfpt& zD~`3VR4!8G{=>4f6;~=oK{bXZUVo<7(d5*um>qkR)d7~O)j?`~(4;b15P2Z=E60Q8 zF3Eyrt=;t?F1RCHr&hDANJ9SMRZKY?sRzY)vTF0~SverjI>MvbwHn2WaRFTmt*H$p zUa`*5CFfv|P^WY>ktBNTqqNHcpjzGdw)$gl!wm9N^Y?MMlIFG&?S0Z%MD4lCQ(kc9 zZXYhHk!Qr@!uILsJ20lo&{T4KaaTwegjo_lE6 zpIg9+l0Pzi-{oBik8>++WJx}#62rb3j&lLexzpVfVCCp8@sIUM7t?6%4xh(MI5ym< zA-H({BwNz4-)=RbZE4uxB#JuK)lqia+3IgG?HidFr5FL2Hp$pu40oR2O{L|_oF>~> zPVcYmr=ASYmaM3AixDjsepPL>^Dc-j(?$n|+70+K!T$iyJK&hAI>Z%oFyj4!^?u4P^iMX?) ziqQg zUFzwg&8Mx>G;SVA^N3S3->^rYZr(e-2fff>JrC3;O{1DAV3N}3LWCqvaXq0$UpE?m z+V3l{>Jem(Xu8Y-G`59R!Jp*^Ahf+{>Necz%0dA#NixJLA1Vp})hp&94|mG{IbX+q zTXZ9TNP&rn%yMTZ)l75hdk8>qgG;n)$s#Z{z^`^$qdXZkds#OeF zPz$e{zNMEWXx=dv+Lg#H&a1rdmG85TZp`N1aiTnC>+ebQ$if z{WCOCtnT$&jpaDW*^wA$1};K*(^-fDh2siXo`2Ck?t0$daX~iU)|q_KvVMF|L$PFP zth|EwV?SMQsEqzF)J9fU_aXwE=RMmQu&X|($}~Z*J#u(MSyPEh5imTSu{ykB|`jb<}e zj8Xp1-rt7$KR>fjF>R3B+d!7Bq3?77>?sq?s1Y2ePYkkcvQG}{twn@sG*q#Lz-ccM zG@fQ1lr!V+4TC>i3}nPl<&HPk;gJ^|F}EG?U9k5r_M-BT8ay6Zd33rDx9d)KY}>ZYj&0jX2Oac^I_?-NwzI;HZQHhO+sVn^`@#RbIbYz^n>j{} zsv4uJW?|NK{qDNOY-w3q%d(0;rG$!ZH^+!vkG?4BMxVCFFT6D9+dX13(kx$E$R{Cn zPk-H5`N8^TvuDv^TN3NI`tIEnTwwIxB|H4LCRcaAgU{bas@X_Skxn;@+H3xAOEYdc zRMACuN9xlrZ(!L>aWkp0yJFo2(477ZNy``(D zaLXO$2N`JT(dB!R0`nz>Qnqz*8xnTwSvmK3k3)h|jo*?KPzmPLmb$y~{MBUg*JgpN z_z(DZu-*6l(uO8*Nr^P^obaSY&VTzHuiTO7*DR6^$Ls0=Lky7QS*L z$^*9@*Q}nld@Pw>yL&!YBwg@ywGP^r}z+gO#JM=xQFH|PFTBzoX~QS(!c@|n0J z6Lb;3I?JK)FA^FiMg5AKTw2&IJPEwv+gol6_?h=Si)IT2s8i9dph(;3#mrBpjo-EJ zC;4q@*y{|$*>MHTN;cxjAR;yt+DW3cT(oqqA`NooSw z8VN$;6sO_KtT>dwL@Ye-9-b-S8fz%p*1v^43&v(PA4dm0@_0F>KV?le2&}>aD!q1~ zw5aU>q`>d6VHpWAQiAe(Wx3s1Zr@mj$nqb_>4_o&%KR%guLdSZ&k#@QsQXi9illfh z84#1VS!!lmUA9bBylXh~_)L5DkL?v)gp{dX%r3ToU|rbFWGmF% zt~kvKqrHArWRt*%^ejc)2rJln<7D>A3~ln-5L_05XiT_IG>?_b0_a9}x-avuff_SU z{*7gyamg5#GwS32T&WPhrz?N9r9NtQakw1a{^Q8bNgJrt$_7C7n_Wf*81J<)+5fBK z(eh2-Q)Qfyw;i>)xgh>%&8y7Mde=}9gBN~+tl@E8ng%E{#pQxTm-i{cm{^`trsBPA zG5Df&t?*o^ri)byt=_p4o!{`$nrc+zkV4g?lp_-{0%TN=wU9}oTEoZat3>CAdM1WE z4r(I&5{{Txi_+g<`(4gTkA4VAYPhSoFpJv-hxyd&z9%V4{^eI{FySq8lAaS_`@1x9 ztlrm9(BgGOyxl+_*ImzUW@=oWZm@G>_#o_(OBtz`1)+f_v_wt^m~<7E_-bmUi|w(v(iJcyoZtux`Xp5TQsvQv>y0=I z@11L*xGJ^{C@g3!-Xk&`z~|0@-kMmhBlgBs8z4&`%_lV<*I7NQJNW(gs#-I0vt0Hx z>2Y(rC;9jCZiyQg9bJn&*DDeq$ZWpwP$jB?EwxSLqc1W~uBX%9C5_Nx$A3?ip5Pa! z^;P*}%OFH2p7#2LspMNeef&z|ExjX~d6zLKWrA9@isqkx-N1Wyl-O3dUahZ>Fi<1> zOJh@(I2YwKnEQoNd`BUOe%*YZk(L05`EJT z4fkQE^Tf@pXguP1v|aCRG~iK%#JSU#n8AfPhR_?K#eSKvZ$7Jv81akrccL9(tk|sP zXd}exYHKet{v$$tlVdTb1G7pJmGRIf2wIg66^z4Gk^8eK?HhH;?B_da2TQZw??CrA zV?OQsKGDTu^5PS*Qm?*i{o50E2r7#{fGTY~Nl;xzh!>2ZskH7=rVo{w?z-9^-eIn< za2lrrK~%xZ>C2?H>wJJ&QlPbJBy3_-F#=$aEWGmftdcs`=Z7`ZjY1n119zseV`P6O z20{w~VHOhOVpcKPKUe$W!N_RGG&!oE6oh#QV9nx1``R=7BLlbM=x?g53 z2vw9b$}iUq;^RYPx>s)lA5|lX-)tssDa37WbQx697Yjix^Od^>K6c;y3_LatDI8c? z4+eDB5|&f-G%K0t2fI>kuoj_8xNzq%Af=CUm)hhPLHG9vV1^?jy%&~DU1g46WGW24 z9z$-v&x0*HHLIt!zt-Bv^!AZ$#As)a5RK|#g~^w*vd&9wXUIS ziUhJ*IvmyGeLYUvM&-k&$QGw_Q3?Jg(~&te$Q?oFj&vVm2y5SAR11Gwv$Iwu=iIk0 zF8iC&cn8R@0;X)2uON=e8oOw>Y0|)u#+UeX^#5>Q4NX<8DL=cwHLY1mmPI%$fd@X@ z?N&>5(s(TV(m#C7>}weANz00!uFuh6WPi%&exBHaH!9NF2Bf?;l4n2DP;!$g7uJe! z$Vo15Ujjh%pY9Jbm?etC_o5v|#l~R`7sgL=G*r*@ILv95Gn-o42W1c>jKs(Ev5SLCM2fWN% z%n5T#8FSPdwtjPktl3o{xQ!wVkF4XB|5fyNjEAhyPwwAB1;*DvAG?8G=MkVHKjk(` z!R*uD#+H>*Yt*FlSqwLmE!5p&bH8A|OfwmRy%gN^;%d5X*Q}06q_BuB`JYosG|q_W zB)aNTwS4$n#DNrAgFZt1kKn4u)_~OA-kHmi&YVv@z=U*?Jn?Pp9XF7bi1pLKP)dVo zoQpBNPz`qX6vO2RlEZ7tcV+gLVXHFwT9 ziLqO*PC11!O%4G(Ei%yAwMd*6@R3_0I(b1{PIcnLE7u4 zx0=DST-iw<$sibv3e=XqA>uXhvQ$b8}f51Oy8KCnugjU%Z8ork0cljzlu>{|E-b z1GtBK%UM?3oqaa1d6s&|rO!I$vlt{~&QP30ixIasno4L>QDHVoaM0B0R8lXrg z3!1X}_;KIL@bRDT=(AbI8k*kl6hc!x|HH!#Z~H4y{p&Ij)ZP9K#No7axGL|QB_AkF z&oX~+>arUAR_a$bJ@NvY6`C0%eV;pC#@GdN^N;WC61p!N46! z$3EXlyfbayRyDChSk@Sq`NyH%wOz0PC>=|+WLH?47N719h4-LO*|IZ^VzsA@Bo(|t ztZ-g`>gtcu;}Q!?&bD59k|eDZel4xxQvqGk`>bXh{c@{Y}SgwkKLu}Me$>oA3<_GTB`mzC}jtt2i zi!$FEOQq8_*tci+0p|Kmkuo_vi0bPscjzQ|E(GKyny&_><4f%VFf@`rqF{C)DNUrZ z6{KzeSq_RCMw<%8ZjR~2Mpw;K5c+SZss{$8@bufP$}Jzhm`tC9{NMz<{{5@bsz{GHtq)FqRXI9``v!o)9 z#VI?iHB>|Tnbzx$py=8xe;!tU#4}cRTZO6SG;OJ1YX8@gf#v-FNEQodJ^McYmxwT! zjJrTFE3T?x?;3gg$%YY@%T3Mwk68Ejr348|PR_-Dlm&`7w*#)RNoLK>#_rZFEK3)yf1wp@y6a zLHpF+dH?K@YP4;S$A2MhQ5v(%|A2WZu0fcdpe82M6}}0R{HwG2XhKdm43*4CrR5VQ za4* zQdRTVow|>Di#;I@DS{pJ_VGN~-mKMMA>JH;y+ki}ymJ7h*2`-5FZdyidtNSb3Oi(S ze`rV~%DID&Bq((R*~Qd+~MSSFVMfK{PLsXnOG#f8?#N*n=wc z3oq7`l`Z+L(Z!D{s?t03{;));=zsbj_o0uO$YT@5Uv$Jk99h%W>7ah|YQdo42@y9J z4+wEpf{bP@sHPP_Q6-Q+S7b6h{G9Q8Y$q4RoJ8?h%`nj{WFCU?6DvbyKVI3y^(pyd zyJvcMp)sl6#D(jG2my25=8yQ9-kbXgH)RR6(;q?vhG@ffMn&v8x5I3R2SORayC&Vq z68KMcm7b0t+$HNu$A?5tL;3AQ{|D((zG<(`JO*!gzVvftD;AcNxeKy+8S106x=cGNhl{ByIhgs#% zK!s@CRt&NN_PU*DX3{)-~d0K^r?sBv>mG1Qv~|5EsGca%M#eWD6=h1LO*j%J~V;Nh?e6J5SR0g zcpF=6N2gIa#WmT_qrhGWL+Xew7YJsglg!WI8@b6yz7UIK(7+R27f7_;7$!RUr zAEZTO!7e7_mJ(hegMw(whe8w;Ch_9YmQB|T9KF@os`Z2!UyI@GPthgdWWbLQ8^W4w zbLGd3bO#q+FU9))-yFQ1qv;VdPD2P7zj&S=g==A^J4XK~aSnomPYFqgGhI}wzdBJd zR0#-Q>ez0%K)vi@83`LtPW=Rn<@~=8H^LHqNHCiLc#}SN8jK?Pm1gtw{KEy<&|VS? zT1)!>2I91x{{zGU2FX)6jwto$#cqOlJ9`+sxKx-37CjZ+N^1J(d0u|+Bl6~%x?P2G zfu5TGmKS0-1S$>A#_U^tp*#h&&9wvG()O7H6?YT=+g(U(Q-O?0#|K$njA>5Q{Qh=m zpWZjzhlcF4eB0`V;bJBbl^i{MQdUBZ6{A%C!>EHrk<#cscWrB@Tynl^&ds{2bwRJ& zwS~DY4hQQ2`qTL1vP&!0_;fM^e_LUr%H5fMdJu9xm7v>IWc0KbOTp2zRen~AzkU~h z*Ak(vrjX{v+o_#oPq}ed68RdBhR*j2N|RnpQ+=01@?7jKsyO;KQ}&npJ}6PiY)dU& z@Mlh6jUO3z-Itf{a;CNjXJ|F4_3SvJ`yb&u+o3E(=lyY{ni|0Ac5jqiS8T7|w%$fT zqyB`UqLZ?DSt_x>p|pQ%Bd7j)-$*_a2|lfnKuH(<@t;_9YWt%*x=i>RWBO5PzywME z{moxptL9~G6$vsK><50vNO+edlr>Kb*ZF^v?2+9CSz!dA_X}4uBJJe zZ=-Pxz{;?27ivnS#0p<4&Q}q80Tc?@$6ir*1=p~rkr3gUTlWEu)03wk_7^(Y#m(cl z3mi$l{GP;8;6&gimwzH~5#p;fR{Ft7A?rAVXRN(u53Mcqrc#Zz1LV|VBb(EI{AAUE zD!|1x#ibm3%%S8C>aY4UPw z)}R#aD$S@prQb9;i70)=a=bqGZuIfDIcnPPWO8Wqg*XhoR z){FO>C`lk@8pQYqa;*sKcC~sN_fUZ2{AR&2@x6N9i2QhtsuMYCTI?tj3;an`+)~1I zAwE6vOL<%E6!&tXrKDBxEAU3dHksIR&phX^7xf5@<2OiZWib7-zpHi9*~5KZcN4I( z&sEqTZZP+fwOd;aNKJ>S1_MN?1!O{fhMF=@wlpy}Rd+Hyikd$~^|zd!$WAj%3#E!w zmzz2Rq!q{--D)-uPv%EfbZn(Q<~>_y+##1%R$=Ymp#HY;pqcV7o2kLm(EW1Zgg+G} zY*1NXYA34O(jqaSM8`LdjfuHmeX!!KJ5fO*^s?ooaaJKn4I+vYIe0Wzd?eib%+FJp z&kn2~lWKEaMNYk9@J8=p^*mjo%y-HArb{}mwKLveMWnFdsS;R*t4U0v0oJaWPN)YF zGK4cnhca{xrH2IXC^rRbnvtq=2Di|NZJnVCzzW>$!s!l6=B?*@Y~H^vG1w)jv3Y;i zF?>3FOihZ-C|_sbIZ6pPZ^H50I0NDY`XqBGBii#C1Wnit8+=6GCTNjSJJD_riowTn zRjynK_u2e%8oo?eB&K!x1s~0QE=e7m&oY#y=96x zQ!sbZ8O9OL=fiH;T+i2!2hLnvj+}tUnFV&&*M^#pehf7Dhc@`abDEirNfiW8oTmoc za)$U!f}v}FX%=3Z0gb+F+^!h3Rp))t#}w%yCrcsnKU>0F!9si+c#_2-zUZo76ZjgJ zf9Vp%mEZ1}#}ufLQ)sDYLAVd|zL|bb)4GWoC`XX#$inZ;xSLe#&~2nnXwxba&@D0c zC(sQvA@O?au~cp@K|y<3t^p!b81mM-(Yl>XmyCI$%$RikC1$9^tIXtW^2@hE;|OEL z@w}CFZ$s^caD5GG6%)rPj<>#CKP+cYzwxj{aq3`!J6u6T)i7^#mcwX2{4;vDJ8M&S z7VKGNi<~{)^3idMi7b$=htdD8^@0p{+f5F|GlklmhX{>2YjiTsX`cX6&yg zu!akZOgRPIA+t8_uc%#k&>p>`(d80jF>jHZZEb%Mw=U!wNm^JK65c8V?ANO; zvKYV5NhL-;NxVm%oc6?|`gp}TX5x&Cg%gtyYu2{uq6wA8l@@#-aRv^}>If#l@b!@( z@)bmcXJ{Yre&&V2%@r907<}heK^z%|2x!w-*)Rw@v-iIzF*EKCF~;_opav2OV2V1Y zpui;b;BiaO98TL`u6g5X*;WIZ7sTIS-me#2fH?S43`5-=~YSxoM)c6W`75PhX1+FYDi&a9jgmJ-^Bsc9S*eRU2NY;dRNL-9v zgM<8s!1<5+kDtmcs&$q>J5_hf!C^w~4_ScM+oKDW>FO0|)rEVnmR}L2rGR&C+9U@7 zkL1@J+@i(p-12-n1pC3BZ`I7dd@rzLmOuDwrIwz~(_?flj=R!K{iLR)`Ex|>f+QtS zxh!fe>#iyH@DG=MmBW5C-$B?GX$p?NZ>Xq0q9d1+82Vh3A_sO2Pdfvd$N5X1>5S*c!&#yNL*N*S6}jzySwfdh z9q+wapuO*|KIFwDWNqSY7f=g1WuRdM;?Q{qTkhV$hlbnyEnN#oz&N=?0T&aTK2*W;b1vZwJMishgt<0-$@SFJPOX z`U_j2B|28Io#;hhY%J|AXXRv!Y5QIWD`u0idz>hIr z@s>2{5U+OhsfkY&Mu6eQKB_jDMpq~TUw*Jy4Q92+zY{pQcsdsm1|z*;hSRQ6!sB<< zRZJb#(ts%x@<6W%t<@|$4bMCB4mSPv%RC>U$+NA^O2HOTx~ML8VJuD_>`E4^(M~{R zQW$ZFjQlR{e868Upkje;)03_IJMZs44_nPfb0ph`Y9IlhhDpooc;}2gL9Z#@bv#43 zMo%9+buYh2=?4uNq1sPF2Q{>wVXu(Zkcyo}EA-g(xs-s@@Ji_e6#o%Y!0p@hr(>nU z>Rbu!#D!D0pb1wXvR!3$M>wjKQV;T81)DA#Sq}m_`(YimT8L0p8MDGgga>Wp+gMCe zBpG^%{uIs1gWnZ;C&lG@hl>Z8FHBx0RIjbO>X6hP0oae8XG3xX^tFF=7w0<3>E9O- zFd%#|EY1NICsYQ~wpu7k58Iu^mIEv##z}UC@4KFdhU~J{O>vs;B!0-U*AK2^%fPN% zr*^$OoT@dEvm4yT{>gVx=0@BZ_!swhX72r1=Y#Qhyto?4Gb;~WNUE4>rVly;`neb@B5Dz_OU?8G!cqw-K;#T97< zAF2N0BBOST?*~3t@QsAl%#xmxpI=#F?baAaa4tW2SbEf1tbkbdya?CfFr04vY2`#| zpEL4^poptC=z>4?B6!iH@o{T*^zZjOAv>vhqu5-q4t|I3(QWnL5ZmJ7rAIL2mSX{3 z;;#g2QS^C>&5EWMTlr**^uRd(xIpjkQWN9M#FGf-o}(D_r3zFV3FM$&sj!ch z&AM$gN-6@Yk@XIgh|(_VN8&1`pH+O{LSmQC!Y*R%nr-*5cGK}wT;%spl~~t_vx%o;@ZCCA(8>bo%60g zM`SZUM2aGjwDnl==;4vo>;vvWu}DoOzbRH_sJ2))$eqvjGatU2u!ysGoPEVysBnER0n%w9PP zRy2l6jas2Mc9gl1F2daqkgX@)OELjoCK# z-tTW}a(t7UGK;s6)mU@JZ|N!6QpnjY(&?&5a5 z7Yc`!AKwy^?>oOZ)y5E1SDai8O$nKJl&!2vZ-6x;EkX_$(NoF~LmaQo@1<>YE-W`(Tcn5>$c{Ln+V-O;#s8qF32)H9JZrmeSIcmWuPd z_p79>pSW}A1H-#*4jBQ*AA>k0N?KRk!)rj>B~Cou`+bwQSxVlSt_g#yzP@MZFzqW;ag%$iPIfpF~?>0tzDgu|0& ztdqiX6(S4Z8mp|=Pmb;v&%*`ZXG6ISezycixgHggC~>->?}reZf+or5MC~GyRT#Kl z33Uhww{*HdBKO-G9kv=S2J@u2^z7D)^D4BES#U^IxMpX@%lt7p{(F&*C?va?$ zgHL~g>33+HpxJJ>ZFn*OPlmEP>vzj(o}!ujLIzxAHf=Mv)x*ovg7w_?JgS*!yRg2O@c#JMw#cqa#1f-_m7VIYKzc*&WpO7sZw$M6+GDw&gruFr41vf?>RRB(jkR} z3J8KlCq09$O20cBP(f|4OKtmn@dI3Cl<=|;Og z=uHqF-~ps7p4%~-b=`kfI$m}>eUmRvV3hRUuGn{@35yITane6Hs|r(*RNe=MZxsQo z$6K;0;21k3wm>N(N10;gPlHKXtiep(FL))&$tWju%XIX#6iAW{Z-{Bv1tc>I?5-c& zdusW#&ZPUL#r-Wcu3DvAt7hz-3^9xw*qaLlDPgtoBX&n_#>lpWyC6}G*t=squ= zgKqZsq^Eky$nB~gF*u9Y?lk+MY4K(SJIZ27azuK}@`zXF${hopFw`m0;rNEv(W(|X zKZI$<%e|*3T9QM78f9HkACr97?4 z6bB(8+(_@YZmMnYv{V*?(!pi*CZQI1q%~*&nW;wWf(u?rs6faOahr`&WE26Vf@}4U zJt==gHixOck^>K@-IbvB?)F_H1uNXdm{HvyXFQ}bO^9*~d?X#@#(dBzn#WWLOMEIE ziR6*GVQ~{!aKTRGA`3GtiC(qlP@D*w-5&SQ>_ljRuRi=CQJD<=t+#3vpv~!F|8Wq+ zo9y|{k~AI>R*&j|pLZQ&(k9-AWC!CwU2kO@fo3YxW`$^KHxHLhQXHs@EY1MCdT3BWa;JtYfJ`ClBi7=?g9HS{@YnhF zyNC&DY-ko=a3n#5^T3o;GWlezQwam1=rq*O!ttTk*nLGs_R+C9YctGk*h%M{4S|%@ zXB8?Bl|Z4}Mq3h$LN z&=&<xJW0k7%$|8%3BAKxH#Q57do1BSnRPSWBEV&x)6e*XtgjGa6H literal 0 HcmV?d00001 diff --git a/doc/workflow/import_projects_from_github.md b/doc/workflow/import_projects_from_github.md new file mode 100644 index 0000000000..8644b4ffc7 --- /dev/null +++ b/doc/workflow/import_projects_from_github.md @@ -0,0 +1,13 @@ +# Project importing from GitHub to GitLab + +You can import your existing GitHub projects to GitLab. But keep in mind that it is possible only if +GitHub support is enabled on your GitLab instance. You can read more about GitHub support [here](http://doc.gitlab.com/ce/integration/github.html) +To get to the importer page you need to go to "New project" page. + +![New project page](github_importer/new_project_page.png) + +Click on the "Import project from GitHub" link and you will be redirected to GitHub for permission to access your projects. After accepting, you'll be automatically redirected to the importer. + +![Importer page](github_importer/importer.png) + +To import a project, you can simple click "Add". The importer will import your repository and issues. Once the importer is done, a new GitLab project will be created with your imported data. \ No newline at end of file diff --git a/doc/workflow/import_projects_from_gitlab_com.md b/doc/workflow/import_projects_from_gitlab_com.md new file mode 100644 index 0000000000..f4c4e955d4 --- /dev/null +++ b/doc/workflow/import_projects_from_gitlab_com.md @@ -0,0 +1,18 @@ +# Project importing from GitLab.com to your private GitLab instance + +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) +To get to the importer page you need to go to "New project" page. + +![New project page](gitlab_importer/new_project_page.png) + +Click on the "Import projects from Gitlab.com" link and you will be redirected to GitLab.com +for permission to access your projects. After accepting, you'll be automatically redirected to the importer. + + +![Importer page](gitlab_importer/importer.png) + + +To import a project, you can simple click "Import". The importer will import your repository and issues. +Once the importer is done, a new GitLab project will be created with your imported data. \ No newline at end of file diff --git a/doc/workflow/merge_commits.png b/doc/workflow/merge_commits.png new file mode 100644 index 0000000000000000000000000000000000000000..757b589d0dbb71c56b56905eb9fb1af895d859b6 GIT binary patch literal 41422 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 literal 0 HcmV?d00001 diff --git a/doc/workflow/messy_flow.png b/doc/workflow/messy_flow.png new file mode 100644 index 0000000000000000000000000000000000000000..1addb95ca5455fc9ea12da3868c322a9e5fdaa01 GIT binary patch literal 33829 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 LQiA0I+P?n{JLv|t literal 0 HcmV?d00001 diff --git a/doc/workflow/migrating_from_svn.md b/doc/workflow/migrating_from_svn.md new file mode 100644 index 0000000000..485db4834e --- /dev/null +++ b/doc/workflow/migrating_from_svn.md @@ -0,0 +1,17 @@ +# Migrating from SVN to GitLab + +SVN stands for Subversion and is a version control system (VCS). +Git is a distributed version control system. + +There are some major differences between the two, for more information consult your favorite search engine. + +Git has tools for migrating SVN repositories to git, namely `git svn`. You can read more about this at +[git documentation pages](http://git-scm.com/book/en/Git-and-Other-Systems-Git-and-Subversion). + +Apart from the [official git documentation](http://git-scm.com/book/en/Git-and-Other-Systems-Migrating-to-Git) there is also +user created step by step guide for migrating from SVN to GitLab. + +[Benjamin New](https://github.com/leftclickben) wrote [a guide that shows how to do a migration](https://gist.github.com/leftclickben/322b7a3042cbe97ed2af). Mirrors can be found [here](https://gitlab.com/snippets/2168) and [here](https://gist.github.com/maxlazio/f1b593b0d00aa966e9ca). + +## Contribute to this guide +We welcome all contributions that would expand this guide with instructions on how to migrate from SVN and other version control systems. diff --git a/doc/workflow/mr_inline_comments.png b/doc/workflow/mr_inline_comments.png new file mode 100644 index 0000000000000000000000000000000000000000..e851b95bcefff033e075d76221c29f2541a38245 GIT binary patch literal 193311 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%0DwnsVR>tH;FNZ9 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%0ASRIMfchd`2D70m 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 NlddO7Kb^n!e*g;nZ1(^F literal 0 HcmV?d00001 diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md new file mode 100644 index 0000000000..17215de677 --- /dev/null +++ b/doc/workflow/notifications.md @@ -0,0 +1,71 @@ +# GitLab Notifications + +GitLab has notifications system in place to notify a user of events important for the workflow. + +## Notification settings + +Under user profile page you can find the notification settings. + +![notification settings](notifications/settings.png) + +Notification settings are divided into three groups: + +* Global Settings +* Group Settings +* Project Settings + +Each of these settings have levels of notification: + +* Disabled - turns off notifications +* Participating - receive notifications from related resources +* Watch - receive notifications from projects or groups user is a member of +* Global - notifications as set at the global settings + +#### Global Settings + +Global Settings are at the bottom of the hierarchy. +Any setting set here will be overridden by a setting at the group or a project level. + +Group or Project settings can use `global` notification setting which will then use +anything that is set at Global Settings. + +#### Group Settings + +Group Settings are taking precedence over Global Settings but are on a level below Project Settings. +This means that you can set a different level of notifications per group while still being able +to have a finer level setting per project. +Organization like this is suitable for users that belong to different groups but don't have the +same need for being notified for every group they are member of. + +#### Project Settings + +Project Settings are at the top level and any setting placed at this level will take precedence of any +other setting. +This is suitable for users that have different needs for notifications per project basis. + +## Notification events + +Below is the table of events users can be notified of: + +| Event | Sent to | Settings level | +|------------------------------|-------------------------------------------------------------------|------------------------------| +| New SSH key added | User | Security email, always sent. | +| New email added | User | Security email, always sent. | +| New user created | User | Sent on user creation, except for omniauth (LDAP)| +| New issue created | Issue assignee [1], project members [2] | [1] not disabled, [2] higher than participating | +| User added to project | User | Sent when user is added to project | +| Project access level changed | User | Sent when user project access level is changed | +| User added to group | User | Sent when user is added to group | +| Project moved | Project members [1] | [1] not disabled | +| Group access level changed | User | Sent when user group access level is changed | +| Close issue | Issue author [1], issue assignee [2], project members [3] | [1] [2] not disabled, [3] higher than participating | +| Reassign issue | New issue assignee [1], old issue assignee [2] | [1] [2] not disabled | +| Reopen issue | Project members [1] | [1] higher than participating | +| New merge request | MR assignee [1] | [1] not disabled | +| Reassign merge request | New MR assignee [1], old MR assignee [2] | [1] [2] not disabled | +| Close merge request | MR author [1], MR assignee [2], project members [3] | [1] [2] not disabled, [3] higher than participating | +| Reopen merge request | Project members [1] | [1] higher than participating | +| Merge merge request | MR author [1], MR assignee [2], project members [3] | [1] [2] not disabled, [3] higher than participating | +| New comment | Mentioned users [1], users participating [2], project members [3] | [1] [2] not disabled, [3] higher than participating | + + diff --git a/doc/workflow/notifications/settings.png b/doc/workflow/notifications/settings.png new file mode 100644 index 0000000000000000000000000000000000000000..e5b50ee249478f8d5cd3601e801fe7b7d93d10c2 GIT binary patch literal 114727 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 UU&3@Wo56p|3hGyLmjD0& literal 0 HcmV?d00001 diff --git a/doc/workflow/production_branch.png b/doc/workflow/production_branch.png new file mode 100644 index 0000000000000000000000000000000000000000..33fb26dd62163cc5d9436a91db508971e73fc1f5 GIT binary patch literal 21716 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 z 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%0DE)q4WS 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 N-bUzamT6dr{T~(-0Zae@ literal 0 HcmV?d00001 diff --git a/doc/workflow/protected_branches/protected_branches2.png b/doc/workflow/protected_branches/protected_branches2.png new file mode 100644 index 0000000000000000000000000000000000000000..2dca35413655a79768911db229db1462e5397018 GIT binary patch literal 25851 zcmeFZXIN8P6E=)T!DB^5KtaGp7Z8CUARsEBROy{4NN7nws??~62nZ-hjr1m62!swI zO(3C%kc8e_Kzd2x-FWmo&-cCG|L=Os{=>DCu=ZNBXU*Jm&&&#btfhMDILC1Y28L6Q z9zM`zU^sS_f#J~AzyAV0vE#|fU|@Lm{m}y@{TD>?aBx(#LDDj1PkT9-(=WZN(m)#C zS{&iG1p02*2uGq}Nc5c-#S3{{9WrGK_ZWhYD13=`qVCRc~-6aZ8;SrKLZ~B`^wm0IeqBQ4Z{aE zanAqTaQy#qV>)Y%Zf|d20J*!n6Nvr&{S}~sf`VG-StJsf=Va&Lun?)RxVV^yT1;$B zl}6o@{EFGPM|pg2g3@JOXWlzHkNRy#$HZ)XPgL|;%OE$8)cQEp z-d9#)R;2!vCk*?n)bjZ6j90qOR@q0KzqDX3DJi)zC=RVkm6J8npnN;bTyb9-K6HDe z@Q-9tw&yf31HHX#O)T0w^F8B@LFrg(TtR+*KKpVFEOlwLet$qtAG6hcpLsYcz#z}i zy_H+Uq@-zczf*2yPSSn*kp4*s#bzv$;pVRA0K%T)YT;d>e#x!U{;j}nV6%O-LH#aV`Jl! zEG#H+7b$x>Jw5HMlIRJ>uyMN0&zT$+zTL4JH@BOv8sR|M%-)imBe<8nShd1??bB&x z6-n>;ykxk|Je%}gvSS<&yKB=a#dH%B6MObXG>kM9rNr+&L)~2`a)sT<)jAic?C3So z6g)9uCEqtVXh&oDck(YSdEGHtlFiNB+ao?j_dXg~dhwD30(;`HS)zKINS9B=)~%*S znVFgJ9C@Fu2ieldTQ+LlmAxE{KFoK1-tE@4z#^r^?DBI<>==_m+R#wKL@V?J+l{XX zZbkn)gR3SeNM$Uy?@uN2!=;8*YxLk#&N4!R6jtmIW;O24)}VF6mg3rvYtdlAk9sv)moVN^~(LX4afP{)ccB*C6pp{oLMyN@SpcP=QVEy3cM7PXaD#CuP8_@Rlt3nv$X|U ztQTt%u)98Mi)_G1hO8gcz&!e=@j>!ip6`nLEKFTTdi2q8 zn#wxG1WcVDh5B8PS3EaxEw6&U)2dhnotbKjqE8&mFN9UN<^4tL@egeEZu60(lLn5A zVFpaIYGqUr9Zc4-+m{!24YM>Py?idt3SuC-$;mi*gAUNN-!`J?Ajsx9wuSaPzLRzXSyHGeEjP zU1tdCRJS|gvqFec;~7Ql(~hz5RX9Iin`-kPPrO$*-8ltTlwPoE=mHX3_pwTY#$ zc7jLR(=@9gMLGp$8Z)H8I&tDxVFbi-1$>*Hu zNRn|Ldntv^?+n|BBk$~pBM0(o<-7t~6!j}uv0>-JgU^X69$PP0jmjjfJ6M<52^$r@ zf9t!azT9efP1dwa=wfLCDj|V`bm0^-2i8$)OAbAZ7 zF2Y*!xz2XtB-lmGFZ8K+A;IEVWyUurtt9G~HBksgpQ%~P!dmaIzIHo5#|z`{-B6Fy zXR>Y6(?_4ZY<={eK3nvpcrn*@PQp#oejDo-d-n}z{Gl{c@s8g2iFP(&(b3WI=p`&@ zMC=9%+dXe*XZKT1#(m)v3>{gyu;4&qD3HC4R_jTN!pvXdlK0lPH8e>l{i~BNlVYzz z){k!YRfKNUw17%y4eXy)8o6MyULB=%((<)MCKSRC6$ajQ3q ze5vYwH&%*`J7$BN3u|TFCLa-p?BkKD?>E%j(_G#YO2ro>svbIPK7|g(?5HQdT|3T=VX2jdc|G#*9JP>mU_qe_7e{^J&+ zp+{OX@~nX8!udFB1Uh;VW5*OVQ@Ru9$mKJN_qilQ0bb1JX_+3MwUjr z)uhVaN6VYVCZFnI{@u}&R~y8O4O&lgvlf?5=uC)y%*pM8pf2|3=O6ioQD7w#5C0#=lrRd7o{RMe25m#9hz<`o9* zJk5>6Ci970kkcu~0`ga|>rRm#d2xS|XHVzDYk~UAk}sD*>w1?0?<j7myF8;`9Xs3_ONH!A%AOxp^PE~bwbRJ`Ok#AtzAM0)UOV} zo-2k?1+i~WamGBlh}spayxwKGN2jb5*iJk6cf?EBno95@jyvT~18~No+;B7td;9ss zn=*XLyPnFSOU)r&3KZxWlnA)4rtvVYOdP3O*vf+#0zK(UlHZEV$_kBPhSbDq%9)v( zCTtBtYI>zFO2a=-8_OaM@7Q|9@kH7m`7wAxC5(LAF6ZMl)xFI?rNMdCRJEhD=V}(1 ztgWglt1gZ|lfJ9nE(qaN^siy$JbL=lJJ_VHOzuee$2RmoU#VFSe=W%fX}~-?Kz82)Ptf4j<+j6B?)1E3Yb`#W zH)aup2a^JO$kZPTp<;C}_G#NRep;l0;`KgI{YI}kJh$dj1njH)tta0YqOoBY#J_`w zj^kgq6xHqJx+o8qmd|UV3zivyUWVLaMso(t)Q2Kc0=SHff_ve?q7BJM(%x% zF|@FN6~_dM7oQ(7rl-E^h#;8TL??exK76jeUr;U4yv~+-??fO?`&hF5U+d*1O712lC`H0=s`M;^BOD0O3CX@122#G7itJ%$M5t z>2jH=vf3;8LsW0rT=F?VLux@qMV#Y)}X#>9t^0zq7^@-`aiQw~1)_YWW zDiI4OtJc?6TX{T<&5r%ik?vvtUODMSVwqz?!s1}Db^F5k2hC`mkf_+$Z0JgVj$UA3 zASl$>-=7YsTW69y=Z!C0LR+$gtdx{*#8zE0u}@nOCjJx#Bf}K=jyINO3(55*^YZex zg&AmM-FkpE!dF@^+7)#r%4N66CYd^YKigM~TPW47R}t|ZEHbY=2OI1A@!26ua6{Cx zCT{55pLvaK?HvocJOBh41N|NHrw%aH0vdU|@6mLmj^jg8HJ zJ*nyHf9xoO?tdB8|9b<7@BbiyEC$CFl$E*v_;6+W+4j#yX68$`o#(n!7Y7O);qc|Y zqxy0bASSiT1`utXdaUU4z+cf|cd!rRVWyLDH=lk70t-ra-JPG`4g&z}v-wb3Rh8_p za3AaDpml^42;_2?fng5oEdcRZEGS=Y3sbnOZ(y)FKgwc`;iGJ?%pU*c&c_UwS?_EQ zxq5kd`B{NC!brWnx>};}kIhhX9R`LCfBEw$leO!#y-jsG1C8idv^ksrvI6~5^eU=l z!$fna&wic2c|l>}$*BppU2#sM^9&5N=Gv{$1FZiu_nefU_ieYi4-|&+OA5X_r+4IE z{R639Y7Gg0$;kn5Vn5)mmiS?LMn%ihlzRfNMU^m240m~Y`^3%et$j*PF4AT$BxF2_ zT7Ego^4oq_M1zw7c|8#~D@xpbA9uT+eA2ZWF{NR+igyzc{_r-x) zpY1B!&skYnfE4H366HJ>x>)$N&YwTObL#a-|Alc>PX zz>w0hSr~^+Fu}|q^l`1H{WyH)QiIHqbQ+~vaA@O@=kicx@%{3HfcYph_XfV?NyA^; zkqYDX`p5H<*KOa1YJT64nJI4N4lT3-G?xx7ZTP}o%(=CR2X7Cz{gzF!594w0YsE$CIEObW-CP-{^ia9VzQj)GVrV}n;iY1d*|_l;bUB+o^(|+ zYiaLnkEg))=`k1ueC7IVm$v);`$xB;jVyybHUVx$xG^+oj=Q3k#YSei#HM4Q2+UKx z1q8R^w+j|V6Q2jZ?ooiPyM3Uo%h`5*9^~rZ`9dEs#bmMMJ|@?0kAx*RLzJ`gI;f|Dk~rfEU`%P-Hc~ zd&u%;mgq1XRoO^um0N8%ceOq=ElWVfsS4P+^L@6#s~OUYVF|6 zilc^|zg|$u(I`b^sxIR^NB!_?C@IH*>uw4V%mv4I5lEj%sNLO(zS{7VmzX-U&v$jq z&s?7N7kE${=GW{6crt&9#J^#PHPA^;&<#^FHg!g`Hn|BWgRY^GV1VkG@Nr2Ay}6x- z2X)n{7IMAUycEW>S{0{hV!Fl$8>xv;zRtifXu6hM6yu`$XPh3tw$@8I%iK6JumgMIH0zTnIGw#| zi+XSEq&tLtR!f|IJkmDjf{e?v?#i`@1DIQz3*H)z;&{-r*Q4exq2$SVzI5m8C!yrm zeyi3X`CWhyp=|R&unC#fSNBdAgRiikGI}kW3+&S>1IqoSBL;qzJR$GdQH(g92!*{l zfDNFM1OtvT8-7TC#WIUU&c=o|t3)5dyPaObG$doP97hkAdzKulI+u7` z`b#>9gc$LtVG&1e{ZLfRGc4$p(f#CzPx=xkMv6|s&3pHWuWCDn%K!}XoFO1idBETX zT&0Si+Q=#V`t_!V2?g^MOzh3uGPqoSa^sGtD*ESLbjtme7jkc?0# z({x;+lmKVtixKq;m&;KPZjw{~wnFnK`|3_gSN$a=^|)y|30&)4HX4oA^fY?y!*`4y zKOxS=5>`83pL^)l(#VgE`97bSEv>)mg<%CuxD7CFyyRU^Sl*|3eGEj$?(yj_^@}4P zedYt%EuCpcjb4Md_>U{V9Ia>nI>NvppObBkKC;aDG}(X8{^ylmFi0Q>AEdu+qB>f& z#{Ur21#4(MzaJ3JwxJ4fmi%1qx91i*C_(qkr$)f{?kphAY)B98Q*HTxAktU$7chNU ztqBBz+FV1_r);+cnL@*#+DRW-@}DeQ8TL*J9GDMCIGJZ&TJ=T2wj; z&5QM+7>H|<2?zU99ixtronCSS{Pd;UH%-4xwLURT55@L!A6JC*nGWR^>~D`E6iF}N zco^#kNBh=Mb!4rNf=V%Mj7IX#KK!qEP|b$*yP094)0&(pf;qfUU(_z2qzT_<&98D@ z03Y)(*<+8$j3lUp0T~GSEWpTsQqW+@88Jt2&65BPtK%{XiF&Bc8#D}%y&`vEs-(6CBN9**=-98QM4mhwo z<~goZ8Fy?KkJ*_+!90#A~qL?Ec= zOfH{|21dumD9^*OBn<#R09S=$X2@~L@Z@9>Tz|i@eFB`XhC9`%ohW~Z^G&zxYDY)D zZhNxpFr3kDCe&`$sb=+Uv_s`nXqq?@mYVeZtm&uZ50%%(f`oh0L|a!XU4y>E{hB(L zrn|JAz`=BY6$ZhG`sU`JC)+G)UOd0Y6q7FpU%6h?Q`Nh3pFlKzI4~b45|`iEoR{!W zS*P$X{AHK*qBnD2KOCe5HiUq6HuGt#@F<*X|B2Ep&Qk#U?@qtDS~=MDv{=wc5`uC- zPlp2C4T`wHDcrbwd)aY+vz$UTCz#K+Fz;Wm}}Y zu=kP3M%GrMK!(npzTo>$kM`9~T7!zSa2k9lhhX9Ie+o#}L|E8Raal9y3SLp=Mt_`YDfES-5-Q7#- z*;$>ez6t4A4LI>TA7$~P=p^lAfCnpRg@SY+EZwL`d&O61Ug@&AV8Chq`ZvTZtTKASf+Ez+ zH+&23QMR%jJZEE1ulSho;)W?clMXWs9Xu7<0y-9;LacGrp;KWAX*XP5E7Ib}tYdS( zd~s&gX-N49xCe795CUMqsnZI_mm-7`^%w{!ZrFT42uas$#OF;{@7FGYLaLL9l@BNH2kt$Vu#pb}NZ+r($z z%g><*4;uDP$I>raJX`3`p`V%kojY3RLw3eT*;sl1$0dMyBXz zQj(H8!UiFeZ2F}r05ffG174ZZaey@Ci|U&^3Vq=)PWHFTD;7J4^&xpQS5`@!y{oMK=Q%;39GRR^Sg7Z)tobM(X&AIr#ol^ zK$;4K!R*GVI4rR%UV@*9JayYyIJptJ42HerQ_3lJR!T)&YM`>>dFdu9!NZ*k9-|FrTw3`ER6D(!hJk1pTnNDqxg&QWbA?bHof zgG-rkpE1Bu?L$6Qm$Q6bRQeRdm@Ou|_mNeTII|m-(Zu$bX}2BufNGo*_EyJaMHJ*# zmup`EI3ebiC+}%bQsRB49?Av>O(SUhq2O?s0-4Z?t1IHI6R(`_a#5SbiK}ZckSda>!1c$2bmJ>CvD=S zD)&X|$A(QVX}?Emh&>*=A$1O-PC&5dvTK|l(N%J@P+?KbvSv`hd$0D-;-|Nd4=vd! z(7d^+6Gh_}4OpjwT#wtQEtZ*uU9VGUKK}nd;tjMG-dsn>CwQ)`jgqJIj5D^Qa;ifbS{3bJ6CZ2u|!xcZHu&3 zgC1p$u}Zw-)jRsJA>gPJvGh?R)Ra0i>N5`nQiLPo$eO){yp9-=K5-rpG;Y5pOBa<% zJtqx|TjsLY`0*r73b77-qw8tv-fmc?b8Dg8bC>rY8(9=e*wAU*pIbucpQ=-cv8T|D ze&PV=;$s;T-|T-HrTvONdlMF*^6KH7NwO5JWh-883l**qOSz_gpal%h?CmtmhA3@? z_}Ex>QUI8RE%SX-NV2cLLCmSa3uORuebX7u0GODAjhy-}iANf4eve;hq(tl1b8)kv zyyd3Uhwlg(Sy?Si@feR$75OQb5GiJpdy4G~A)%o@Yg<~No;VT1n`TMj**q7Sf&?xY z_9v!aE>$-U?0l_m0Hhe&%99uv?rJY6iz8<$6Xku-Leti#V-=Sf;0V_L8((`wT|u^DpZltnb58ey+sZCLJ^EF9IvN&z1L&l0zTT%RgfP zvv;nA^2>dV!9VyG-L;RJ;idr2yE9oac_>1K>ph<&KxKRtXH7r9U>vW%_E4fdiYg?! zpL8Fn4n4FvYwu_c;Iv+Xl(*X4Wz7#LnIZ$-&jOH1aK)e$|mmIy!= zZ9eMD{#mtT=*FXIAO*tZU)Ua)rciKCST~uGk@0ip#U#19xt*wX>JUUA^5pTOENBy#J7ZNBpl1^YPOFXqNV%4veH-|5aFtuEy*HoZ z1Dp_GV6Ok67At4~w|U}#vvC&wLw~~+t5^Z!*4@@C z*caS4zj&XW^S?&EZxjz#avX_I}2bkRA4)*E4kGI3I%C5^}w4A(UC#J44F~ zTsiVzGv=CD$`jLRAHbg=x}Br6Z>szgVjR59Ek7cmI2(M2+a>W$fCWb0L`b~RZ zgH+Q%vqq=oou^03lMoJlmDN+Ji8xWmRvF4~C3q=1#Rs`ov{23L$@FVX4><#AkT9&u zr?+hGesxj6x9H!$WSBeG1Z{%K2gi{;syZY9+Ogu_tcJzwxUjea{MjeB@W~RAMEXUZgfkw9t*0oio_iwDG zGC5?mm_9h+vH|R3(4S{k5%*eJvLM@Y#nE)i!1@vBa_apC<-ZSx@rw5e=)o?{ymHcYJVy^h`#Q)y|bHQqKOreM~!sy>K;{h z6y`JwkH))O5r&m4V<48O73fZ2c4crH6n=S)4;z(Cr@TJ$j_~==mfrZ`RUF5UshHG>o;e!Lm-< zU*_?1d_KNys3Ud1pwiQ_S1{Pn~);3jy(6YoWsBJXK$xj}jVf*Uf5- z)w`VJUs!hW+;-2*MPD%sYMz!lstq>ifE^X1zF1h;&C3KT>WZu0W}VozS#Z zXV^G8LhwCz5Zw=98K8>I8Tv+-HT&^lv|vo=Fo>jTwuPCFMEl)%H;m>ECk(yjz!xKD zjDUiLWbStUf294uKBmlk#@h8Fsn&YIzdm=X8<$mXI8c>=#r9<#{k6EWs6cTu+-C6{ zN0H@xU76h=X7OlRS=p2CFRaj_S(y3JAd%8bjt$#E{*-AoO14k_h`a8*(Z+5>7VK|_ z4iBz2&YvSoDe*q!zj2BK$XoSxI*zQuU+pC68$Y_vZXsaik_zo@OFM0ip3e?7nu^X_ zwGBE^5(e_h7bGuCM9#k z{x*8lE;xLkBvYMzad$4HlSIBRZaO2iHn6LqyH0NnPY5L|DR#8PqJE;E>d7cZc@s6PIxnyK>J+xuHq|9pr zYo8fi1}9PDG?m60>y!G&?|y14?)!2rFkukmR5U8+ao#ZRk_byM;dJx>lT1cpCLaFBkPjvL zgJfrQ(PEBUU>wS1e+x>|f}VP5q{!wOO@!~)s!{a)Ucz8Oa0Tsg0arS+~o5{u;5*Ah~W z6)sHIb7$}8kUW;;EU5{t&yY@}@xx!xebwGS`@HgItWg#y+VDy&d|epzUI9a$n> zN8Q4w$56VyC@HmVM#;Nw`tR>%2Xz|RAa%v}+cYU%yiHJ7JpGFZ!ltQr3ai&8I=F6) z9_tBqKo@D5t0%P*A_#>8pcb@_X;uj+5LP^CuO(-Z|6b&&h_F~RMcnNE_3PD--D$&| zoT!3I^Rw0RPGr7+wujp#RONdz!qW+hd6v@urr>6VL-#>6vk;7YYBFGYhQN|N3 zj`o5V6bH)k6M@tE(3X6)sgf zp$e48lOxep64~iuCTAXE1{l-CNhIs~4zZj&9(6yH{pD~7IvB{K?CCx?F&JLq&tFZ( z-~G5SOYyU%-X`lltz6u1YIgLeq9=bU%kSbME$9nS^^u(kQi`QW98&XR9G{V6xOyif zpO`%XDoaLf-E~_BN})#ITc-NOm(AQ=`uHeL6)S|%KZK^MW%9|GB^fPGh_+gxhn30o zP6ACtOXJKK4odNiO0JRa=pDP2X;`3v*@@f*l80Ke^C-Ar$kLcL{wj~rU2_0BzT2yP zdRnaRv7x^;F870aIHA*Uz_z3bx|a>2Jh7*kNfuQ-b@h)P$om5U+QgBk*a=R$2JVfU zV2C4!DSZZ2lkBIP#$WIYyu8B+TcHOG4RWU=xZ@SKLbE2WZkt;_Rg#4CHQDoqvReW^ zX*a>ZFd>WIqMP-IORi9!oO=m$(=+!biXTTVyQo(bPA#2jJ&c!#010LHO&oXx`$SUi zOvu`ryQ6S9ypuw!_RbMk9Np_@veS52`;1gnSf~or)5P05be;WF%i4wPLnr33QY!f! zBY3KRlJLTH;89D~>{^AdV47WR6V!t59U+Ma-3@uHN1qDPZ51X}j29VZ$WlY_nKg%T!9>Dn*{%d-=o**=TG>K)7^jyF7+BD-nzB_OGec{0R0T#S$;`EMi9&Uni6{EFJ+wMtRu+upk zJG$FES_>5TK-7Tv2Qm2QM4S0%NJOOa31SpFhK)U2uu$(?7C(pRvOzCxwSw=P zp@tP6ql2c{>Mg}qDS?O83cRGaD$n`?=zjCoJ#6x+HcIG6R&SgbfLj6VSPb{UWc|b> zk7yi<&h5JeOTST72yN({Ru52!&a8~p7nmvEu$80ead=)+U*|dHMdnpuMN02FI@Ngd z#;s}UUtq7-D=1J#LG03Q<2Br&N~m_S*+ciB^OsE?8NE4EhmLzx^)P?k1dF@0G_uYIyfDGw1CDPpx;!377tpH9PzskDAikHSqjY%DCm+RLM6C=#Sd}6PEEUYl9CC`Y~-N9UyAC_E8XoTpwnz3@CDg{T0QqrRv6{#+> zTGN4Wl+>+fYvWe|_e!{h(unaDCS48cVr$AWr#C_U_ZObc$D|*7N*ZfhJO`TG`xadv z9|^FG^Fyy4K#I3CK3qZtL)1dGNHf_sh9xm$rWSfY*>IBW~#<>nPSma2ZQyZ(dic4D3b zXU1krsRR8!DzP#Wm6AaAHE4`^oy!%7qu(!rcNXn|itd0Ql&Qyc21PhfIk#|;o)&2S zSr345MLAJ%1qr_wn@NrP<>>0O*rX-P_ixb`(n9u)jh7R+YIV151H|a{*V+TduO1-c zyRSuq3B=x&Ms_M+}=0Z&oFiss(r%MS~hvHn)-l@{kN3hJ8hg zHj_4j4_ifqXmWPo3vvc`YWbe1-~KE1Z4JV`iJy4R3a!8b96rdK~4 zHQyUYZjCg>k2ybsgHBzW-*dkhN0X41Cizeb?N$Ra%XqjeI5<%z4?H(#o?aI@5JKAO z-iio^?yYx((kE~K5y%J2GbkEfk*!X0HiXkDv!pOHWo>UIbq~A8(>g^PZ4B+dd_gqw z;J4T3fXD<>Pg{ve;J`nf53xBW^LgUU_ac6`s|T9k(CV?RJDd~s2nU6`weD0BM60;Z z%|P;qwEO5&6(E=kcKm*VH+jQVTiWPTbr~2bE^_B~8B9q>dsj-gKyli-(8;LpkceMdqajv%og2Iq|=r^lksYK4n_EQvix&G zQ8ES6n5q%m0u{1yJ+au;b(($Wno5jR>HhKYTQ@av17r7;#D-iV^jOK`6MMqZ#eh@3 zU0IOQ4*t0j##q!Z$zzt|F|g}Wp> zH*oF~mKB>c&*mg5aw?aQUw2^m-1<^J9v(r`X5ykF>SRRX1hJd@Kn{)`CFJPi-c<6W z`v*9}bX*iY+$ySUYfhoPky0{er*tz(ma_V|KgDD{%%4*EoFy(H`7?bjH*i+HgL8j_ z5|C+QxRB$#tL1u~`QB^`n-<}+SU(2gq%r|Nr|^Tx{T<65T~1 z9i_am%#PTD5WQ_e8F05`$7*pdQ*>Oxi)HAdQeHL4Iu~8wJvy@WUUQXHiF%5&aCOBo zS4%#~5Ps<>B;+vqG9h72Dz9`Ezn%B>Yi$?W9IKM;)@3I`)CyC!*RFakgSdAarU66& z0D`&#S-homB>~7$?;4&#DYFyYCBrlVZCy02MPtAJtEA)4Zm^ zbn30v@R;Qu-qZH_25P0r2LI>`s?uaMlJ}E`V2XnlX8wjqby6G7mR4(}trI4t9)}d+ zl;%OYRKIz9(9rY=NZW30JMc~3Qv8Qjt>_ruHb8N+#pPI%N5tJZc-Fdt%#BOysbiT$ z#3i5k`cu_e{MJFCHFXcom@hp7!Wf)^I7yS&(L|u3#H)iYX=9oYC!*q**WOIbMs%D* z%|FeAw3%&Y|5e60bS4&Gj?NJzG1*z6x5jxr;Xu(Dqx-^;lpZ;8KXO7Ah~PIGGRoML z3P#tnr4`zuODK#tIys7#m@>M#mjOsu0y1-SgE616p=24 z{d{Dx2Eo`D$&ECD=Y-WA8>YuHlsK;oTLkz;D3&>L!u zuYex8s#!6=+k;{quIG~~FLiQ40+mN>vUl$1#4OLG-UudmpG`~$cn_a& z3*F5@fC{P1Ffj@hW=^WhZLj3fw!oE~3Nuj*%sWz0Rj709GaU*G0~hVd_@cjQ_IiZM zelFCAi14qen!87sIAJ5v_38TiiaG8+ZFVuZo0|U?4basb_rICFB&drH21Y+nuyF5T7pPc`CR$xV;`S z{^&i|v{@kh-;=V}KR>M!G1bG*EovbIoo3C!4_Vvfta{KbpV*v={uM{(k*33hBIf_*$Wq>U>>JRIa{Tyl zS^RyV!Tr&rM^65&l_vyac}`T8zXuv|76v2&mw~2^^bTW$i@@*qItqJ7Fg26tVg_$? zBm8UaCdWmWw!Y;EFRbtTAhhmVyDqEetXey1<6o~JY6lQ?S#_&w?B8f`s^ z@>Q{;6a{~3g`5qiG<>P9Gc+Wm3T?}qLN}WMY9{BJo}7SM9FLaE%v=^y7ZeX=Tq*F@Tb#&EZ}?pvE(wlyjr} zBw+!IYLnV~KsiHLg047cY%kL{{j+94HntAbMIB*te$K6==i2Zg&jI=Ly~^*s4vu%M zaSGctOodRbC7HjwN^C$?Qg^JHfQsai$>sM1xQ(scQVr4y#9Hird-16#65k<)pSzAI z&bc5?0(#rc&$t7%k?d@2#C4#Byw-T zPc*^_3mggI76LYM02$3hh* zqzj#(hMQ*mRv^vo%y{=S>`<+L?BewyUcA0WZcyJ%PX`{eYL+2u;BbT`z(7&Or~Ai% zwwuBr;H-u1XV%C;a=rIvuJ>cg33kz`7s>?S;EHm5>_`X2&5@gM z(@CK8siUKVX0*PxR(g>LfvEsiuJkB^VlXs`HgVk8mQP_xot!+~Jlzo+V3ZCN!Uk}a zUt(51gR<9yA>5t)sjI5xIsS$~BazL~0>Fft?VFkdeKzEK=%_D^%Uc^w!7RmC=vdR6 z;5pfvb0vvQoz2h%B{L-XZ##<+HuD=9f&`ajgU%795MlFjQ}yq1o%P-Di-;Vj%x!HY8_Qw6U zC!Z_zhjYojHj?_5<G0ZoADN$hA0E(I zBw7hol%?V-;W*1#0okL-&O~t3Y)nWCbYTiG4?VCg!7>BRf`^EOAO|#rFn6_w&Ea{X zc>(WJ0WGtSqEL+55{+VHt?tfE2W)#m+yg z;Q}qBcz2OmLEf!yQIFZEF9zt?pw$BO`VqYp`&Dv~XoX}4?7b!q^fN%Fed@Iqq(Ekj zjq2R)-7)*RwT>m$+S36@n&R)`oamOu7&Ls_zP^@N@l9LY%uU^Qxv;!f>Qrm2v-Bt= z-ep}x89C3k`$;E04AnRaySj;$9z7Y+4YfUf z^0kzMqdTW`RZE#8p2_SB9B51BA#VXaYADh)`YDk?GvIt_zkP@%FFI3f>^d=-%3+ z(vxDXahmMU+}w#LNY~9Q#sE)tY`BH{c4RK>-6fEAAq7*>Q z8J3(z>Pkim8KxFkArkod)j9x4)d~yU#Otn(^)?|m&i7!^#?8s(l zQSDZOO9fCoeZ*1UVY*1iNf&Wm^WZoEZ?P$t4`m^?NnU)$=Ez*0+y-2sHR~mTM0VP3 zzm?kUv@%Z_Zqa{K?s9bU2308HvoKKg_o+>OYiq;Kcbc2SO?>ObD(HnZ6qEI1K~cdh zvOv516X^oyeci4vpGD_X8}+NU8g1bi!?bdj*5J7r6Z?cJtXQ4BF_0=SnVD_12gPM1 zIklxGXnGd1f(8?}Q^JO&@v!B=>ZvLkhmZB}CKSBuc{V)h*z)u0I1v!M4d{4>zq54& z&SNQ=QWpSKt_nUVMpDo#?VqWB{indg#Wdio0+f5&aOq%Hqm)ox3AQgc{p{=bJ6?0^B>E7Gym!+_m2VHoNR*#_X=5~m~6W{dy$+BeO72_e?HK$T%hHY z_9lkQ|KmMe`LR$+dM97eL(3QAjB=`#JpaBk5@15l{D6uh;jXo-&E;$+nx@5+A)Id5 zg{F;+L@ONWJv)Vw{<9mU-ZNxM!#yiDaB+NcW4D`S+?FZj@ku_4i+M+L`>>u z-PTr1fp(6Ute=i}1xy^;u7NP?lJ7ak4ke>CrE?5!W}L0sU_^wTa!Vf?ri829u5v1T zq0-O`z&z?Vm2?;TpWgycu(!0FM9Zgn-)O!r#HGiYAfE%9bB?8*W7|x@?T&-*C1m>P z%f!Na;DcWqiXQ4Qb9~nHd?h0B)tJ{fovmNfEKCN1dr04~@E`iJz*Y{Ur#cTaab{%> zIe!G&UOQHA-;RbYB{#_QGTG@#?rBow-{Uww$LI5A@%gUfI`2Ly zro}Rv8bR(lE`Ju2LO|*uGi?DsFD)_EYw8LUvSm8xrIL43+-2rY;GFPvBZoIgG{ab<3=R#h z{pUb>Xo0fs4OumQmYOE%yY7f@;kHcU*&hni=`A{{Y;9EZ1h767Hnawm83qdGljJ!X zc_O1!?XZVn1 zE_!Np4M+%YO^#E2=jCT2_k4jT*Yai^lgR$=N!CjcV)kk>9GT=71a;VkIY7nFjSW>U zcTWi+YL!cIg{T(K2O%Dgf^T|cdVr+Eej0+IJs2@}OYE&Fvk&5@wjAFVz`h@?>*lio zoni=o{)J>wD|;XC5wc6Q{fpMkxz9=1{qQB1wf}pomcc>aZmh|{8?(qyOJC2-^WZ#g zs0=v#bgW`A&ac`5E3>%2G`G!(B-^iA>$Dp6Vxv+)?i-FnNp^<^1L3QQ-#m(O=h?=I zw6z1MRmg(lZ45}VDV}bSmYg--8y%?S7wkI28WXE8*@ib9l1&)#zeB>DA=p4`LVTa+ zd}9Oe*J#V}XX!a^VQ?32dJD0bLZ;uNpjQpdm0UW_xgQ-sBaauM*S5u2)c|Z#5eMR4 z)c)oiM@N9HJ3_!`B-?t#TUU8k%VQ%n?JpZ-LD>lEm;~Q&eSc$tQx~i4!4l(<3^;t~ zS4ccJroMw@yuaOaA$!LfTOHaznVb3bG`fs=eTC3Z!d=*Ba%CRN0^Cn&v^C^;W2rj? z(H}sfWv>o`N=Ov%Ts2KvU`_^EuX^d2#N5W z?dI$PI_!FO$Y;A67jWW-`tSUeLHa6WMgQ$#-`0XfHz&~T^>^<0V^<8bq+4+=9QpNS zkoGjY`~|B2;l`6gh4@^?2q-MgwZ}^OoBy+To#z9dp0-CwFZo8}^f^qr&whmaGYNwYU7ti*D+|Y-i zI-r5ndAB_-!Pk_{e7xeJ*+yq#FQ$lHIxmpF4u<{CMPTLC|3DZsbN(Q6B-XpdHVEsH zK*>whC6S~VfqU8E#re!$yZ1ZB&kaUw#8pQtM*VI>NN=Cezhm)~&!SwdbSgm4RmN>y zS5`}*M$}6!8CvNZtd6?jiajgHKcty|8BkP)3XG-?oe!(@^%h!|vVrVnPu0j!{M;Xg zN%PYq*Dr+9h(`hiyY{6S|TZ|8}6(Qvx)5*J^ za{y1-AX{YVJDo-{d!Mj|W3j1>uQn8X*WNVw!&+o7=BJK?R3V;CCg&+n{$0IS$h5L< z4;p7Ce~$z{fTBx0)>&E^U0USCJS9+$L%su9++8v)#1=}{O?kh-O7%d14eIN6>gE>< z)E-&*6=n4du8Z2`v(-YcA6ONiPr>U^;wZl{gK0={0Q$t$+`l*yfx+x}#piq8siCIU zNGjmWB>-HULAZ^05%iMZpf+(UQ<4V0Rhg+FPWUPGA4=;cP2gqto1xSA#;%jKJwGWvO5XIiHj@<(C z^c_vYhPHy~(;kEb)f zc1J~o>Q@mFD|x~dIPS?{K7!4s?di63jlJ$A5j2|QBL|-~nZbX)R~syz(gJ{LTw~p3 z`afJ^qC+BpQ+z1aZ8apEc$JY=Ebkr@$uuDwuNU^C7VD64 zmIs3uL8=E<0_k#cnXZC7F zf0UZFI%OE75(8=!T${Jj2<1_m1H_Pb1}*$QJC#kG(B6uptivOE--f;f)jy-S_$-jF z!OdU2B%AXAOJrLw#DsH9HK=y!$DPimZDqfTNm<`p>ypb{0Sdb_9UD^m}*uKXaw={pXZCQ=2D1HqSDyZWK%j7b7_#dKc8E*1xG~?o52| zIsEm@{ZDh}FUs9qcUjTrjSxXnZ?FAj7?Q4W%JF#x)th>-L-tBEnWZ;Z*TdWe9fo<` z#2{OlJ)?-+Yf@{#_;8)vM%nD$L)ePGSfO=vTmfR;YCUf4 z{bjH%8F-R;CKXaHX28}KpKS^mx<#Epe(>X3xw_1c|Cw_?0Jx7FOP$RL_{y##`cM9Nx*IE z6+pA&n{zYXV=%#)CzPe^1Gyh$qD-`sg4A{h!;RZ!_af z#Wr%?<|?wbreBlaua7{(?31=K&Jf6sQ3K`3z6s_*R_<)p7=7=dYR|9p;e^?j%idoT z*YR`de;kbo^9Qq=UBR3tgQuiQm!l5qGmy2PrcPU2%sfI%DnRSkl>44E66}+yg(7kG zPi%A~(2rfiQjrt-Y53#GQ)U7z??0ld-MV7*ZJ(b#DZoFav-wMpI=1a2twezVdSTWX zDacWam4clrK;Xd8+U2tnFO@WORvHo-dqPSUjp)^z3pLLZDL_RbiNTkfZis@@$nFD% z1i7d_^l($UopW{>dH58D zoMkSMH%qjoNMGJ7vojKL#V}@TkQj%Rb9)Hps6;0M^P^D>?!NN&YGuXp_U;^FlITBU zQ}g?Y`Pdbqe$S%?cH>rJZli`F%t|ON>S~0~y~5nUVC6r_K!cF3o4~Q}y^#0lF{>Cc zh0~`*V_}eLgLivIw2D8M$ppL!Pnb$M)q@EdH^u++dqzTsB5APFMYi13KpGWY^{Qe(E<}$>2! zU=g%OW48Je1y$I5Or1UD9+n2zMn?QrBv;5}>EX{knc&)A{8a2<=&EPya$b{ISKI#Q zD(jilUqim;35MgZxI)VJPe=WZfoKD5K?M-oBxNi2+ph>3J2Y;N*&)#>EbJPaKy|j; z$&6yjvrd>Sx&7aP6zIw5*F(A*3$8A=wP$q;cYvoe?o6bfAt)g2~4{$!o&x{E?xnGwQbt4#gAUURj#H z^V5&N@n;>eF|n))zXIH*qil&)JZ-0WY)aUqk~LdRZJj=z1fhO9uCu2QcGax0q5#%q zLvXUnyQ^J2vN7d^CnN3s8M1K#UR=rQw}S74(qWjTGNq}uv9ACi@O~8;_4^GDc4HKm z^tU5O@l(H$*UYF5M0>HTV7wU!`Z}6R_sTyv=n%x|k{%s>lp|)p_9~z!efvf+I(@?B zy-UBAXvJkPi9d2=?t+=rbwy+8Xx{;*sVc8z&=%rL_geGebYEId<=v@tzzM0MN4#(` z#PU8RVEFW{C@@iW>@CEIoqYd3Hx80eP@k?+J-B&JbL2)-Uep9MgtjzLC&J*6-ZJ7g z^y1x^TOem`ZX>?irZo}=5(0@bf9+kJUE^qazFfMc^uZFhG4Z8I`7}o zadrBa;05*Ms2$sOUJl}9awPoE$g!CvviFsZ6jT&m--#MSdQSohICwg!&9aX6_Tq<9 zh}TX0IE!KEU;A0>UFK3dKVyc@QhluI;*dmJ9iDQd_@s3Y(RH9SQ6G*`y)9dJvYeqb zadZHXq9k!tDucLx82IRd5EP9pA(U?XDrS9gRe9|KOn=uz4U)1UFTNn|zNa=9LPc+Zs36(E{?H!T^tU6!q| zwYy!ZS+0{2zhzsPeBjS9=E-DX~SHj+XXP;u?*84(DpC9-kzFTD-*DyLiVl&025K!1K-wf9+}@U!I-eJP`P@!t>ar6}@~v zbHC{B`uS^0Dt3q*On-X^!nk4j5amm}o-e3~Iv^s*(;|CV6`NaTy+t5W=X7?qZ!!b? zxeak#khtu550IMbrw_`Vql!D6+}g+YXvVCJ2a-S`q8u%N|u=W#k(Eo(?g2J90)x*K7|)<`X&L(USYQwo7P$*iM?Nt1zqh* zJoO@jZghx;0Xlyk3TAMAZd^43m-4F}LNLaA0-&)5Mha9soyT5pr`nTZmG%q>8*hjJ zXiFzY7nQ$=V8bs#n-W4o+==3h2Q|6!8CX2m_J;7&h3@At ztKQF2-q-ydYXb--f}5sN;PUq-;{m~WXGMLV8lpWhwF#{5!XmKHp7ER`UAXmASx&w8 zW$Q!NwsJmyjnqq`62`EOKCYp8FN8IC>g7unf2=($3o^1q0wA!U!x2YDhrpV=z|dln z-;-By^yu#F=;%w<=L>s33fLWedm=9Pf^xztK3h^!(o{GrN_2X3i8W@8wR1)`$h?1W z!!Qt8BE15${*>q<%B{s3F#)Nk&6}B?JcF(Z_L-d5?#_tT3v!Egxbv8&O;J7d&(b37 zw2mmf#M?a2>q)BvH-zezn@4w~ z)uOO3)4i4Ct&iZaHe10Rxt9IynG5?jW&>UM*z$$p4aRt<*K&F`0PE@z`PM>CDEDbLin;ThH5HsvkWkam$aLJczPn{tl5GpAh=rlfN5=qGP-c6k~50^l>lx7Z&H0 zks597OHRQSxVbS_q2sE}d}d}H#xdA@y14%zV1OAV`*?0bzU%fw)@QPny= z`7FC;F0;WT&(Bj!>NPEt+awpc;4%k=U1~PSa4VT0 zb1fUyGd}0HYVc^XdR(OlVq{>Ha5c3q6}n-Cs%2he&z;s#f33b=66v}9*P%&Jbd~e2 zxt5{bFau_hmOO3HB0?j7MVVVq_-uL&H`Ah2cHd>$n|!Y0n2rupLGS!K>}p>CFdR%s ztO+N#@I9D{5*3OobUH#ZT2XzOl=vp&+VRT+J~V+OVJCAXof0%{tTUbUpP54aW^bHK z?!?i=>mD&Or60O9E1l;NBdV(pj%xm~RzNcI*cd^ZZXl$26{bhbP5VCl*LaDaiLNn8 z9WvVQ^(|mnBMIr!352Y8F9|?GS^(#P7f7T?PT0^H0o_vdG ziVRzhlG(m_4S@Us92e0}_J6*EW()vQy!CN_#`C=otD$@wi=5}>`aI(0f;3rcny+T`qo}eqSn<@X#~LRtx@Z(t!odO^P7Fnxc-NXiu#)n^3SIpc?r1g{(T87 iO#lDQ|J`pead(m$MJ!;Nj*wr$(CZ9Cc6wl~Sfwr$(C{mp&!oO8c_z^{{`tdL5?+@~Ug5tE417zL_uPWDVCds+{<+<^s@pa1aLm`)l}mrx>&LhyVfkAh`^FM2#Y`NEE7Zcbku!C6_*>7q8EBS?vUR^{^enlTC&0~a zFyK61lrW&hZWOh=B|U&zkTKFnrt49p>R9ju#aeZ%`+0CREcht{n6cH_Q53&$Mz#mk zt}b5pMm!SzViJ*utZDDa`N?^N1wb?5r(g{G;$mL_01YYt>N7V5@ku3UM*AKh1dm2^ zK4>#QZat`RdDF*k5VzduAAI0p!1j9d+x|$gfJA!eY9Nb&Kytv10a>{~=O7KaI9Q;f z{FH1Ulm3$17|jru0it}Mpxu;oP&@(j-~rcg$oYa6u`EUaY5a?^g!`Z`eEf23NMOW# zBeCG|0u{N?a_~w(%6toQKkgx&5IDfJ{XKHQVnp&|tI6N-lcs|O+x z6p2C42GtX44_ga`w2SZ<+M#iT1k(>S>%G&#%!M9udu1#06Z4>_ z?x%{Uq#&&U!9b$^)qhm-6!L_3l6x^R;!OAq=TPPx%!}#`>oMz>B7}qmg=vMWb}a;*OfpM4ph%)9Q8tnaG9TWjn@iJ76-|3e)ip7nG?+pd zUuCK=U#wIwD6doIRstyxE}tlORqiUSnfIEXoo_5_DCd?RD$OVlEu&T3D)5p2c(59@ zN?B=N39gHo^eF|jOtlEI{5@Z~&|F2IUsiNdLcU0~#8v&G5m|PSmsH*$c=l^CmPLC_ zTC=o6@a^}bWb)6*2?^7r8k#>L&P-14&V?Kc92*>lts<@Rt?sQ8t+MxG9?>4+9-Q|a z4u(495&;%uEcw=-17bE!fNoE&Az4nJt(+tfZJ-SamG4 z484YcO?S=WO={Qni^;7lOw$cC%su*`Sn602(=S-g(gL&gm_(UMOk}Fr2Qsa*4L8j; zO?&FWYlrL18r&FFeQn;?tsUDAeVXd)q?*=kUxp=CD7q=8$~R848p@cbT4!IQp%(_1 zOe&=_CNwn}HT^Ge*4|v4+Vt9@T(VrY&auylAEh4!VP0V%V3c7xqM?T5h75+rWYkDy zOB!TKGI==HI88Zmoj=D0)`=$$a>us03fu$S-;N&IPFo$cE3}PmsG5MAylogO4-|Lq=2e$i zSDI#Am!~Fei*!SI7kQ_BGkj@96Z-a;>_UEE0jYp#ffoXc0ILA=Kr%qb12=&6^#Jy) z1>#PzrqZTwVE_rR;jam12Vn%$Kz}epp`u`@pg(f0Q+O%6sRDBwi1+t|#l&UDX%gTH zql=0ca1@x&Ko(%;iyF2YN*EZ`I2x)B=|n2VAPzPR?Clloo$ZREJX0;V=;`Hc^acb! zp`cT1bRLfyJPe#f%F^ah@6fO(`$>n1v!z}qVfI$;L=!n6R3L=9>$GdPt9rqH!hFVd zE6WkM#qFmn#WSUzr?+#vlMQeeFaORC{}Wt`@;vN|(S6J7AFx_L->ID+C6;_nrT zsV9=O#+D_uaMg)gkSQ-}d_sAn_H5j3jA-0kJM66N6e=TXD)jZdeoV&x zjUCL%bFNi;CsAg7vZ-CDJ*!!1X`RKDky6)PwTHRVS_@}rYMKwe5&CB=xvnmJM*QQjlnzH=Oh0lpk1y{#6uO}~W zCwH)iI?Aq~-u`&HSCAFf6W7L)!M5DSgas8FP_$4~=QI9#tfRnRlrx4M$CAUwuKg7F zLr5QTzBb>unYl=L5oKx7kzw1&v`z9YT$kF1+fL$0>A*Z`PIEGLaz6*1H^bAVP;mX` zRqM9pbCNhKW^yRI%Mt%v;Ax;cGKKap+TxB6=VSKZeOd)QaT zTjy2aO}H=jVY>`}D=&i|I zvm0y;Hj6NiP?p!k3k#F_z3{Q@vu^LGik4ni=NI)`#Fxh7#J=X+Q}H(HAF$lUAAZBm0Kj>c?5!C2 z;&%XeNE2eP0Bg&`cR6uMMuyx)!ZyO939~mhPsdhatbp5^SI^ICXV1?~9P{Ub0319r z6NiX-VUu5P4%C5_0acZ>J$_$dz5w&ez|70UpG5!wfLZ2BYEEj>QXGc1)-?J?wg$#D zZq|0+nJoYSmmA0Tt+la}KAxMkm5n2Z8#lo}dT@N-|J6)OfcK9sPL|vRYSMCeg0>FE zc+50RG;{}LLdl58CRrPjBCw0}LJrKh2z{SWVNQm(&RIpoaUjIGp!%&m=W9KU7ourV=k{iFZ? z=gI#`{4YxN|D$AKVf=5(|9bK-N-o;J6!xMIhFW!c0#UWMI!xe(A@j&hMe4SNF;` z@W`X8->6k>kgbssZvICBH)=CU;NU+U&~18=t70>xNEp97xbH}qlPPB_AuC%SVKP;!u-D@=EVhe3n;3io!TGiPFr`)67(1T-wwb0Xn;)jg-)f? zs4~ev=DBOthySK_5W0WP$NtF?hyQcSBjxxIYpCIWM)1#p^agZK+3emsrpvu&z9J+S zBnAH6a3ui#IRk|!B|I(98R?%!RO+MXG~k^uW%)5X^=oJF)1E3 zd>gSrNH1C8rxW&iC}s5=wahSfFX-Ql%SVX^S2Z$nfL)lP&A4f;CU%rM`@biPKtnQ*@(Iw^D#L!g?|3h4l z090b(Fum-8X}E=^$(@>UR*gDWd#(gKoH5s_yk)mmwyhCW{c8W;p7+;#Hxma==a~6( zifY5jP4&Am?`Vx`r3`DIwZWRwXQ!6muCm`^Tn5~=eXxF_@qf+2s}0=CZT<&iDnh&2 z>i#i0Y1zYPB0j5}-&1k5VHa_%<~$*7vUge2Qn(I%U=Rnp@l+T8&7fm-@*R^z@N?>5g^lL?1-Bb zi+)Kz`F&hA#Q=@0*l5kLr4jg4TyJFwXYss$6hMHlBM~u(AHxjL{dE7S*Z@kBAueq5x z|4S4l12-mcWsL*S>Tt~cPGRMW3ftYG+J9@ASq5HzoT~%KtfVR8QQ^SNS-~KsJeAz4 zS4@bR#WX-apPKdD+K*TM@C+5Z8G#z`wB$O%4IEsikmw&dxZ~7O;mi0em-;*=49FOuO|A&S9m0I zNvLT|#JOP=*{ad;)51RSct&n+?)>eu08&CMc*G7JThW-Iz}sAdyZA^V6oTqj-(jf@ zWDD(|hNB8xUGNR2km-F8MdDu76MSW)Bx$o%tC{ALzyC7_!U47x!(mut?Mvr6aTBRF z$lS&<{bCQciiDQ&1P&NfomC6lKeaDj{DG|$Ls>3KZ*C=AGr~$n@)D;|>_CM-#KzxQ zTZtoE6HdWmp_5o`Leg|`bm7uAWKxG+UQwaRpXU6w4!&J!+`V;>hF|qKumJt#&Ax5>{gzl};W%H<;0zZrlgfP=!Grrp4rUw;2 z>1E+;?;sZTWGca;?F;rG`@*QSHyVyS%M}5hY||s#uqpS3e`vwW%lt9S9A|Y z!d4NP>}q3wS*8AKy3ULPKh{x&gLv@};F7NpOIsehK#)!Bt#FRFOf*0g&Kn0qXU#*H zRFgK#)=rP%(__FB03l&gP~+%4hIA;mB&q*Wrg+(VrkMtft|+umMv;6>a{tA@M@LA} zR4`=7#a=v4`RL)jw3@QI_=>;KAf6pISEbFlu5&q4F!YhHLC&J3-D@yU@U8j;|1Eua zXnaC;={m|tx)8SX?W`*S+IPu9fsj+#MkU4BgKM;Y?NWceB+*QIiS;Xx5^CzF^s-Ks35RbA>(*ZP|$dt~FAnKaQuCztE+61F8U`m&G_F}@3F!PTwn@OG zg-8rkLM~q3Y2rK!KkyhvD#5UB9vxBpv#n83Ua}2>T4)AFt&8fmA0lk46>#?_B>rhP zkMz~)JUWHvQ%D5Qk6e~A>p#2$0umXtrH5yBxc))u)p(El8etB|ue-tzA?3$bWHIQ_ zHlm)=i`%e+@t(V^YznSODy>W1t?AHzCh9a39&25Ld*n^~z(rx|W5jMWU&DF3y2a6d zFI3Kiub|;>@clcJd=@9f(*-lqIZOO{ML5pYfF+YcTM<1Bj$F;w7%HumI&45ma$9BA zPz<_>Sq~Wxt$~19xCP&Sj#Wtpsto@q!&dyPQQLlISe>fXI{X3XjJ6_k?a%UFtfAu( z$e$(0PY@xgL}ou*Fpy#dh&wZcm*x8#D-re;!W~bh8=a3z#LUndKR-NvOM{Uho9t(} zXvDx)ezid!4qbhBX$^HV6I0KTTe>D4Jf-lIOj6o{xG2&ZkxudOUPwZBvt^0STShlb zN~IYpM8R=K8{Wa(!L;F!ikRbauC@cGn1t1jk*b7glP4LwD@q{M453m&VC^xA{f^0km`@(1lv{ue(J6*P@ znIPJs(LpZsgF|6UX92W2z7P>vS@<2=na_tS(8vd*J@Cj7mkFUeGjCtu+Kxkf21?J5 znO+;Q?sJwxXjQoU)*!f``6nbSd<8(nII8IWpy1PNGu7a&2QtG>G5jNBS3JXV#vijyt zupXMHy^=9a>CP+E2u~T0(*I#fj+QEH?d6Lvg>j(6h33Di0V68=gY3 zgMD2>)?l|6wf+0;*jkL^OyGEvWpas?u)m*%z1EU8krX%B(Ibp@r98T)kMRq*E9(QT zaKOcH zqBE3g)+*kqB%am``(&92A2w&1bHaOlI0QDT53h9bQ_DdUB-T(w4jW(tLhAYP036Dy zn$jq_9_{;qmsnK;X_K^lFjXw%=x~lf0`|g8|I;z3g_$6=$df2|z6XbLh%?pxRmI6y zgF(U73b?r=sYI%gVq_ZaNtgj4~EC9J-`v%S3v%YbB(3={P*F)2D6hWw( z^r=HLT6v?D-(H{Sn{(+CE|J-#tGVKb?lD4BfloZqNj*+jxS~-;W30Ucr;@TS1o4fc zmpz#ut;aU)rj6>diR}jw1gXwvW4)UJ-<(K4MyF1EFG_MpWbD_GdC z6a9&wv@-Ja5O6#978Sgw!3;uVr4^2W)V=Urr7p`o@Thx{q$I{TdC-0Dlw`;oE`e0{ zlp7ADedk&^lfSE-cS~@sb{`u2%?qCM#|b&$OV2`^h?x7@$Ul0Kd*5vzd;2-7kyFrL z=2m~$(5pKO4Oa-p*}ZtzHE=b2LTdkJRT(!8&bhwG=EjKjePzHZM}s!EAEZ=F=AigR zhM9jX`kfoqI9(h&2+c3~Rh4DOI^dl;rUah2H~2w`Vp!B!bbzEE#7+qLiG+#ho8iV| zFNU+;-i+O(&XW+ucerFKNHO}3h_XOp9jO?A5BYI}z}qjZ9Wr6!!`3dC^NQK7^SC1v zB`ZsajUri3%O;g|8WoII&S&gZY-F1h#T_mm1D1^Lw~i~e5!DIIko*CmDJQ$RGIAdp zLbaLO1gj>Or={U|80gyS9{}G%kC~#y<}^)|tz5}((Hj1Bop%}YNS!337I-iT`kOv}BoyQcNeKYu2$M+9 zw@B>lxo!qcEq#`%XFZKU{BfS3Y5uIj#D|yA-^^6qNe46K8*%*#LEX}=Tv>OWVoAMe zYs=GAu#(%?1{vD{v*j>qb?v6S@&)^1eZx1~;{b~MdVyW8uhv@T*q%MJ4UYR@Wah12 zf>9Rj3~bWn1+sj<$Z`BcwzJoXZZtTsSM5ue@L zK~0#2ESAprslE25GE**#MQ6oIW*#_tX?wSm1r5-&Wd|rUW9#*7E&ntzqh_X9E3@6G zDz>-1s%hhgn4!`Qru&hrF5P~Lg2q~3@_3`E?E${&?T!C?!$)n-`8W-FNtY3Xqo0$1 z;Q^ZmzABRo2Bz{w4%N26$9*07da&JBU<;D8w-{D8(D{vnRqchdb`Ol~rdS|m9{8(f z#~<5LwVPMx3;a6eli>{p@D1L+ZD&72uV4LQJ0ax@)Sj@)uWk)1Q1qzt_X^id+Z+B> z`BwLfAP0X)6H4O+Cn&b(UKacWrBer8{zT;E)1V~=3gPJ-#wYV=)P6|xjsg??l^yex zeY!uuMVlPH-TE=@DYYBXQo$;ttOO=>c3^%pzoATr*#koa{HaF1^nBvuk0bO3%E1P* zf;-^?1>ZAG=)?Nu9)jO_q}GK4P;w^0@vsWF^6uXYI;aDE$_Z`xpGn{@YSiUYRcALu zj0GMXiRO3UQhqQYxkmk$jyUVEjWj2n+GQ8AOoJa35ia~F?P}_^;ke5Vmm9jg%lwE+ zH!Sphy7NX2ur**VD74z!RpO3TmIBuh(dyHaAm%F)3wq z_YO7=wBUo`nbCl~+TF;|wCO$ZweOQQXin!VtlIO^QEtca5jX5Q*jneSSO(g|)g4@Y zj7v|zif=$cR%jT{COEu-@)54@3upVAF>fl*4b}5@@X^lDF;A~8M|1R5u&It02+Cl6 zsAVW?|C+LW_aEIr&cz;=oAa|K-w(>dhY5enk{Ca+FM57%&PLl9HF&8|w3~Kx97^FW zUr<^q4VWWc7j7ud3U05~Yhg9&Yv`twb9q>!8~WSeFahWC#o*qvI- z>frHvB75?%z&Jw2odjCeo-iHk^o{_LB}hUEZ!?CXEHfcNV3yIy{c=udI%W5SQxo=t zDslgn6DNMYE4JB$k(be-R4$kV>tAjakI5JP-E!VRK;Gu@1rH!G%GTf~oMy&p>7*}t z>i}R_>a&M2SV+M}Dr6i6RJsN|?d5$OC@Y(!5PMsPQi*gmhbZhJII;}f7!n~K?euv# zHsdO*(x9B{^|JmykoTUO_%MkWqu_q`yNS31ul{tQuN>jASkK)G&L>M{MEx#of{3$Pms%rmmon{qmX9MNv!EVw4jp_U| z&dT3C_45lQWgcE63Ijkhxh2na>T+8SbY~eo$C_cC^Q~zbp9R53>jqRQ4EdKP4X3Q< zahLTZ+VF=9^!r;!wcd7CLVvkj<+XS)r%fBesz}6IXZ%pNwQ6!-J8Lq({U(4IZYo$) zHZw%4t4s*S4MF&1+D3PqJE4+N+lTdnfVp{^n)Fyund>IxkYqW&z`8)A;l$55mjh%1 zS7~A_b{$ao-9-j35=x3HYNCelo1vlb%-1rJkMRIq?L#0qrF`tC>tl>AvbDk`zu{vrayk`O!uIdH6X9C zfJ9fXfH4C#9rDzPkYUPG_Z+_Gokhb#!>^Ii#*baH`3ax*YLI10^sRz}aVCMA4Iy0U z#)WOx2)_isp;c-{rQNF!RO-3@H$1lM^N#hgg?vVVGM>V_abB_;F>I5G-<+y$k^{3XmY9Qmcj zg*Jt66?j>6cXCaOoywgHiInpjz{@`HFw%PTMW3r?K`95gzUVWacQBt|Y(J3Kjw~Ku z@vjm*1CTSRY&Rw?51~yHMW}p2Rd9GgzvgK_yW~-^{C33NfPq{l841P>;97GcQ`A$~ z6x@AE)9NaQe3M{S?3iqwoR=4)hLeM<*sn)vIxk8QupQaDFFq0s=~H}zFlCC`n7eAb zT!t3Kkl1R31eBW|!cG^Bo5x7HYO!~}CbF3sn7qEVfR0%AN#InC3qMwrPv~R|4Zrp= zn;`qLdIjUzs(U;o_nVmOBU7C_QGnr==dYfxU)+*R_z&E<5A><7L5f_}gyrd3+e{P= zk1v2iFCRBYJm;4RZjZ3AVM(p8v&wA*yLI2$p--LG6OJC~EVECsGm9#dC?D+5% z$yVZkBXfK3AGlW3H?8gP;h~A{6tt}5*4847f<4`7_j}=cw`uIsySVS;>A(3ux^P}Z zxvnm(Ej6M*s^3V-8ykZt2}Ruv^Z_RcxWhQz2f9}9%w?Mk@Mf-AhN7*$8RA!#m;3p{ z9|7;i`QlNnT2yt~LI&K=hx*Yoe(wQHBLQCnew6q=p(#aZwq52p6B7-+c1Gq;;n$_o z-(2*+XaadgjOAuE<^$XPHYbxB&9h$3(?0dLbvbtQ9i_#-gZRbkh`K zazaYT|8Z~t_@-f>aUeqPKC1MJ_XJ(7t~L~qU{dWSBc5?Dks#Y}A?Mu6f;J=fiT_aF zrW_GX#0X)KjCvxq-d7Uu_@JxXU|CBAt80l>#th&6K_0QN7e2|KlX9b5YdIbi?)9tS z)RN8><0)PkFL{<%`Z6Vt`eBWA_ct!10+;$#Ow{IWh^ar4JZD`E%~&{V!RIvwzOVWs z1sBYoX8tgMyq9|M&=|ulzl1{}g)D<01&!~j%uo{M`n6v!^jQ)2ThC?VMGv(OU{&yv z#&zi3UFjOJjV0SmTXl2l>#iJEn!!3^9HE}8J8LO5XP$Ie{V#aqb-L`-^IRBf2?EE4 z%zZQsuZuNp-Rz@oxMTPHx@UP>_^GQHdY}wF`K7u-MyS9Ze>V->fjilG)qJ;9glf@1 zha1B|hCT1eWjdBy`agyfrgnUptco<)kUI3!+)7^HFgVMwLlPDBj*d&NIlX-`Uq7Oi zT7ZryUe>;Mv8v}_s<}Q?$<}?rA1~hl4<~)A{jpa9-7K>zJZR6k?0{uFz9&6_%<2>G z(4QID*QcCc{s=e16LBBG*CnJmA@2`w%(}EPnC)#l!6_*{HMI_G%|~F*XS}>Y`@bFg z%M(${>z`@ZPRauWJ)64gWNByL%PlPQo>pVFr(z~Tu3Rs{Ql5#SL*2aD4-v_EDg+uZ zDXxl7&z!DHE4j-K4&a+Ql~t4a29w5iDhQM8^7qM<<|9z)xbB#^9*2Kt38W(9lJ_t6=!8*VmLOLv2EZ$(weu%<`aBsDPyjZgI?GV0gM|DjE?&W)j z3jW3_^M+4Qk7>f{{L;rv%(=&w9EybHGgwm6lC?Cee$AMFsDi8{m}}8tF4pcJj_7Fq z$y&YrM`rUHMDmv8EERU}l+-WO1!|{;%$kYjZa(}BI~uTPVwApQHMfhxQbv1HG*Y@{ zue^0T(5MvAAs(c3mbK+;weEfwjZ|WYb0v%sK(s`KRUhLIF;1BV9S%yqNxMnua{TX= z`m&}?=0^RNg4*irOd~Pl?02OWdF+GTi!!9%BN`ZZ1EvUGBF}xSo6#%V&VZ_2YD*m0x&p3mR>IqTs9Fd1`@Mv)79%fp;P?|7>5l zG{*-e6!z69GOh7(`D&P<4$EJU?1~zZic0xC!FWThSw5QEHua&h>h-K$*bzgJikk&C zy49wk1oYX8Y@q?)@*Tn9OYPOg!?Ov(cOS_#dUq+erMBPS=LE{3vE7I?KfZU$X*-}v zL=I3uezs!`XPXM&vwrIE>c+vnTm1nGUG zZ+@K&)w2`PWY7GAT>Tb}+-Gu+eB^DE0rlLaSsl`HrxUzF$vbbw_?8X1lu4^HIwP`X zfZuq|naX&_D4jD;2ximjLB)0fPj@g#etqgTsp^PRFf7m-mN<@K~+F*LHF)Zo_t zEG(A!-%iUi-g(7exq|2%Of>y5pw}L`J za>RQz@9c=lk3&s}eIu&b!<@=|<{hCHyPy7a+8~|>5lC-fGxT4$eBgnB@<%}DfWQSC zytGMZKavmV#=lwrNR0)2QOx>lf4lxC(6hyLlCBpdcSw|E&d z*={h@4ml3}vrG7vd_)jg&253|I!%oP{vt_!en6MxU4*Hj#Qzh&5w|nclR(2z8`kB{0}E%HxTaLG*H_Y84t2=58JQVFUHa259}(#@)J}zu15aPr zEZ>hVI(x0N4+Rkwu$fhS5@IVv1ivThN{^rb z``YG-ST%P>m;z-%_m3%+I$Mj`h!|&$8e&5>T>P`->JF+4@({?ZN&!1@pjMuFN+FD! zIO0&AAk`S>T#I831t}904TBCL8uIIU1zh%`8^7EwUxr7HsR6jHv39>54JJ~$DVADZ zyTAiqCdfx06BhAek121F<%NJaTkB7B2Wf-@1a#dxX1{h zUYQJwmkWaa3%}2y45wS@4V#3vd9k4|@HkeIssrjSCq&jdpK&C}akfYKQfVz}j?*pv4S3$a# zw%rMDd8-3ZKI3Iz+z%(q~ z(!kBs2L_gQ5p(F(Q*11fzKyhF=vLe4$=c%cqqay_Hu30tGm(qP>B7TPQ9Ouy`x5JJ z2+B1YXMckPG>`z_<(DeYmU>#DHMfPLP-`8h0?z@tG2VwQB1w9BG@${#6gLi)L1I&x zkvJli`w8C~d&(EK`aTJV@(h0HL?%pwzv5&|gWw!At(K%;O#?RS}vcF3G2@FVDr zJ6D!|wqC+LFvE#)msa}KXXA5!ZzEuhn_@QOyjJ`Bdw7#EC=m}jx1jHaV|dmak_Px~ z(i4yE+n5a{cv#K2NwSm7qj^wDI9R3G(#bP~Q{(RSTPof3JZs1h=#EB_t&Vjv#P}$Q z;*1?A(oVq(*$P8w7C}pgr<74G?TM2pGU7O^{eiASkifkz(#C)b(i2#z@F?IInJE1& zsIy5Y-BNYyA3huJn*96m!huQNZUB4$+C7h#h! z?ZVFeZXSA`Kzp>MT(*e){kz)WKXEVDbs~J7pd;;zC&fCnM!bYNOw+mCytB3IVI^7l zFvmd7TXufqOc>K3;?fi(Eg5b#r%Zf&JU07IMlJBPYjgC5&a;B_m8*)H{DyOWv1s8E zLq>S4=62!?qd&OANhM~67sEi$`vZComb7@J<^`n_={QGY-O>!i$`NgY^Py29E-ILR zCuEjd$lik1Guw0&h&c14ixtVwR`hwc$iNUm3SX=W{sH1$UwNiI)}sp)KW5Uj;z!yw z_T3LmW}Dc3={>&Uu~jb;F*(Ik#kXqjf=!EF^nDF z;J>kL|By2625g2!6Vve>S_Q)%EMx@&?GoZ!`AnHjn0rbcPs&P+hNG!tq>QRG&x zRhgjehk}Fz>_$k!%DN;r;P730`9B!$U*N=P+>){8=~L+FsfV8@@}ew6eH=F)owdK| z8*6jOZJqOl!p}e$=khw@bdtxoUUGl2aHF3Xl~0+gU{k z+nz@?ZOP+vmLm?>x0ELgo!%~MACBOb#br8)9i*xa30$0>c%giDF~xx+P$2&-4#U(g zViThG8y$0Q0aTA}Z?5=1& z%%-B6h>_s8UOKKB<k+O&atH+#ScLv1dQD0%7A zZ_h6xJ@5I&*+$U?@hR;%5{&Meb0!vcyE-}+$+gDgSxMt-IY$t@Mrk|P2cPgN)bI8A zenpx5>^PdiF|pZY2<1si5d^{tC z7C%9=bZsrCu#9MWf9wCHUft&9&C2fTP|}y6`WRW1Gh450UEz7-N;{uP5^4r{)ynnU zr%N)_NdDUIJhkq+LA{O^`X5}V1_B^f;9M%EI~9d6dgW#;jIolOs6#EGHd%zvKH+!V zQ=MoUSyoaE!e@zX)k>(^JQnIg>=p2W{DXKQj3sFCPvJxK2Ob*+h>vl38 z-KvKc7GwVD8`52;*W9+Rg+!uSNk7W^dMWsL8~8qr#+nJWq}_FK+{@{s%~p*pGTHNT ze5Fgt^6-*_zD^uNWxvTvL#=sfUH!}hCgj~DYvOgOWjAv$In##-7FWHIaQca$qqY&> z#(6fowQ7U2g>SR>_M}W>wsSXKU)j#3xp|Erxa*mS)U)my3I3z_-vQGt3kT@T6|J31 z&n2%O!X<4LI~3sJ!7v|}X{8jwLam}<`E49Pbgp?r?M;D^`}dPF?(Urpgfm+PB~=qFDO<4e5`7M8C0%9=6S5tAp{#C`pHZRi4>6~^6Gjxf^%!x zNcu|!$M5ge2PwLOdkHghliwC&*NgH1-RsC|1;pAJ_FtaxAgBTX^-bt}P9Zr|ZhIH>j(X;wUJq7B4WYB)_-j$F|ap3Q<;b>*<|)G6jOuHZjvWsNBwlyRy&&5Tn*=HySRj|@db ztv1VSDDZIj0LHPwY^Ayi=k!}gK27P&HSP9Pgg#26yp5mn=-hqmvTtp%RO9)@-F&uU zpzLqWb2?td@7|U0DyDlrbz#+!lss_Op37!b`@AP;Xa##2<+r^BwCkdFqL?kGjvy%7 zheggpwMVmYs^CNFmgoX#-fXsrWN{u_>HK;#zErKA2%uV~P;fEJ0ef7Y++{*$pth&) znu`~)^eR3*>A!m;e!RoZWtP);K1gn>dJ_zP&;+V%B(B1$8eN4~{Ic@3dkrHwpL^X5 z-gPjg3QKi17Wulc{dC`h^)~63LSL`3ZGSfCyJSDqahVdXzJ}7cPC?>uKvb)XjxQVMY7xc1=XVV?T`bDzS?+cLn4*(jFrhTKz7j1`t=ve$oAm zd81U@k>A_I5hkNJgY8*>&jx|LpX+mmtZQS&W2*N|@WcXgQDji>M!ZwLZFe6bcMx&qYJ ze53})KtRyVte(ytc=59XH4R?Y>CFTXGM>gs_(d9u(Hj zY3FV&X_pPvaem7szx%nkD{0s}<0?hlsR(wXkP_5?&>a`RwAvPNjQj} zlj~jOA=sD{YbA-G8`S^v+yr{4)LD*F1&FXfVUJ|9j;zG)WPX8d1O_YwE$d&$#kyrd z0iCg;!xMhFrOgtN)A2m66+~$nagWlIjD(R=yagx0_AzqlPl!T`@DUE=iyC8cZyuez zRlKZWvDiY1Pi4qIUM@wdetO$~p0-UQHo0HCgc>T#H~eVi0ZI9V-QaV++_q@zEUc2> zvdM_VxYgN6C=-7!+DV1M!D9O{pjiC>So_MbI+mqfAOQjd4-g!JySux)yGw9)OK^9$ z;O_1OcXxMRI17hM&fYoa?0on8ai8b@UDLg$r@E%P>V2zfPC9I!3aMB|(zMRqCK#j~ zZ9J?KoX296mI%qZv=nP>mW)c?m0-n1LcuO3ygFs($~hZyF+Hzru1?9*FE_{xAH;iZ z`iQ8s(x01AIpM6h8?C<}ZlULES+BOat>lskp+!DFKLD~L%WLtI;mtZ~-gI;$n8Z6i z`ip*o$Nh+c5l_K3spImnL(rV}eQ=}wV(g}b<+G`74d5_o}lXH1&s=o{< zDrFBY#l7E$Z%1~iXpplw?zIFij}yMe*6I~U;*M+;=-?zz-d%aOG+xE0v4dMk*Ty7JrM`l%3*JPUq}oN zDlo|G;eTtSJ;y^qf!$`lqmRcyt%lQ&~cr|xNmc+?O z0^^bgY-!vhSDsXDSf@4Y?N67g_;ukMfu+5kJgw0|WyB2}aT(F9JIx9|FJC%L%?M^K zH+P;L1&0Lt8l=K;dXHbPR#=Yu?ytQX^i2S{7_8i09`5GX&a@VZikac;izVAhw}MHi zgB@A`I2R7Cb*lWK=7H<9`{^)Q7SyggzJ+vvw=L{f#=6_}%v2PZ6D@O$EI{Qrrf7=A zqsU?=nyPEN{8@s-E!jXL#MV0W!yc;jT62m8y^Dg&MA2a^s=ZAfDKM`+iic*az7^dc z{L^@t^mATY>Oo?SndczvbS5Q{f_m@tk@97gzQE7X!g!C7C(Hx!jqb+^RT<>tE#z`L zQ9ngXuq5a5Q4ep+I_%=Y@@vBU+zb}Wn6}tPuIXdooD0_Bn4AJl-SYN|Wmcmh4?$DC)*$(S%0k;E=Pr_^$ad68s#F9;M%-hFg~(DDgNEKUT9bS5$8_`7Yxa z?dN3aW1?_7XOltS#dnj?{BiR0(e!8GSV$+kEis+;mr>l~@nP4>>*Ya>)6W^Sg%YoP zlOP(!r1^+-Q_5}D_Q0aL_W1@PD_4~LK5T{4S>-SZrxQ{t>uol-N-PyM0c@UZs@mxx zIaRx5rkC+@qVw(GNAang~4Azc)2vNho(3Lg1nOaen6?0pHC*aKUF=Y_tO09d#n-e z_2atxmk6QE?`Af7ukZIW`#YAS+WOwG!9c{3~Wh*%9ggP#K;NyLfsL!OJ=3IUhUneAgorT5&BY;W?g@?`b5H#>~>*&czK|GHlgznC1Qz_ z-N)-J*xeqDJiAg5n%6M|=*YyxVDO-t-YfA7zIA3uEQinUE1Ai;to&T=y6+&hY-QEJ zTA;Z!ns1~}V(DF#$Lx9bP*!kUK-5VpBS;lBQ2M3K?D1N$&GHzNzFlsw@dhHbGGm>y zN~oZXC%Npy4a}a_T{S3!fwRU8!5GE9AAjE4*-U9%a#_zEhOvsAr)sxD!$yfEY3L_< znc=fzWCpJ<>^uw6w>gM~_)5w`eYLslI}PtNT6&B=u)MHPaIvoa$-;>#E!L{vpS-L` zW1v3-wwECf^^)#JB`zNs4(72|rlMOv@cKLTYI+-imIlRo(#K zIX4y~pKJ;blM>7`w2HnvykZ=`I;CKU3DPdo+3RK2R%d&k6yYu&gopiY5ud6+6#+RM z4-FpmzcoYjqM{sP?!!p+*28Dx zxEZLB8^YNZ^gWy&!aLc7o^8%nqeZAJ_vT1I(VpJIo0SL^3wd&ZLF1;XRyR(xfdc~9 z9{I}8Tz|O1*i$=bfZOvp;UZ9I*2$%BUDobnc_4Z_?gpuPi~|RcVlW2Q&w6hy=Lvx_ zZ71@TC_%}HmP8Xmt+x_!QOw{rkGXV0`w_SuMy=k%Ev}1hPtB`dwBBcT7yDM|6l(~L z1uJD`doyoow;!H%qZ>?|Pl{F_M3A`Hv4ss6nzwFG`5ML!lVe6+g=?I+AU0WEvv;}_ zaM$cBSa~#J!sB<*tv4M8OO(5vSTQII19(>X`h{)X^hla15NEv)?|4%KlNQ&0Yky#K0g+$L!Ozn{Ka?`ws1Z!|d2rEa?HQhrTla zYob=oMoe0rxH{0eqF>1LxHp#ydTPZ}+%`oD^IMHej>!fC^12HfWfk%4DVb9!OSgM> zgkB9tAL!q&lR7A<>xdL5Q;CWQ_E6P8oQ2q4P2sCKVMT)}=-9Qo<&-vS3=wZ7dU9f_ z(sEX_nT#VBc|Xin2lar==$WGY?%K0d7p{(KHf#{i%e3S13_ARG#r=bmR_}`~xVi}P zNAHa0iwRCmSHz0i7W++aXHQ#q>EfEl-Zm9#9*&DZfKhK1*W1C|zyv6SZ34~Jz{ z541`ykJW=1>9Y!QjA?G7?HH2v5pK?hp}H&IYWiZ;ujcKy!+?3qmSSg%Ou7zl?*RsZ z_63{Vs}KA4i%{=ugpOnPGpDr$Ue=b#Fs;TSuxTGMcG)T-bvj(#jXbm>xlIZj%&o^& z85lY?BA#5=CYXo9+9l%ZtvRR4RKX)(8-NEp!}vtDhDY;f9f#x*!$C5kH^ldD2e{V3 zEF-()7||Ln_V$}cb*a)={#LXP*^_GNJsPuVdB>sq5_jnMzLzq}77nXCR)jqv4V9kl zJ5(~R%hY1n`YT(r`>IEs#2HnUF>lJCBYD-Kd`TBM@fpDRp{--iC9=7Ho-inQy9#$c zQTH0|iKqjt2{xh_Lt;m4i}+^2;i6K+DWlddlVD;%Hg zrrWn>ybR>Wr!uOR)!BG@j)Aj%lFQJXXE|d(=~1F={wIdftGp8&FvLcj~L z(pO8nusKb+Y{FS9|Ck)@`sm}Hl26Nc$x9i^^J{JmM`ex4w`zBfs!(3Yi+4#@y+dxp{M7Z=X19uk)aI+i#-%SAr0X_PcSev znbdwh2<-~I=Kdwu0pLpEaQRjqS?*tK-amD)9Xczi>woQs!w0(GF|T zt*sNGZ1G*5(mD8+Z-XLx7dOol8kLgf0oC2wIx`yo)Iz*?<$5w-rdEmR^^1b5|2);{ zg896s%~`ODCc{Yzg)rQ3*hTFHmYNQu$O;DOI^XA+4E4c|{>LMhzzz{xN%j2^xGjJ1 z{(0)PN*l{rh!yALrmZqfU3^>#X0vT}M~VA`J0^tLAOaCiE@ZzF*2X-OiV33Y7xoWE z)Pbss-@S`Ni*h+mH6x4V@# z=nF&lP^NeIUYt%uv+mLT@HDaMOF6O(JN~^f*-~L#qxL&U zV858WUrVX+{-sGro4(bzR|baG&nXT;N`E7{1@K^(E~zf@`t^i4BfIREZh2L9ZJ$SD z(24LMXKUzU2ibRu%HJ|1@-GGS$YmUJvzRXiKIVo1;Pt2b9KP#cpqAdDt*`-`5t*j< zQQK&#S`G~3=TR|_KVk^(KybBe+kd=q?#ffJOkeI@D2_b4D%W_Zn!m*3QPjouEUc$a zzTg#enfp#XbmjmeAp%@5a&c#A2?{I3k;nSO^K1_TtN4A)_s`j%hQ! z0+VU4M?%Y;^(u8(y4yqqAR_^!O~NhL1M=!%60BgCa6ak@?V_;-CL z%N9PH$W>Iu$h0|LHQAh0?3p0GeAqluDQ7)>G+tI6m+w0bI3)!H(UNjbE8nFzzOF?+7>&wD@GO zV4hf3cnf$AP#J5a%u$N%@r=LddW6AUyw+vVN(`u(sf3ccAP++XUKK^Y04|E2x zIOAG=FoIm#4+NPw;y1j~LaajGBGvG1dy~J&fIrVPiaOSytXtauIL0(!XNbF`M_Mtz{xm7-}N(h;jH8@{9eFGEP+D z-E?wYaLjp8xN;$SiXu%J(lW-MhG`lEEHE<&5k-F$mG~giis({!tjz2~jJ~V+yZy#S zLAVbmT%zj<%A9S^U^MykbW#1~TY-NW>X1-`$t;U5T12osUJ{&u*ioT_P<7kLa35*G zo2!egWTXmD{`4e=fTj5-S(ozt4H=Ktk3M#zZwrQ5iq_n#^TB^%oZ}X3)Merha%}zt zhJ1yuoo7~_OTsp{8@}_`>i&!D6UKY`FkoQaR@I~(YFHp&OO%|UQ!>=WC1(~KAt#wz zi!Ul{ht69aZS=bo{y$_X?-k+%runo>x&u8CcD>}~o{1bTekq;C!(7Wl?dZ=a4x+2- zj*(bcxhXxZHA6P4!|0JOqu&GfcrsX90WV33)S0%X!9p~PCowI!wwh6L$Tw@RW93uN zTm*}(A3fG)z5-vcITmuVTkEUabmrPBO^Mpws)Ryj>+{z<8wl$9j_cS$E$#QIi(lo| zOUY>W_b#eV0cW$EoHV#P6pP=9;9t%Y2dgYYxNTP%ZdV_$)GK=#h#c0!0ttw3f`jg# z?$=c)Cm*@W8NffBlKiE{AOtqXczBUNcdHsGMF=Y+3~aJV8OW={M@oRk?6dW1H*;yE zdf5wfO@)pn?2B-&VL5$Mu|-~}N1j>U9W!2oTMg#pf3b4zq*Qkgk`Q4jN$iAW>^1Dx zPJCX-PgE@LU1?vhL>_NLi!2q+7|_dGyp|{Zou8U*v~wUU)y@yLT(QT7!vKd19UdlQ zyESmL{dG%ov*#7#r$^HqbLoQZ9nCiUgVWm;%a<6?&0&@493BHd?r?~N^s^NOnW^}% zE-hSN>UB?BsFsyO9X76E**%_mv1@wiyxyq2pkR6AjyP7&$NuF1bpLtl!{oMY$(3AQ zqZ0%aHh@$rm>b<<)<8SeI3vYB@spEtwA0#~%sdj^4R?5Bmr;uedifo7%EY`Ad4Ddg zV#1wiYTXGTld#p{M5>Y%fvUtVS*OL0Y8{eOJvj|ORk(b`tIs?P2)o+Vn~G zaQRkZ7K<%+RH~Wi8Y1FEu4Jj?8GBULmMth2a1>UW=*r+dD_s_AopbmXH^vjZUN9Q? z)%D(A9`g7Pn#(9=%r6}*mKc6a>|3mj#cOc7<#cQdOPZX9>G2eL{kLGIBE(ZICK;VK z(#R5=%=WtdI|aKFdonHQ?pRUf^PJWa4U>R5PEgrm0@MHxMf|X(`F-%|I5geq$KU#Q zUc<+WZC~PeLOL{~M4(1|<<{vpb$rjL;Y8jawxIW>cxonnIhiLE^G2?oVJquh%aNK3 zJWkIP9GPnw3iglj?;Z*Sg?bjXdgF&P+YP%%^7KWFM}1f_+e)3G8+&;mS%0xwK|kAH z7=#rz?ui=m;l)G8dyLRp3g!Uh>+Tj8TJt9p9jj6+Q@}q-t>ajjUZ(Q0H|oh&Uof&j z-u0!Qa?JSd@@jm3N%?7v$@;8&?J1GOK?W{9VWPxxm45T&y~WDSb_n1NR4ES-O3a6U z->H~3VX`s`RS6(oHE-uBUyBP;U7DOJTVi}lk zJK|Q{dtIcRS*NNXbdvs)?27PVPb?Txa4tQ~NmfDSR-%8m_9wZ8(f6$ty&k+lbE?Sw z@gTybdbUYZ$uWC4lZ(-4I0wAl@AA~Ek8V3VtrYGHT2H?5YXb*v<5RhSS6yDl{+KEP z-SJS^%kuiuRUhudNKi6;qrtjMOTXSNmOV|&e)cRpOE%SXyMnxU2UdhS36b0WlMFJ= zab@1<6z&wf4l^~?vYL|WWg>~Pb=$Qw%f|yOB}A3Gxtx-U)YJ+t0@R3hg_i%a%0Ffi ze^k|TzVs2!xEUY`=(x(~&0!*!sMQzPWgEt^e%Dhp#J;q{_MB<6x0B@(*uGPWh|$ozHt$;mL$*Un%}~j>p#XG;7P^rv1ES zeG}Uj#8s{y;&ufbEV*GeOCU_O_p&n@fLT1TJLbI_Gb6R3sGy{>6&5ZkZkkAr=P#d@ zK4YxCVy{emJ+qE>V43H4pu*--Rn{C9+RW|h?lAtLYQ3ZcU_?g;So+v@1msiLLn)or zo}>HCDT%x|Rmn)0r_EMDO_Bu(sDoMf`Z)~;`)6sx68*<+10YrV-}O4rIuuJOSN3Xl2UtoP?{ zbG*FRp(WdnN;La^?5^JlkENm3&m)S@3-lm@X6JT34-7ko@X=2xq+j3EuH?fe$szo& zP`yu?%ugZ%kuqEkVEK)PR&>0n#y9Uc`YV7r#}?tB*UoTumciAW4yV!33rv6Hl3n-6 zB6tNP*R&#J|E;m>KBZcoRLqwZAtsmtt@;PDhXC#15|z3+Wu1XkPHK~t9$SGoD{h-T z?2P-UI@GI<-ta}{tUA^&|Z8@(E+w)6F2aWqimQ2IOh?plJ zZL^(L8c)=qPUru|sBE!|6x$2p;HW5U#HZSi^@+)k=I5o3AwDwFSX*pPJx8Q3BmvWs zU+*SEx2qZ)4Zqsj>8)|Ow%Nq&EM8pzbt1XFEw$GgyWoL>R`5NDH0zDGL+#H5Jy+%} zYt8OAjbaKR)HsiiH_N>jd*YZe_qx>T>Xg*$UY9{)F}E8k?g7ETgf~V+Ff_kOd8vBB zdE#vT7n5|%(0fSAds2l0nc;pmfw2Ps%8R_jUcuA{(-2xLU70Fh^2z+l&;7gIpG^wE zkBGLLz70bGt;rhvbR~?4&-s>hb0PAD5eJ7#$?Az5#<>hJkDifTG0WwP2Q6GyP){yf z?J+g_Kcx8W$Rntqx9{G^?wjQEMk>0qcS5NG7d(aR_%*Js@}MB#u#h*NO!jXh#LjaC zmCwlB@9RsR07Kfwt+VwBof6|jp1wM}1^ZzkW9U;2D&kv}GiaClLMsU@IhDMU?@MgW=?!ZHT zUpYAI=R{=J{iWuhdK;87`upr4l8i>eDL}n@&oWhUubnYg89R8xS8e;C*0UtLih3la zY6Fq4u9)MKzWPM4rL{~qiS<#Q4O29hcFIsX)Q#Tb&7io#`hCoFYH3XlHO`x>%k9Gu}=`+;#$EW*Q*(p{RE_r8>2|lq-@tuMZ&_5KdADiFRr@78C;&=jy{v zdJt|%HT6R$IH2~Fn&;O#?t=Yugls_XYWs&T=bE%N2A{TMAax9Y{@QTd$xI#|mYy|% z8H;>674_?$a?}Tj7{Sc{;s&rWHtgoX+e+~i(^gh8;97gfoGg~*?wgoXpQd~}hm0w& zaapLB6G30f%1kf^QM~WR;MhOuG@lh8C4xZLAvD#7n5`tlw`&|FF4Q=UYu`d{sdeOn zF1TS^ymh^~z|z+Cv{wIdcJnj&KYcg9DGPR*K2Z(dO!0A9UFM37pT-TEmbwig$zs5P z7g|Vf#7F{(-tsfw%Q=F@VHu&pKr&U@_L3}Z@kdqhfrFNYi$`MOuQ{gEM3(xV*gogJ zcS3QicQ{U8AThcuP22Yxp=iX`!I%uvFQa1G@VEnRT>EM<9J|REq@ap~m#Dy7mE5^F zg6Fc~v(vGoWcEK4ygDbvEcctjFz)t;_O6C$)$x`!(11z!yI~{T&K<6rx%>-i7i#{I zy+=v09h+lI{RYshS_<_R4Eu6_jY6%II5!*jX8SkXAAeRy@eM5H;yo5WLqs9#v%wm_ z23pXdVmIpdN2?+o*zb*-=W@HM4N)?_w3PpeqrOeUf?_x$^gbzSmj@2vS~~U-WUZ(R z>v&_z4)<7_DH8CO4TM`@?0i|1-fw{wHKk5!Vl+!~iLYB#I9Vg-8do^m3AlU+$y|o% zF9GORW`b;cz5b|IpXnsT3-ameCr+TGcChAq%)0f8S}rt^{Xu65j)5}b@HtxVID|sI zeYqb4y&zY!6mmo3>Zl4y$rov5Zk_ufYvfk=!-c(=PHUrA#-1aSBzlq!s$4di9 zZul~q+o;A_;$g!(IXJ-G*Lj1eS0V4F^O-hd1mZ@ekA*qU7st&|8s%B1gY*d&Dq`g2^~bU4 z<2{OCJW2|<6v@`Y7Svif>xR|3;$D38(H6eMaY%67D zCgQ5M56Ch9SsHAP6GyCdq16dIcg&GsZ|f*Fm3)i~lgRL3csxI#m@fv9jXsv3E`1ll z_nr${SxQfZQK?vU*dlfCokR;CJ0jFZ22g70aOw>A@~1sfo(WvNCb1TlarB|Y;{@|` z?tEpTH@evB&G>L(SMx4&l*?hiK9nnKIR*0AT3ay;dBU%T=9qSSiu&v^ z+zziR;Lu$&j^~*4;oMqAm9eqcPwc6S?JLV89T-csgO)n4+5MmjrWqR4ODK)?8wTC~ zYH{g@4O&CTe#9Tq+m#{50G4rtLPS72OP#!kei*i+Zh=)iC!E)FgTY$KBmL$S7%_1q z@;m}i>|x?KiifiLEYY0HhMluc$S6iH~+N>56x3f7o;ahK>kG4;PFj8HJ?tD%_ z-?P?-aPixp2KL)@UZdw=C4^gyQT|DeM ztwP>rxb!l0mpAF}RE~U>!5oji5wh%BkOhViw zm4D#9Uptm;;wbLullSJ27;V9DI4QG!S0#cYa$Kg6IiAP*~o(Tk6F`EiYU0>E|^rcOl4Dh3!t` z!ii-)|3s;(eNDBrxNKsW+siM%n;*&S**Z;-fu23f<>UQg?9nUYkD|c|6cm|1p?f!m z%V0>We@eBg)v8B>&66ZmxaD3x2q!SnJ$ZGl5!s^hyK zAW<3c;CD>E`7if^9Z#2Wk2%I*%$U7NMj2q3S$Uz1F<%pH^eb6?DnJZb<`aoo4?$kD zWHigNjYEK2_Q3WR+1ds65J1Bl&RX|<>&GZeQ{+z*`*G}OQtd;}(Ol=y=Ae1D{e86e zum^rxIm3Pu1xxG=p;bz_>-$;_*9J6F?=i3kzWeHFs5Q}AKAi^vp@N+XYWnJ}yQh}G zB^oe*qqGtJSe~vGtK$V@Uy8Qd5UYHM+#D`=kLbVZ^`FG2tRB&!rL;D z!m*H+#i}-Mv+fQ+tr&sC;&ucf21+x7pIqD6-vCtY@>orV^W}pJWBMtzlu>*4arHj7*gFX|hK&PZ*gn zrUdy*3S*I73YMPrTgyJ7ylTnastA7%52jt3^42sfaA%)*evY9K%SzZbTME@7CSg2_ z4~kf1-QDtJld}#m1J%2f;JSOik7_>z+rh_HS-x(Rn zXPXcVU%%B!EImI4bD)W6zz zZ!r(Fj5|6=Sz@AtQJ_V)JcPwQH2vp<`P<*-%|VFjKWCI9M@1~|6-FkA$B>G%i9s^!5@Z(Ll~Qy( zCKL$)_beP}O%8KdA|#a63;3g^;$O#lXM_5cLf1FrDrI}&w>|y0FaNhokKpwtxH+p6 zR_>=oX8JE{EGKc&XPK(;G=|@f|KEepUl%#h8c9o9FEU;C`aXUnxP9zJv;_f2 zW)%JeYj4M*kmaD^U=OpjQfN`@?nO7g-KW=w&9*Ax{MPvrrdyd_wpRtW^-;XwN3Pk(VR5jGxWvLb{ z>mOpnWYh%`nj%1d00G4VdG7;)00PDf@}UtF)Jbw|sk?rt+flfPXQR7a?x6&$2Nd+b z#(;do7u~4G^j(h#nL;>W!9)6MD#*XbbO(cwigy>)@hboKtY4QQ{NJwlen*D}1$(>M ze`fINvLb>AePyjf9e{rSdo6!m?)-u9c14y}0RNveKt6mV0c)tqc34htrAuS~W8wzt zd#h!-TR{^Y;Bo&c@A9`b#~df~NBr}z-XvuI_L#6&5brO({k^|`$rJ>^ zhU;z3n&Y4B-v4ob-K4k2{F=mq7X<%@Xnc;LK4@kV?Xc>C4N+bX88GBtH$~o>MoO|h}jgqWRq(XjwNQIiwn9=Zcn%WKTwwb+*og)wf6H7Fh zwqx$dTGD&fap2^Le&MMxVU`59;b5GyEVXX4sZs5_4bR__`KBS{(~tiaDOz-6H7(NW z*PxMwIMy&HPgXnE==B)W!~`_nGwWGkVH%qwH@w&3yHgLK$@Yl)mX~+jl`uQ9dR}BG z)VvWfo>&B57EJS_wrOZa0X)(xv!!k3o>#FB!n64>W02dzc!4=Jyte4jVl~3V-VFNW z6KJYoTlx`;>w3AAf-94U8iUsu3{x9HV6#l{+}Rj>D}^`)K_Qnc-Nm=ML;z7n)0Wm| zNRt7vxaAkpEAWTj+7o?PSWfY%y5)or8 zFexn`w&bTy63H#QADn0e&phuPG)v=7t*Jto_qVM4xqP=lEfpx)LYq@dY_A#VB%_LZGWl3Jjt1l1!bP&VcUWV z_N`TbLLk;(s-E*fsNPL@9FEWvUxm# zQ6HrSwb=t0x=-b6Cca;vSAB5&aT%$MR-#xeP#ma&t9}JEQCbyya3q~fcsDG9NF+9K zy`7h?x~3qNFHy@lw~sz*d}_I5js7llXA%&Qa7mEo&U3dBc*{f0t!e#ZX|#IsPlCG`H#eqVjhMUtmEGd znOZRjU`CVO`vtwP8O(~?l~4Zrd<~a;0!o!F5Th9& zZG$}_PGwan^C-4w@UCck8TG$?Y3cL!CYNr2XnGe6Qc_QLv#(Mp%h%H3-66)p!qUh2 zz;gM=O8P4oF*`2h44MO$aj({xWWG0syD%LW>!HS?^>xa^;WV-(Nr&fQpmX*oMqJKZ z-13guLy6VA7$7^^c?e8IueUugM+yT{ni|lXMM>>Jt$P2l>BZtlvQC<$LqF9M-}hgF z!E%uO0QR?KqTTuMv8ifA7eT$+GSfNXJM>&C5iuvf2&r4d*0!g8+m4H?LLgzU`+9vI zY)zuYAcik><*RL}8vezr%yIHc?8tbh^EiNb}A@la32VX{~BF3Sd z#63%%42@S&@>h8yoClMaC)WGL)jAs72y2ZxTB0?09s|^e;+KNeil=Lrc2oxWEpMYn z^A7`dpn_uEbjDbH4hT;M!p{3P^2+P{OFcFlAIJS+X>5L4eivR#m=5{<{qzt^w=S#gvla3Mt57zt`CAIvKVG1ZexmvW?mFgXJl}Jf zeWGtXk@2iEQ%}-gfW$`TTizx_a~e0q`6SkkPw#-Lk&t(0dD$fKO`pu3!ZS}=#R%0E zde>_$YtxI55Iu*~HX5?PQGaMcC(}El^s}HS)fO8VGlO1k)PURWqqoZ4UaL5u+1Vl} zu~Ss5gnRDkCvrut+ARtGwe%+b>Qin@l#FV?md0qonp-k_xdP;a(|m{-vG-;F*VWmC z#QIm)sR8QUiO*3u7ZJ*sd{#t{SseD z-7}O4m!5|0eA=uF{vq->59d6gu>?W-LCdw}MP<=3kZzAdIlg^@+0dNr{WW;v*8?Hz zP$I#InYp)F|gDD%-+mxB~p*6s2;~8(YZ&>#>N8_aj&kGfw+R>N^ zda#6&mk)*=v1hlUHbh-dMjGLPA?EUXoGQLwx?Nu6t0Lzc*9Wv^E)mWzYNi*(>ck1# z*9I5I3HSHbbu|4Go!Zmz&(>;)C7!!o%7E~aMfINDU)7!m7Lmcnh2UXqetVyv*M4b* zo597%9t(n*m+N}ffzGh>kkXOJaAIqLr7D&?-Kq8TUZScAQX@;L+^54Pk7b?Kiin__ zpvcR<5NU>`N8n{S7oFMl$@jY|kC^~!kJ@V1)Z|&Y`BrZmPkG4!zd|K)#g?^vQoa5A zIcFRl2l@_5n6}5+;wl3|g0_7s1t=#g=&W^bi!D|F*0Qr8EnFCzrZ5a?e`bE4x88>rG(J}xF1ET2I*>9(o3=Ge!K| zd$yR*J8NtU#+a4AWW{YAdeLVXLpx`{E6ZMd?tqQQ?iqg1@Y*#ThSCGY;ccb`F*$yf(RCJHvY>tjD}Fjgp0EOF&tg@o%NxLFAcmSYxjIUrBvK8p|u z(%{xYzn;~Wba*v_jgwh_ZZdJ2&R1_sUj(0VC?&i&WPf&#G{&wQE8o?6OL_6B4f>we zxU@3O$sjj05*OInZBzA(OR<+2l{`xY84;dBh>Mu3NU-Ky;Ki5`3`e#<_$g)qM9(iT zulnU$NFYa+G}k44U&=98k=h9MS~t3#wcCEOCH*Jl?;|n@$>yffI!JjsTD8d(Yc12T zlrzMHD$X?KdqosuS`m6-jNzzSNLP$}Uqtsi2l^WM%ZG|O^98GR!YwL6lk()0UA;I z#j}ACjIvC^a$)lTiyifjMB4e&WiQiv%dB zzH_Yl>Av&5khYrVxJKpTBq*G0+LV&(!3_W5Q3tx6rgPq8J@I)a2;Tl6N(TC&gqSCR zl2S0poSPZ^`p`i+;YQfXj)hE7lHI3#r<;LG0^v$-GTJ$JDM`iKwtJ7Q{x@zEd`kF>aJ*~#fz_XF3h-m-B#e{`>Z-~U#>`qee&>~z%~jK}3z zZ#lL!{jy+W_IfU`0f*fLp+2?;>M9}PpdIS9Skmi4STv;1*pRTGpkO^ZnvTe|c4WXJ zQhz!cG^_B`X4WmWjgcR;qr`J*a>Nf0Uu@dtNjF{!$?%6%+uQl}&{>igIXCIT3`3z8 zB32cev06CgbwA!1f0yW(PZ@DpX}1~|-f?fxrW45RP26BdLeL1o)pQdWDwuo$9Ud3U z_wpzsBSYxkdR^d@nZoXPIr%!HR{13Vsl#+OefWYKsoc?I=(4Jq;ZyHMHR}3Jq69bp zP#B%WWw4d`i7fj&%#aG>^cx-FzKU-5(1Wh0$l3R-C>L^ z^{EZp$GbdSE4J&kuVh6%o&XcI@2}62Eq3GmdV5aap5uGtBWqqPRB{EZr%ZWtkR~Um z47uLtQYh^*y{qw?aaIJYp&C=4yHzr`_^jTXnVwNf(8Hh411-p>^;}I7VXnsIyy6Fw z5t`})+3@d!mlMpY>3WRCBh19*gTD&>rW%f1B_nazzChG5c>C5lDY-D-hw@Hs<4EYl z{EcY{Pk}}0>lXO4p9Rt3?FtASB&VYzR7)lU_#f$EYP>~OmxXB`g^P zg$2M5zaI3k?f1(`35+mn9GK?l#st=*gQO*1zl@rAmTvC?}`UE9+TP#IfrjnRF>2&zzlx7Y*7-)2p{1q2*W$0*o z4-}%?o{m>H3hVC(zmrC=lgcD+O*3ik&UY+FZoxG>eni(pnWXqjYBNiI9*WG*^;NOn z8hVx)oO}4>_R^fB#i&E?6;6yu&x?bGIsS<8>EV8e)y!ZQ^-}aI6^4+W&=}a%v>0u6{*@JvWR@D@#5t5~uqJjPa)s>DrR;$BYQ#M-4YS+3XPurs;g66hOjm z=$Uyt1pu%W%KA+Vfmk_qF%WcMI<^8?r3S7fZRb-x&ix?vl2qn&;`!TI@#$zVZZ&?? z8ci}G20b^EBXzI`$>6SbZGr(`Hh{V=DOmwBPoD;-p}B_b#=u_pZv9cFwwnL2eN8!L z+^_+`h-k16A+PSbd3);u73 zbiFD)lvw0Ed+?Iscxo7QZ(79NmdqT_;{h-p0dtJA z!s;FJ052c51ezHJM;i*cOmz=J7Q{6{jWszgTYTWEPVdV6Jg!~bW#;a-$OMuv zsif#9+#2hY*VEOOQ4d5Nm&0MH#Kb+cq;|H3yA$84sm5!xTj>S*&Esv@&DdUNl3}F5 zZ)iFe&k_V?V+P#s3FD>CUu8DE8_)nea%fT98r&$>TO?m!&N6jAw+FQb?zf(L4lW|U zfTIPZcqm>jl)Gi;4)v|#CMBY~(&3Y^gplUqG%JK_G{Yz1-EaBQ;S6JlNV7>kY>Pi< zX}R>37c^3tO|q+i_K)6SEspwj}3VHphcaz3AVQX9Kq<) zAh|*B;pJ9NK{-=)wCT}F(U7c1b7o^+ZoAJ{3b=dIIMf1wBapl1s+#ruFa;mbz@yWD z_~0`obv9LCTwyI0MSR~{V{LNHQb0WiNt6p2{&QH_KcE^2X1CL7Hg>}TM6|nzfs^69 zC&)Y;j^%bSUmBDk#RyN`V0q|w>|k#Xkx=v8S?2hh5xU0t_GS9NP)prAq=PG!^Gyw+ z{h0o|UOB_E)x|B#GEpW(a06$p;QF8U=|HmU5lLtx#)Fo5gLq3p@O8#lQTYJLz(rs3#sPt$!*#)Ixf~JUKz|_$Q7uvRnx1hEa&mHFtYH>n z#vr+_21>8m#xb4DRAXmy_O}irSKMY|#8jIeYum zlIamf#QnZ1Sa-{ad)pmpM{5LSIg3=p2@2e*heEyiVb}&`% zSixYX>eNh)I~K94YW8SSYE8YCa!G?1mse!vub{Pu7O5g@JkC$@rh!|Es4U#lkM=B? zFIX6Hk9YO4CXR{(mkNF{&z=T08b2aJ_MCCt8{ayys%P@mB=^_8OT`?wI>UjY869T^ z(9AQ8Zx+s=Zx`f8VDRt@y4Tb3(A$uOj<)fnzBBQ71UzeA_d{;|2CzTkg3Vi+IpHe2JFK-~P&|{^j^aINslGi%4wGB#VrzOIWjT7Cv#=y!w^=W-n+X4%Hxbh9) zeXg%>OT8Nu`gmM_Q?n`gy__*(<=MZ=HY{ceaM;b~vw*owEArP4TS!zp-nlG%M?xuSs;A(&I}DivVK1 zo`vOlJ)qSVNkCiwQOzSot@`Ng@gDxI12zE13;SY?b~)Xz?HGzr1n z&7Z$T0s{8eqi*ECA1Do5PG&(Jx5!9|w!JJNz97olv~Uk#&+%|(BNsaO@~~X{m52eY zWAemF_D9!m3H_F_1G0^T(4hLGCL`RL481Z=nXqUuG1(ooSa)(t`l@Ls== zM*aI0yTjE~m}v~}30FpF4w+i%boToL6US`wA&+lsNeRlFLV4_QzXGt;dJ*2xyo>m#3D4)WUa9*Hw&K+x{ zVP(r@INBG@?G2f-RH)Efqx>hE*-3@B0St*D$7cn7dog)dow=e*;8kY6k?)v z5SAlS5;uIthE}XvHD^}_iIiE}|A)4>3X5y&x^;sFcL@+2g1fuB1qlRqhv06(-Q9yb z1ee0yU4py2JDf_g{xg^Y@_gSJ(n60CR0~Cv$}uw&RY>7>nvZ&M2?|8A-D207qU^^xkMxj2wmVF z;#1Rhr+!hDGd9Jrnj9x_Gu)9}3FTnQ7Tnh315w_PhO`TupEg)%a}~Jn-Qm6(d%Cx| zs0Ns8^S)oLGt-r9&h*>0%&Iu>T6i9mi`+F2jxlCA;9j{Qy%aqT-NObWQpGsQG_p0mKNb(IDKyR&6Hw_lkz1lbBOVV#W3(fUy31<9e68&2E!PRc5q1#~IOBFwa+`h}rP7KkS+1RXH@o{5T~v zEbrbV+eU0`biVX>lQziJ%Zf*K~bdOBrTs-zN8=?a|VV|JY#r;2zN}-mQfK86tS!4 zhG!`M{a}BRdsTyohE={ORGe9<^m=mklks$phd3uU*wy$nAhMVtC(`^bp1p7}>-@s>=8f-dcp;$C`|J-%ykx>wq=yB77Mu{vv#+GcV`!i|Jde->J zkO+;g5G;I=2;5Tv3&!f}ksI_+ecghkg#_JQFoOr#Qy2z2sw^b5roVV@^~a5fm>tr_ ziux$`AY@ehmIGldws=wPclr=J_rwTg%qRLouU0&pbn{G&)m}8IhA6rBKsJ9l_Y8Jt`PXAed zK${dV<&(_kH?-34%Mm=i#uC;(drL?U#kxD4uAts^s3XP(6BX>hjz9~&l_fnxxK+N1 z<}|IzqzgVUF=br%P}FRslTdjdA$iutK`^Kx;dT)ZODU$H^f$~ESt*c{>g|4(3k8iyE&|*hclX+nrhF`0tI3qL@`HL!X z|D;M{i3&fdQnvVA{9uf%9-uo2JnUkd%ZxiCk4V&?^~8;n6Lc&=^_j;jnfSo9xkba0 zGbP!Z5e7W0QjmY5K86u+T6~fxgQZP(1_48{z7?X%rq8>U(<|5ed9p)W)6M_?V~NM4kP` z0m=}Jb|xg6+$ov28iU1Y+{^Q@Gl{g*%#N;9N*M+m#Deg9V&Rsmz@D7G>So;M3CZ6) z2obkdbFyy<)ayTX2qdDlgtn7kXoYV4Uub1LU;$!`l7@!;$pehHM;0mO9Sa{Do5Po0 z+yQR|MEpZSn12cWd|>Z@p%>9V`)|olKMv(J7|&6YC1Z6|>t1dg{63I5#h4o3-D{gn z0r59jp`HF4tQcfJ9?ig~Dk~Znr4<5o z0~j{gq0T|+G6eN4pKup_B-JdeuooQu@OM4hv8?bOu|G$gkCU}yYb43vQnmX>a8=dq#h zW-SkmyOO~*P2V>@!s>6nBh6c!3iN$b$@Vux+3NVoP`G|Fluv#Z&!vn8IDG;03*9_> zAt*%z@BZAWKnSc;{}O+eW#q{IU~vCFir;_&SNR7}nh!uivj4ke{8{t+0k!!!;=gaf zpWk-`n)Ir`K(+q;R&RhG8Tk6Q|KDE!W%2FLO3-WI_ILnsv`GN+GNBB0tL-{Q$;0=y z%d!pJJJsq|ThNRg^$!@~D+3hid5t){@g}t?1LjKaJY+%gW<0qX3CKp_8@h8Up#`8l zby3l#X3|;<95h#@(-^5t1dCmpA0UASDIutL;Gm<6n#ql6x3jbGS{&o$zTKiG97jokJDwNO?y~_4qI?ulzSTLuaC&pj*(|-dHoYP6Qy8y&; zhtcvr6p-<<>zDV@QBlPq09WX*H}Tev__~%zS8ho=BVluJ`^ z$d&*PG5@6nPCet!auLIC;fsgF$Ig#LYc~Y8uGGh z3>JA$sUgry*`%aja#^zfOFf>*9B0O{P<^seIT>?H#hRH~=w&Og@!1P`HI{L@9{&gT z>D@s#PU?saIZk3#oXcKU{b}U281oAmzn)OC?($I|6$=8>@IS7a$W4msXx6>rR7bv+ zJ@-~2Lj|Da&wbHLFNh?m`0?l>%>NJv*T{_u6mW3z>BZ)nI<-z+&6=Or%RtIRe#J_% z4CXt`(49{k<%0IQKyvbnRhIg7LFjDU)|bi2QjU`(U$ zVt9m7x1GrPQM;vkU8faZWcH^*waq>RC1O0OdwS^NPv1n$nXQk)K`OC_kBlE#-Z>xZ1qbaxlru2$Wkd>Z(8xS@-PIP6NTD*O zP^gi^T%=tOmj`c)%=d60tWr!0ox?PykwO|Uy&xL+PFMg$#jX+B3 z76e*x{?oDotaNbuJ+jMFwdtPReoP~2p&7Ocdo?&9#giy_sw|6v51wC0G<#MJiHqGy z+$kVtMu|5g!VWcGwaD-~QGBE}{E}BOy`B$u@`#>Kou7gWQML*BD}yw=9-bwz?Q zKL1f=iWc$m8WEsaS$l@=X*&yBYq2tFxeKRF;ZA2E8id7Uo2C+YZv(FwSKaas02-oeo>!4;EeZT+D|fLMV&GK1SvQ|cgltFY(6j1DEVx1 zA9Gs+AF5B+`)s-Jgpx=>n|rzEjL{q&$j~`@S$4|Zx@X|2is5=&@=3o>T-0?;Js;($ zCV5~_(p4X$po(?!GT)L~K-_y7*L6j1_%lDfF@e{=6oS{1{||R6w-fmsW6bWz?M?JxOkOqBw2_+drk!Ws z!&p7R55i>%Sqs+-8Pm^=X^g|P-}0G;59q5)X^mVGu^?CoXh88P72NQsX1m2x5wsT0 zj*}M1b|Ljii$uP;JZCOMk3H$?fm*gZtVQKp5Zlh4V9Gt4aHHZXjK)%sRDyIuNl6(# z?}rA6W_=Vt=NwBtcqpO%)9Aj&`df^^+x-?Jo;7jQbSb%OR3z}mW*WsL*9X+$g;DY` z;yQ$!cE^o73@BSP)xcGaf6C|97>rr>ck;k$cA^S`5!%CkIGWitvrvpTWoCte#`-oi zzHEirEoornrE83?436I&^6eP+8BkzA7m>Cf2i~YJa zSM+#FL-A@q#+P~#if}zG!IO?j!RU&zowIDk>IIz9C76;wjKOozt!R_c%DCg#o^j;? zvzE5Ll>3n(xpp$%-8nx{m6wu}vHq$^Y5wk~sbiElCL7nI+CyZfvfD#Y;eExyrCmv= zthyuhCQvcSn)au6UoD^H0hCj`+F)5IRnVYUlX2eLe0p@0IZ>lYo6&`M>-Eo}$woYW z`}v*~^8BaJ@nqrbWP39w|4Zn|w)c@C#6X8Ts-SmmF-281v2AIP2>?wI+ui|c4rW0()IV+APWr2 zaYv8U!{p(Drg6<~ayboHjcbuK8f-G&&NZ_Ef<7yYTad~U1mNn&DT_O@2Solg^w=SskxUz4htc@%D!PiC2s^$4rQ zBnV*ijxxA9ApSYt#Hy_7`iIw68rY=!Ve|x@i6`vb{L>EUYkiMN*L+B|kngHGTX-i;G1S zd6@}t+|V8^td&Dp`x>&Zia2l*nE<|L+GOsSOXlRwKU_f7f*rzaxa=0;s)q}3o%0}+ zghuwel2r}L=%?(Kg0iFKX|i4kf@bi};mM8WO{P0ZvR1krmk%HABp%Ff$ZO!Wnw`89 z7vcThlVX&6tYTv@rD|ymS3*}?`wfW#W#d_|u2;%H7cCAb=hUP4VitO%Gwky!jlDjf zvd{LB!lz9K$%o9gFg&h3&b^M&>q&VzRePwZJ}BL+;#=AT}~S`dkO`ZtIiIa z<^sy&%h&NHl@n6+X?VjsS*CoBiYi6f&NL{cq2$S+yAXqU)u1 z$P$Mt00xX!e3{4@ORd|!=I${he=|A1PYZ2 ze{^PAEl4>(F%rIs5B0p}Gm~D05n3>&sV&PW*2_ovSEd1)pYoARn>2-u0i5a_O0$T% z(K97nxS^Zf?6y1%fX1RS=GY``(GUJ-mG{dS5@pK~690T;MNQY?`}h|T2dcb5)D~xu zSYKemdsY`NEWRgDJjvVo`t=L1@_0#B_4xACW(??A<&4058{)v=+|oFO{?ee}Fdk_v zfkw05?FoO*H{XZ0@JNkOVh>&k#=`ri+rX5oH?Ki16<$g1h6g@5 zEr+uu*~=MhnE7n2!Q&(N#9GS%^~me0-@j5IKRTTp9l-@dBg7`17X>K$7My+$7Q4L6 zkW!70E#Pv?dYJT2>6NweAB?5$5H19H?w_W>_i$%Eywe|;>@FI6pL7?AMOrXc>y|9q z6Ml)0#V_(+t89YTP|;~eInaxpe(8$V!|di^7Th#ujO)=+)XVNb&*P(@>-f9m32hT{ zb2?`kbLo|-*v6k)7Bi7~E6Zu=WY&UCYjQo9!)uGl!)5ZVl?;~)v`CH(>XYbe!--0t zWq;IBQj-3hB*-x7 z`q8_#SyL`q#q&z>{8LKB?mBS?YAh|9OViG0*iTj{ z!ocTZU+BLjxf}UFs~*Yw(0Wg1Yfx7qh%6PCJDVfY$5%w3c-#Lukd|%2p@wzU<(y|(hGBrlV02X1>LmHjS}qFQtR9b45k5aD2jSo_R+jJ5ZcISC$EcHnsK-S?fo9Xk?jPI1l(0 z-H{MlGT^|*>0Pi$DwA14afetp$7<~l%CI;f0~^uul{spw6}_f>u${UZuyr54$>w-1 z4(=X|6wNsngJhw+yQpmdH;o^ATRbdu4;KBU-Oy&}DP!0|tD(Yg&gD9L`}4$Z0&hLF z&CtngzJg&^nlpa!&Xr?ze#Nt}B>LdbUV4v%V7khL6Ch+5;V@Sz$KMn=?yaod!{=z# zLwe-;19pA!jc1ZARTE)=qYXPkx_TvBqNM*JKE8lzfDm9+LuUQU-#gz|9FRpDE-Wjn zP8FWken*f)y1iKjHm(~ZOU9qg$gr@$dHUbY$RJBM-3lQ-99av!exA}whg;0v-fKHS zdnXj@2A{9NU;2D)XsT9Yr@5?1)tVfNRD6_9iHo^Du@I0Bd?5Z}IKQ;keeW_sTc^({ z!2zCK>u&-`aM^pn$d?Wc8GL6rGXXYbhySHC-B9-8Kw`OK2usNL{MpE}gs8URW%Tn( zaTVd8)&5f_u#pLuq%QH5Mf`M6j|3p6wup0-rq-&G z2HmOFmyJ*gTX2`aRjzj%{c7wJg`gyW{|5$m_X0E(oEbEGs8dM_PrUgTyLj{a2xt8% z`Mp78|Gt)gH|h!BgrHo3{V(7DiVKX`9ew@kWPN`N6n?dV4(TZSHrnZ^CqF zPg1Q`a-P-+9@9Dx@v3|A^%r^O7t;pmSKnD99-okaGq?9hhwn!WFTuO8FJz$klomGf zDM8qNlh}4(RI??U*{Q1lrA(R0B^*X^h~it#f)yF&R(B&$BFI&idJMBeuSdtgfD@@~ zgaN{vMp8rmd^RBhyl@#P$urp0Z%Gk+*+=ALhC8+^>gsIH+gHe^dl?{sBC!sbdk#=nk zP7J%eNO0$C9p<5g2%z0ypg@k(01v{ZB`UDM4jX%-GGo1KqNi8gf= zPxR-AYU*&PcmyAQ;)XRxvbBWQQ=0j@F)k?qh{F+-SU%JBJ&usb+mY)SsjwB?7_?K5 z#RS%2t*X44PvI4Vi=)~A0==U~#8qw=B3ii^2QlkrOhb&1J0BjZjg2rX8YfPNIFX{v zRsU4_+;Jj*#grbZFldAqS-gV6i)l;SxSXUy&F0;FYVCwjuzB4!uR7;i>5CJNQA$%q zsOn1aDOZE}@*f2|jP94AFTfdyL-A_kyc8d?OV1 zO-3dKaIH+E{sJOe+xRMDRr_F4un7ITkrvQ2gO4&rpQ4rj=>=M=qa^>)3rvZxv>LU5 zDR;zJP`m5e4@zO;9=W|!2jCLZUNcg8&Fd8$wQCJ+A`=F(D})yr`P+xZux9gz7VyN85UaceRo+opAm7c3sp>QQZ|x3UeV4} z*~#kI9Bw(m5CYiP-lR?!FAbb~-Yx6?(Gfj|`#xh`%$3@pz4-jVow#d%{sI2d(`Gbz zh$}=2`{~N4e5)miiI`G}_6Bv0Y}J6?DM>~q|G5M+&dT8Rwd!!}gKt)spW1jLok4v0 zspO`{jwctm@GqM+M8bg#GYQEyf>>s|I0&cAbE8|m*C>~~IC zLp4a53LKplc6r8iY<3p3{HiJR9Ip=LT)WR0c1LB4KM|c@%etHmoU7h1$8k=((as_^Mr)}LS?_Z-kPct!E8Wbrn&&cSSkgzyIX+m?H8pJl67CWv?28#$qvWfa5t7JFK`*S9 zY6HVPR?L8!csOoRWJZ0`0fpPlVhP1l-Un44M8voOFBV!>*&&7B$VDIGyi;r_glWfM zW{K^%C&ms^7eHtrI88ZYI{od^gb|v%Ct4iHW?_;f*s90++xmz~fK3uLA$D6+M#60K<>1N0k=V`b?&5V?OgHfG zmanTI_=R!0Vb!``OHPEKQ~BM@4bGH^4B1XEJv5CjC`SyjI-DkBuT!*dLZWge@Ll$M zsH)c58LujGXy$P;AKrUK(=h#D-?WhF**-nsX@mVBi86C0BQwbC08Meaf~6>luk~DY zgKyx4R8aT*WsS~~(|I2_{*_mHFs~59d*>STShe)?SrQNL`^h*<;mdp(ie%Q~{TLv7 z6X(4#Kc&rhm~hVDkaltLjh0WKp^3jPkd4ozq-*==y3>7M#c4Z5L;n-1;d66ue{@|4 z*kDTURAypPptnkv{K{TabZD?MdGh|@iNF?5e0k(|1j=KqiXxDa@k1u{7v#BAaDk11 zVQ*ZGgVD0}<@zx@XGo4#^|ZDeB-@M+ur04Yx^09dCNn8H9mqUi3umSVL(L%2b4knG z#BpzT<%<1wT_+pLt)jSpo6R6Y+t*(8_&&4Psman}2i}rl4MplfNc>|GlRX8TgZltG z-UU?|hDInUW0_VHE~z+&_7QBItIia&T#wXJzd2HjfG1$p;w1q7(vMjt`8?uI_6fDF zprSO)sj;kjnfey7iOLuOJtUD>%_MY%8VXfzU^JIp&K11asvI{Kjp~D0#?RQ$Y@T-R zlDm?I+wO422Hj3T z$UC#10_obPLuyA7hD( zCa_8#fz$R9X_7XgJuXktUtig^;@S0{X~8;HXK3@&{PM|b$>-PV;(*7P6;q|_m@2V3=iAJd&&x|BtzrY9F(W;^{Kkr7ljEuQ2iHh zT2u3)GWFm;_}aVPJGQjs7gKfuGajgPFf~jl!Qh%@_YqxhMovU=D!f^*IiuEQ-fFeH zSH2_jv<+e(RAHcSoT!c+&n7EQz5Q}0Ghbqm@ODfO_)i_{!8gikKqNH)S9?WRcljev z7W;U&nFbD&*swgGufxsJ9Yq52gT3u(uPEc?j6Qh3OG?JHw9u_{g`G%{Kag#BllQNm zug@)8`+^vlP!w}%)Mm_7Rce#3>5HT?V=?V-(;Gjsk_LZKGeS5nSAZ`aL&PvPcBr>~ zX&rQw%ky7f@=p|6#%1kx@IG1E<>2;x&%xy(rh76vIV=r@660va?fHFh=UlLE458p< zSbpcQ2^9Pd2(b3`)CK)gM1CI-c@Bq|m7S!D{wp*krtC@B(ysCzPoxJ5yu(0M;Fe(Z zlO+j~%gQ83PFb2q0%`SGznA+)!p1|hs?-=U`u$fl^OR@|#NJI&?JOA+<`AVI{hyHp zukz<^q-@WKh_H}gCC?9fnflz6AX`28`S_PB#0P%!3SpO;DV6G%-OM{n zK!<-QH6Wm$jjrc^>pra-aMxE;J4b6CBS9a-SaPReOXwm#TtPowe$?$wbukn5Zm86F zXp~YkCtUE+;$_`?k?x+-8wqMjktfme3&I@6En%rt%g)QH(h7x{n0M`_#v<_@8;n(( zLPWpji&X$sG^Xh1QO*&*{1Bi6ftE!TPdX=eY@b(EyI{}Eq^OSu)pQ8>GB(t~Fnqzk z821clG4Ld^N*ACFbUN7|Wu5*NMI-%sA8tE`Xpm_fk+GM)3-n56od1RpoV-s{6{Si>w}U>4 zaTcJUj}&5UGas0{#gB=vu-V_&QBJ;?=o71s047#t(jJDrgI|;6Z67oMiKa?R?61)v zO5N)-*vE<~T?wMD0a^jCOctDqo$oDdzUOA?iU#5HHfL#h!65QVvHZ8lkM0f5FR#mS zElC9(-ojif3+t%IoBN?DpVr04tgIELPAXwDU8B4HFnpEEozAOXwsRQd^8H6?mE~Ud z_UU)@qFMc&3>~^C)oiJFa*8^rkvqFQiB*(ja!xmb*Lkok;pr{-ai)uLL;a=ZPOh7ZYw2$?9$T9#d+y~*{m6g|) zW7WuItw)NJ8)QAwJ)chQVXe%Rj4sQB`HRo$9{$|(!5w7)GIBeMM+PQ6he=?U)4v+dw!;)NIdJD)u|L@ggbs{s$^R>e2SWX=BvrV zMf~>2WLYRQX8gep2u|BwW0bu1aeGv+V_ybcZ-cQcABPqtN8&V)($g}WogVzSL?NMw z`}Ym{N(Q`?9SUa%+HLp7D}7(}ZXP|eI1C7|W823k1ZGJ$GDqc4HWy_xX5_>B<&aY{ zGCcd<@u7uqQl_lB9IeM>Jd2KQ%?H)t7jg(H5m6ElfmZBXn)21YPGfE}E9(Ksl9N#0 zZp+QPUQOJN@I@0>d!UDW&L*kFqv3p!@NY~b|6)1(wGu%SnDaL={WjL`7v~qITCJd< zw{3&sF8z=kS&zRmPg-a<`rf?ekm7PM(vz|^L=J81PuP2#dZz5Uq@}UoBYKD08n!c^ z&vtVV$TL*bkgIDrOUNU$-byIsEiccYs|(X3e#p>n0jiXeX}O?XZu~I+XEsRsYKLmdHy+PHaps3Tbd;oAD=`wPWJT`BPOJGsP0_B?BWvCkrmCTkk6u6&pwD5=)a; zhdHM7J_{qnm+vbB)r$aNtgnvrX0x^I(Vg$QR~y6nC5>n+jVaLy?SJf15N(LaF@Anx z5F1?{<#@{;?tK#xn_Kgd3hRob(VH(%9G9b8#D|em8U`nku)kZZo>-t7H~%7b@TzR) zs3IdPzxf^Z#s^~K|FkkyqN#3(KnAry2LIse4#P%wzgN@nsTte_`?Qnwq)w5p5RQ>A z@(~XRYxng|+A2 z!3Vd=FHK{c74!dOviio@6Il%pPn3*Ub@}S59r1&>pV6jpw_%bcjLQ4_RF?t%jwcN~ zkIk2^c9QF1KNk{cS=NsK$g+COtlcITL*4qpiU?gTDd!t@60>B@@sOsC#N-ffCiUZW z)^**fy)mO*F*^w;5JO>-=x}`@+dP)JEAGRbw-xAJJW5(z`LHQ;qqo`rItL7#skW6_ zo__GOMeb^{e70bZ{YDi|?BoR;eU+1I@AhDgL~zr~Ivo8w{=oeicm)jmfLEY0B&sa) zQ-HbIp3q(wMT>_GDTE&)79dGB90)lUpOKTot_yj;i1W!K$XT`g+CUir%( z7q}jVqSgF0^6*2!IM8BwBKYQ^8BoJKeR)IrO^{J`hfFeU4}eSyo1r7IVRwfgcnK`gr7>Cjgl<&VMrJkpJJ9 zb3=(rdVN5f!(nDiRX-U+Q#eY?$1}S7R-XF~)n(l^;2f0z@l?v{aVo2AskyD9fd?ac zS^|0wH}vaz;QWQ#6PmCe)sN5ZPcvoRZa^Kip__>paT}f7{MHx^HvA=f(->g%_A@8LeCo{LU{= zBoG}S5<89peN{13STASML)ds-v=15zzbAeD8Wr@tFe!-wbyK4=p#-jrbUD2BFhJ^= z|1T?&P`Vb zb*MVopdjpIS0B-|_aL8|W>};;U#_e3bry@2mvYas=dGke&MD_@HhAq{!8*bA5kB_R zzfEofiJ||P+#0oaf$@?+uvr?pr#eemK}!!5hH(rO<7}??E33O6Navx*?<6e`sxZfY zzqb^fe8HIy_=n2E6P=k28SIqCJZayM!S}BX9i&5x93${;hhL1zKeGMqMp%ot8*ZWn zHX=^6|0|`!C^{zZ-fUH#sG)at5|!(Gx>#4EdlN%bR*t6KVi8JUbt7!}&2Kk+1}3+H zN1^huZeo|h+i0E;5^IZ87)cXo9Zb8men)>XVm(K-aLw?@I zxJ7{yb$NwM3g71@WBqv!ub`?*V3zgM&_ssAZNxR@YlCZo%p%aUOOVM|MOs6C%9f`L z@!22foa@j=Kdw0YHZ1*GKVJ4QC1NgV%Vt{UE3JVq3n$T&ogt-B@~h?Gtd!b?XU~ou>NW7i&8;W0kMS%Zm(4C} z^+c-R8EBVx6a$=kGzTu=>HWTnB^PyZnYOd_x%0vXjA^DLFkAf(RjRwyWxGsIKQ_v$))tDR=WFJ z5$G2_w^h*D>F66i4rpj-zy4=zeqr!)K@pm0SJl#@#agXGHNKaDr6$`O!q65@Xs34i7gJPhVK=<6eP*!f9;SHpF_MxKO+pVv{Z`y{2nbTqne$E z?e%;ERJauLfpa{#$xlHd2s{RP3pya^%LWuEIEc?1fp6*r<5aig&z7Tp0TZgK#;pL- z;`0ejlK};z_*3e)d2ckpUb!4C2K+d@%+E6H+t`uC{DR+r%6JcRBWL&%Q#byl+NWfsv6=4Om~p!rMd?gN7onFtGv(jn|L<~Q9A{hK?T zt$o2{+x3m4lPSld{c2-8PRSc`nxBS zLVlH6!4*3k0V?bg$hLP@pbkMB8QP*6kW{nlEv+^30Rl;sL?voXL zfqr*TUVEf2jxi3CmNyxB$Pp4dXE985buqN^3aG3uv z&TQz%HNUu46P~HE%Dge1mt-NCa3JJvkExz8rsm?SNkE2{l=hB9!7-!=j53ca0V{I_ zHhgQ>U8vNa%IScy(!r_oh+yU$(Co=VB^3t_3=g}Cs81`7!81QOJDuVr}w-+4{ ztxaBS;^Ksl^0pRD2pMR{@knnpxH>IsT96_k{Obzttw3E#3hmE3;0M3{(8fc9F(eL= z7jzAPtjty_kt!8%U(YxhZ$cwxCJb z?r0xZYgpuGqW_j>&}8;dXo$3%F@T}nLE&Ht?EdENx zdQ5f=Ny!H!Z5~jGH{xE)1+w(m9bdq6+mc}FCUYWW(obE=qg)wKJG)*R`4th~k%ZBR zXv!2Mf>9)yvm~$(PIj3;`J0^zW`gwkxxLd6AQ?39m7h7#*9hC=W2GZPBHm4Y6Y*

    FG{)qufguU$Ycep8;6zh zP*|xLStJ4|BycFuWiW3oQ*wwNxUInvJY7E$hLk|%Y>+YnaWqshQW$V#lcFFe zIMSlvK)kB#-i@Z_U|3oZ|6?@(>9}(Gf^{?wpEa@S}))%2Bd%FaS-(P&bmzD>do%W!xViC_u)GFR7Wf(H9MHH zBZ@=;!fQ>JRCbA(@-C;kaYv093Z%ch)`q!3e(X8`pDa=)x=nvvOY03dECoAA7KAZ^ z7c3b>)w{u?OZM2J`gd#N@L^`(4PbQUz|wiNBbxP<0-_-zGj4pq1=|3w>m6}As;zTc z*f1lIh@y(NkL-(7WKAJckTlDM!~u1rk8j=TD-H*nEba+(|GLBPo{_ApmJV)-^GnQD z=((z2jKi&3%$`ScwDVXD4X7TZEbLx#8 zJPq;9+#9|1Y(BT!qk~8hx7(8#tA0gB$Z!wJK6Fvd5$u#?-3i#W7voU%mdCOnVIvm; zoXx!{{>^tiTI!J3Cq{36l#aT4nSQ6|r-V-!Fu?UxFy*(_!ROO9zI6!l^`%1V!-S&Y zOowAV20n_Xev=AIdWhlFg;LRyfhOhha~D1%A9*X}DHBw-h-6I#2v`?_16OzN-I zL#-4{)yn75_vL#@$GbxgZCMT9!-AoYU8CUgBZ3S>C)vsq?9+_wsJF^1xag*2_ly0% zIKzCK;m`P#c25;oz1Cfy9G)P-`>ziQFJ07_ozm&q7s*o1LUO?+-ho$LY)c7{VM({P*YgcQJOD z>QC^`3Xp+U@OfK(%7xRwUauYSI!8)jVZ+E(!i>3aGID8euMgO~b7;BrZS@?qMG)lAP^;#1JmGnyWUOU)62(L?GEN;p}<%EzoxcwMB?h*``6G9Wda56}a_SZ)Ig3 zDjgPtfdlqKVVxPm9;_p*nY1f$Y-CEKK|$3nuegY4HK@^L_mQZf>I= zU>-sqTYI?;it%+rg((6h31igvObY8h^1}l%8S*`vbE4n3b!Kb~P~@|0IW+Fkf7m*{ z&RY$cnlP)8B~!g^HD_#TAM8V+^FH&rR8WE zJVVR#NOiqE6ax#4WYXB(onxp);39W;+l+@8ZU_*+z%f9tFswElg=&XdbtxV>pNXFl z?h6Hw^g@tjpR#q(?0#q5y9JbreUACJwXQ=^BJdXjW-=ZAdudjtj~=j*qnwD?bJcW^gu%AXlo zE@xbLx~JfkQXiYF4a_`A*AtwW)PKA}N#%?jX*`BIUwwD`V5%9K9#NayGy>Txp# z{{s>PNvyZqzvFarnYZ$Cx(>Ch;3Gat!B;Y@PKdyz2e>y*IMa;bohAl|%nVa~)r+xv zWvwSChYtb!UU%(J*^ppDG5)~n?&qo0gbvDnIFY_x8E%JRv>7$_g0yLobRC_w^X}JI zAMwyciCtJDuIJg+@%?+wFoC=IdL)(@KR!I@i6_AutCKiiy3Ln8pJI1K&n4u%co+^)Sj*tZYCT0y%*d*mm6h4a?L( zwv{x{ih9_|(|{aEcPB7vO5x!mAfSi$wd&4BEP8^XMdO_-|19y8tuw>-R#wcrZ&x}D zX0X>feF*5)+>^c=r!va2$*uw_>zIn@-j)26GiOSawU>3l#EKXovMKXnwC)*9W02IT^JYmh_hz~b{X_2N8J({hmD|E`>! zex`CKfT`T4nGXB~+ONO(@r0N@&P`u_)-Eqjp!?0jtP@<0`IS%v{GwL%xiq8{p0Tcz zjRm%T?q@m;a3q3=QPxq_q;MhLM>N7$u7a`Fif4-Bfp>X)I8U@Q6H9Hsv972 zeGBF-SpZ(goX;NUcf;L zet#r@7>;C%U1W@6qm!?AkDU%Ab4+3HltW3iHW^sq7XlPPA6W39@}@VC#3?*8@WfOD zwq69|8g{<3J}(5k&+CMZ`-tkn`3t}}X9baH3_23Z@9O9l3$&kd%XakQM=u9#wN1aV zS0Ntj@WYV>a+6V49WM|u=l?{^+!w!Y#nUb<(gtfVJp2gR9^wds@nvL!Cf9ltNytcP zsrBS?%;e^(qw6mN1h+0&AP`9a>ofx1=Ez7S&(?@w;4UwJO4oLu8l1j^|yfj zDV6>z@sL3l8Mv{nZcT)sv9P<5f34eJh05<&02TLFc+l>TRUp3L(7*NfKfnG{jthQ$ zCn=2hmo)#^kG_Hdf6w-fpN1m~sN~rW;64T(wjc)e9RT}5b|w){i|$UL&dSMC*s)-6 ze`{o5e$BFwkLohcB;9_@CPd@=%d!*csha0$nWwy8>$LA?oD0=GzUSTRQ?-rNcZ>xY z1GBtcPdFqvE;<$;-CYI}(w{Z!ir}|797)AlT%?kjVtFcbq{&8zIJM`lLZ3#87CcNV zig(5wlB9CU4AjT#HeJLVmoae5P~P4;AI+T2tgpWN*_`(4Y2`m=eihVw_~8=Idz)3_!3S-rC^Qf6_P~`ush5jK1hg#=59Q)vB-Rd!PBtX_sfNY}zVnJ=N7zTN|mf zrkL!WpCOpVYkM;jG2FYmUQ(((P)DW8>)}UF>^Tyxx?Om`MCx+YHT*bD%B+9!c4|&% zu3V~;t<`W*ldEjfmS~&@KZ<#mP3@M8?Db0n7P07d#&#I3u;#dEJbc^o%KC%HuBaBueepo^d=b}mSt^3*`8pMdpS`E zd(_oD9_89<66d(oGlfY!tG-f+l>X(*u9>0$nRO`PXn{g=79UZYgPW5n79He;)<>OOXh*Y|3fp1s zKhUlr@er;XHFwia?#`~5 z_(^YL%C8SFb%bu*wFa1!j^dtgaa%VZSfnu5nmgk|3auH9wTrW+?PBeG1{Mmj#VysF zp5(7V--<`u-4y#QzSd*^{y4#`Qd4#(jKd*^67p6TMMLr&gJ`%RpG=t!>cLzRhG+v! z<3%QUrM(w6WHP)m<-OMNOg1J@nQzhOVuwd+2yJXPGoeK6%Ymfw=M&-nS80-;3P#-} z*>IPt`LozceA2(JXprm!OQm#izl~(Z5H(lUJeKK$eoq?5Kg&zs(mT*nUX@wOFo^k) zw=+F_u8EtS&Sw8vtg(9~w1wtbk!jbT_H4;!&*4i3`;+fC-`7gSg7Er-3JmE_TBfGo z$_=x@-WKlEJc=vT8qB?<8XL>13=I7JJYl!uI>9gIwzSLl?CM}02q4c@nnH& zI?2C-yhQ`WQs|?4<|@hSqCGwg_<`o=`E|?kXtBgs9?6w&;;Gy~iV&8jN!Od4*UDmN zE1(eh&I`!ZR(!++3+)B=>@u*SB+>{ov5n>J)NAWuqenUQr0@rwNP8<_9hVs9Z;<7b zl*m<@A&YqoA}k=vx48!LfeywufX3R1+nOK0lH3u}&nb&ALkaT-2^vpLN=&cdm4%va z9ufCOGnf?kIEhH+p>og4$MbY!=8>_%f6He-wF}}u z0J){%UecHm8wd_IxYZ5W<2U>n-5SFSg@<G&8-%7AU<1Hs*yZA2_CP^sdEIP7!#n=!cYS@e?tM z-q=9&iD90x+5U<|-WB2KU1l)g+#ElLZWUlc7z4z|L*`AJc{ERt&=0)$BmBS{unh(< zdbzG2QDn2;HpugigCAimmQCHpwysy6`@^W{cgONeO4k~0Xc!atSJe`H?Koq&!f&Er zP(KICwO^-wI7F0*_mMWQmFxrZ&*Z1RG=AjN*@_jJwYGk_<+oWcI!VeXAM`%dKZYyT zJEYE>(a}XyR9DUR!)zOvq(w?dYtcXWWOmN}f><229McwvLmcZ5c)A{p{*JM%?a7#* z;eCSYZ_~rQ72HG%WO$WdyU35e+sBsYKc6pPkuJBEB6~iAO(ahE#XX=#zBxkbB=u_C z*s*-IU2&x3f7LU5=@WisD|zxMK;9NTs-==LS6sSxPaPkd%C9M@TVivA;0 zMWz;WuRAztShLV3y-hpPB{u}H8@<{cnr*Y(g&pB53y5ac4JhOurgyH-F zlK)Dop9bv#LvO5GugaPF6io_n@ulQEp%F-8;#N2{YR-azXRiu``H6qXW~seNJFfS7 zW5o=eAa@VQ*CBt!Ypw!oom_LfKNphU^jYDwt3`4;J)wKN`W3UwZR*EPIU{Ce)}rQo zmRi2n-ygj7QLCU))hAXIi&#H5Q*eo0J*MZzw(dglb zX9_a5c)`+}LimW+PwZP+)<|q>H%x2c0s_jEeU975+u?>Rn69JfR{i40TFwMDs(KRj%FuC>`ujXB7Hzy(<+s6 zwQA)48bKzrrFOvhI3DW3@&;0|v1FzphZ*Dimp2cEK{$zn{zrCiWASbv2B>Jgu_ZUa zMNMnX$5}H2&=<|&nN;uwt3r0nt%FZM`DLd!wt*Krv_3C09wsE&Q`r9svnyXV2XaiR z4)3qXj~NYLx{YZR@6pIxY%EnlNR9Z4YRa)$C`2b~F;GfsG z5b$C}f4^BHfwWG^xH+wgZSmbEK?Vu}2{R0RlXjLyy$Q5FF#TE6TK9V=ZMj^rp%z)eE% z&gTN%KDeVc^(_FE=fclnr9l7Kn4M*5LtO!2;1Gklyl?WOK51$4k>__|l2vyjmXhf-CaD6%K!i@?#+RyCto zN=|rsAr`0*-Jz)QQZ<##${<>OdQ&%&gfehO)ket5apdzc%CZNq8e_>RG0+ee9NJzA zBm4YOUHYqp;}0Tx(XTFB4XRZecsKcmHB4P1HnZ1(=dZ54qNmA3nz{>EYj}TC4{1b>6X4vzQB1QUw2fq^RC%V7N!h#cEvg1=M_TI)wB`xQ5V0!dgSC`pSc zegfw#sTy`u?nfhFy~tQHR?$=O))u$esAa2z=gI~YtCV$aX?)G-X#(_YI0@^+@F9<< zuYuW%O&}yiak!6+c>(oi#l)vH8P`{x(vx1axX@uHt&g&oMjUST)Vw9#(1;lJ`ts<~ z!mc1Oowyit+YAPRoY{8dUGrJdXw`Z)CVFu3d#UCTgkX_i zr?~J|Gs|tz^%Auj8#}g6+VlO3!?0)7sB(g}D>oyX){a013}B1sBzipj^NT7lKzu&tkdXVJC_RYDoH5y{saz9eWE6M}(m=4hrF8B^MHHh-W)$M%4~ez~ z8-V`u8RGlxSW^G;UR9b2`V1Y-f=3dF#mb!>_mYG@CAizlb)_qpdDWAIe;}}&Y2{b4 zkipKlZ3?qw1_`Ey{#^u_5H4mWSXJ#1NU#zejtLu1INA)pLR<{4%B6ow@!4w@@A%?_ zMAW(Z%9XU-9~*l%5S4MBGrhAK+`%yZ*z0;EPgm%b%y@VR(W5P}pN1dG3Ea{*tqGH3 z>J9EM8(mXjl|Px6E6mn|lTbWQRPzB@`Dvddt{M6V_vgdG2lmeg?Q`R$q*q*CIMAOp z%@h+{-^dsEBdHqQN4Yy69zE~8#-N}wgoG% zAk)VA`&uJrPST7HfyGWRT%N)DO5lTTWZ%5`e8+bo^HE+2TGT&c>qziP zr-k!(rf%c^ze)2?4bDR^87u#vGVw4#CJwajR(Nhjx8BZkz!(q*>4*;@5N>ifmagk2 zKy`B-F|IXUpF-KJlKB3a;CSeb40drlWreJWTz@5Vr>aW?cpPc zi$&WbUU=PzNS+X9(pS7{u2zSzk3r>}G&3T3>W$3}{k+)P=$Y-+?JQ#OPf`UwZZ;d; zx2P8u!w!MElnCd8-ac1ZHV})F>tXY63vxT3R;HiOl|ABIekVabQT!EYvR?+jW=9*^ zWV_g-%7gO@b7*7)!k5pV1x4kiLvrM`8C>DAu&@M*dke2qVKoVExy#nmqOPe^`>M zNoUuW+roh3(rrxS8jdjK#_-5=j4+HF@x^l#guU9j^l!oX{C65aQ`nJSyrXt&)O{ZL z&}XRqtmg53&)oH&vAVTgCox@?Kru)oofWUWfY7Y}MnDmRejhhIA*QhJ+%UX)rguS4 zOG^yI1vgC}iq+iFCeLZPX9}Ve{w**3#1xvw92vN-t!>EUBs8?=Xg^I27XM$sp#3*6 z_yZE~iF%hda+X{fU1|L@gl8N2Gla)61cdOY0b(@?F66-6a6FUn{=kEhT^qQd%S%2* zHq7Y3W3KA2b!kC#KBcIAd!;IGX9l6`Qmgvka!XWt7>7=j&b~72CD_)M_e$+sns+4i zie7jI{vv9ccX_bkc%tE0uHN8|`|jl1$(2~XbzO3+(AaV`=@}+bE)McLDDDsiGA2Bu zj?9!#Yg?8qQ+0g$?i4>wrcn1m?7O<$({yk2y_rfGRvpt1#u$l$+%&Hhn;#e1xKBcc$od!`m;-e zUttE`_&E&i1Ztcec(Euz^nDeIH-9LY?OWoC6;PL?G1p*m9l8s#ac`Ey?YKE`K?bEy zDfX(_m`pdwGP)SN#>KUlh6d6lXbj;cnOF~i)X++;*&2i{zq#XUtoHY`!kn7O`e`oo1BY)bQ75`x%+g-&N z#|fjqfI&3$rb`3}c0E2Q9MCAyu~;MSNUlW*wUo+dMO*_;|*l$*6L% zDf*^*C?FK@Gx!tt69uL5rWF%AOZ>{3017n78vn-Kw5zp@^w51eQYGe>*Pq(T+{rKK z_9u7e!q7hwf2YOQtnafnOj=fR;Ykf_kmfa!WVj)X_=!WzxHaoJpKfS9@1JDQ6r7N2 z-in$#1P%_sp`If-*I_448>zd55GSS=_jZyqhN(8g{|ukK*ubI{2X@o`MmaJ(0F(nL z(RLUOvh+l&YZVxdM;PbDf5a(?l*Ua^%B}O^HZy>efOV&eIQT z5DrmWY*^fkG=auVS_Xm=!6C%I3mL$CX@xqR;G+7LdE7*y8HWG2gQ^lkY1ZhfXR-t> zIQ;()%@YwZID2A;-Ge%c(spO2;K#-VUp%Pef2q-7vbNtMmTlL68yP(NmC|JMQ^!w(YUOH3 zy=)J$X-&u}uBLN&JSyq_m^PJ_&b9tDs$?CN^h@eP@az7~oZx=Xx{zLrVl7I`!x@@5 z|7?9z@Ao!~x4km`Uz)wm`S_1p_@_f2A0FF;>K%q!5}pSsJ$^Y8v`wziy5BV|(O}sU z4smEBq;R+O%rF$JJP`;*ZC0&q(5%IEQk7^U^?v#4p=C~bqQ34pS+7QQ%e^5>aOV1J z=RKJ7A>8=JU%?h%P|8K9#VLBx!3LWkri?(HHPbkVWel6~MCmL+`M8mwygD-*m_w=> zb66L6dnf(FcV5|Yy9If#+?)_I9yY83i=2}(TQN^5(0X)JRCZ%AWYPFU`nNn zLZ96e>^XO#v1}7R^CO}p90B?w`h0`XexW{1>KwqaE8pJRqtBt#xU5*tZe&c846MXXUM8nV|Qb3m1b;C z57G}A@I;ls+H4>~!3EwZx@wzs^X~^9f3%4TBWHVO9bmo)P}gok;xX9BV};`;?Y?3o zynN2lXgTuL&X(u3<}Tx_%pwZfP1hbd4nuq(MrqA$qN3JJ0%{P{Y@`c7@Jx$K zSy(z6Y=}ok94<`S(4nlMQ(5kQ#01<=A4sYlJWO!j(UCNTI%~KgonlJ;<`SB$1~S9( z+I2;@SrrNHy&UsgcTGnLq35wmAfcKtVa?3gFtPRBOmMr$^A_uczb{SMI$DcD>|cH;=5fpLHs|fxHtGQ z#L8%-z@V*$VF8act$81|r>;X~GGGoi^490Pzu^PWR4eUXdK~`BZB{N857R{18N9&CCG~>?^SG(& zs;Dy0PVneiC>`r+ev~g$`PXx2I8D8Pn2td%h_Ry7re(`fLOHP7R4y%d4jt(>W6y?{ z)}7Gb%b`mo&>2(DRc!MwJUWXtjOcWJ5t9f#A*dWCFT1V`Px)s#FE9IEhPC#@-Y#FSI7GL74kO2tzH_A@<`hW1!8vF5Hx0V+9ivQb>PB`?F4& z@M?8z+LMLtn`0I5SJfCw-^CSt+RrsLp+^@~ZQ-wBdhC@N{h7)omlIIy5#F)PK1u`u$U1ywZDySB# zE~rhYc;#L1a#*txGEn~;@Igqj3sW1$S<_V3U_Je59Ml8#{bi_KMc%`1qr^+Ybr1JI z+MWj%bM=NlQ(BRw9{BUmJ+qLY-MZEO+3s7eGu|D1A@fEAHL#<%6$T$-*xO!; zyBZ)!f;4U!v$Pkc9dLhVt5hyMK|LrC9{Bg#K=EFpHkA&HFFkEG7scQ-G|ybsI<{zT z6o(Ei=rJna`k=M!Y=nL#zOhZUSm6M99$-Ew{ZxyDDf}z?SI_a0PohH#4bbDg0M*N$ zjFNsb-}HGZrm4~Pa{K(Gy`2%;Bl-yh1_qs(FaF7E+fRB95&`kUfJj8)F-){?F5DjY zpJ=6#s1iHjFS5dj?y13!nZ+F&-kw)B#Lt51gFm)&57qrbM!Uw4(?U_`(rnqgN;DD? zMU!H5h2e7(NFWjwxzj7#hwU~WY(z{+YK`+YiuEE!WN6l`sy<5;h5!@dMZekIRsV3f zSfRX}xXoJ%a;yzbE|-@uvf)0zu2sfMGdqK}xKg_jC+X|`8Av?7Dx^)ei-$oc zuq;F8W|3P>cAiEzSLG36C_iRrp?aL9G#=r=??5;MC z#>SQtG0wQD92*}8!F=(3`74;?Us;U>yVrqCT+G)8@5SUkO3#BG5?+H6>NPXZrHI>ZEjF}EL$yDCP|AAMSeDu`TF0r3H` z;Ca5w{$VpQtD7mM%IiYpJ@M;&Qit79&1{l<`wM6s807%eeSZ&lQgYHTcEhe+@D{7%pyXpC=+>6Q1F`*WXvJI@NWj{p@ZB$OQQZbSNf23lJPH zg^4n5Q&ZP!MRpy;<$}8!h-@yZ5_N&F+kX_S4ASTL=r7mJ5xKCvsY@HJR=TeuPM0oSV6ZWz1< zYF`0_A=&VhD(-(kNmn>+Fdvvg-PwV zYXT_h$4hE%L!PZNxvMHToFw$c!?T^%FBtj!0OR2}ug0s*nF}^5-0FQ{&>mBLaUuJq zLmv^dYImO1g0}PNjn+|d)I2lgSf)&Mzasrb#G9A5I*nWsi9GOeKr%x4cuaG8_UZm! zrln3IXbIu2G1P{#f}VtKkM^r(Z%l9Ks&^#6D2n416cc8>jAA+*qA--fE&bLY-oeO= z-#=l^k!a7giI-pFSGYC}0X@`n=&>_Li`IC9wtIv&AQfF#LHiw>;mwB{5p5QsTUd^5}ks-a(Gm(?gW6)wi_^H?*CXf4J&q5E} zWCk$B5B3CYUdLjCfc20Ff2N<>$e#&u*z1vl)r&R`5jFN!_y$cWDjzyyjQb)osl@mN z^HF?8k%;)nYx!XUDGeN6K*Dm!wnZg-4RQ2qEtvFHg*w}8Z~-MTcV`y7)Zs&lu}#Ce z%GQ(>tIR#2<=P$A)0={vm(CrZ^)3dUB^&jJf9F)y@0@B~?zc$rfbuhunOmPBHrWmr zYB#SxwH^4R22-4(_$*Qi)POLJrJ;1OiF$%VljeWGLK!vNF)b^{pX#YMv_@?{Vf%1x z9RHL!foL@Rl%N_6z`~*8KsWll%&`-W-T4v`sX+RkG@Kqi#BB>xoR1ls;ko+hy_Lif zc=`>t8H~(d0*s7UNSnvhpHQL^LnEar(=$IxhT+BUxkG2+(rgSQ=6@g$KDiROx!4$v z`XI9Zbs7C~gxI;1`#@?(&zw$ zPR!D-z@6Qs|7Eh{b|K7h+XqZzJqOTyr`Be5&7Zx&`Dy@=lqe1Z07>J0+m9JT!|COA z!#My*6|17ja(d*0q|sROJd5`*Ps%PZ&=8e4V)g{(W9N{q;&SI^LOc4J32Q9vP->64 zG`;&AinE{n;_olBzOdvnT%OzDCe|N~F@xweDL_6U-}mK_ssW7C2C8@vXzXMa>Q4BR zW$}lO{8bItrJCk#yxpy?##WN~k5)Vmo)zSj?3i7*`OV9(6B+1mX$+a*?Dsaiou-8< zp0H%2w<^(8e*ELVFRB0o=(8Tt5B5FFc6z~B%u%6bRv#rKlF*P`6mY+9!xvM{K~tVZ zeB&6@Ngs)!W32KXVduQ732m;^?k2@&m5kqf(&2-|V2H{>5`8@J8|=kDcb4&}+4K!^ zI`z9x-ZY#P=0){~3H_{naIskC2J!tSt$ow3n)j*ZEUsFz-C5V*p4EqHw{NOVygELb zvPAUxrA3Y>iYH6ZY%5zJ^_?mq(kg?&IJiI_WBa7yiB!v`-@1(ly$ zN^f>GRu?oP3xDxJToE!mehwsq82l5vuk)4Aq3_aYRZ=FCrzH7}Lh`!+O}Pv9dSyQnPcChfqfB;lMc$9`kdxyjU=+ z-7AM;eCRFd=}YOoalBYGGnFCaigM3R5FX{_14y<=aBOEm>(%Zr^zP6>{RjU-W7R}| zAbMSq7@NGWI8s>0CN7+ub6=y|nN=_e6Cj2SsuB2pFcY#@MrBr5dfw~qw=A!p(;^bY zSEMgg)}#duv*qjDmuORNtP$79D}ImOEl7Ze@%F&p_>pbc^NP(+t#NWFnFLp`OM%4^ z|D!G$eblvd6ETGVP1|1)6Ks-*bme@yG&?1Qx0PRKwq&+>JyZu?HwYUyMNP^c_9zM6 zVvipfFwcqu|J2%a|J!$b)(X{>g_5y{H4`o?#}AMMiQvN>=+1NTtr0g>JH1AAk7EuW z#L)jhV4uGHMqpTS<}nsP5@!}h^RKJFcAL0;d~1w3o$4{0vL{n}BCf`hj=h8yyBW#I zYVVML~v@mDt)Kvz_DZu^&5_ml0BTwM@-@*riO-*OVXU(*%y3#K9!tqranZ=DwTS_6J zP1~kT4rwtXKxu^wk(O9$^aZ>h=Hyg5SNFA_($P9jW^2ayh{KL=y*)(U)h0@`#M_dM z9SwK!gT{%kc48#7N7i}xE^$%`0asH&b;^TI5%2ZKhu72P6 zSnSyy>`W)l=gmVes{L@QZ%a@1v&j=k4>LE-Q`X@bt~o$wRR0(53Ilxa?UF_ygddix zL=au(_rgY-{OY|gik|BHeqD$(G~gR#_uzB6N-tRznT&V=1B~6?E6ozh`{Z{~@yW?4 z3d}V_>~JYw?)TwVrb=Yd9n(1NRL@;Shp0H1WCb*KY(V%|$Qo8|8;dAfPHU#`KMYWU z#2*01g*^rt1f-t^`*E>Ze|*Nrh2BNQ=1p7a?bnPQTw}?4XEcU7*0tvGry%MLhL5my zFE+bRuEbp;&{3p#JnSk?k9zXyBqVAm#27s#%97l5xDscqV2x02LrGzJrIS{+w-^2+ z>D?1wV!s1HjD6=V7?)C;j{k&(-Qc)>drxd!F&kJI2~E&(LPu>*=^|Dpy}qgy={IiKjae{MrK@=rIB)n(h96vvxkvb zKH?9w{N&z_BmNlOnn@5QrZ8vl34WCg4Lt`30Re4$+wPr=E+9&1%yS2q>G^xpOF#=X zJCk)nKMP=(@4vq;^|{E@L3sR<@2)stU&;yyATc0eNvGhvnKdL{3DW@@O%#C15&9=K zs`vZ@8--7wU9h({Ye0ZjBapzIGsH01g64Eh+Ryhj_VuSh+s=*c#}3jdr(5J5!Cipe zU?@C@Q;hf1MOd$CuW6X&8Tj2_GYH}1GoARhCB{uiDY{*Kl!q-}&oMU@SJ7FY+5Dl3 zN{&E)kOUgK`K#+U_T$YmAYQPmJYvsDg-ICRJj&=uta%#u>K+(o91I|qpVgZ-=3Di# zgXD*^1u#7P=D!%;N6k?AEG#ixx7u)5Bj)Pwit`X7HX z^YvG@YYY#x{S%$lY1-KDXlSi7)+Eh|3rH}Wy{|%*$#_>0!)^CJ?^#FZFE?T~gr+h`oTWF)14v?0Nnx9_{T#APD1%c=zG2c9$kj#!0vUydm+RRwbxKQ3Mcr~ z7fs(eNGaH_rM?vB5nV9#*5*E}hBqPXP*iuW`1&f$HqwYDY`5Y33t8=4{DG{3GQZ@X z?cFJuJcX7!D`6_WZ3=4yD1yp+6k0;WHpvW;` zj>X;NYUd!|)vg?_tZvMSd&y23F-Tlc?6qR$&{PP%8Xy!k-F4171#1cEg{6dy37aPu zVXoQR8Ehy3Y{(}gm13pMlurHFHzR;=!K>Ux?%4PVwAY<5Osrt{LN5#9Cr2)p*xA{E z&U8y}($5yDt&zm-u{$6k=iW+FE8CzH8MjJI9)e>4!P9zt23CJqeN3M=K>9{-ATa0(L|;9YyLt8tjw@m!N_8Y_^#z#`E9-ih(!h07w(K@x4ZP^KrPIjiL9Q+Plm7HXa&&}#|6wHWcgTl2K9|Gm)Ce(Ny94~W z#h}&RD%0>7y;d7!ejj$uWp-m-R)mKW#DvZuRRS!oNL|)QmH>+iRs8|hh=`gS*?V5* z_}ETAM7GEAFNoMgr929oi(=x)EvqA{aHMZb6;${mUSS-c8otXe91q%>QLH8vU02rG zYY+J#Ng_4yAthHn5No~E;}{0b-Xa^!AxIlJ_2!zVy8#WF=;Sr z@WysAY0@z|swB=cVYa+A`bd}-h+MejJJ(Q`ez#q(S(0J{O^OzJ-zrmdtORa@{o3^O z2;hf28hXnOB%l?O@N)A#9&Nb;`L*VbZrOkFPA`q)k!m!?RPq0Hd1kwVR5MvZCs=Um zZe$m3QvnR{ukVu-gp&9DuigUx(qZ_Wh5v+!OEQ8%5VpTd$^K1zc?VcJ{y+@z79E5F z|7p{_f9Qim7=$tj`rkg<@B!ku83RVL69*I$x@xII@9+ItR8 zU%Gfd$Q$u{MEAXQT<({^Ru0Mfv8|^QK^HwL)w@h5ovtrmr-9oAgZs*I4F4!3M}!TD zmLbwK534J`?nrM@%DTND5^Pkb!a$V6LSDRU)$=Ec=XT3VRvom zMW+mYFMzIlM^mOlO`u!(`e2A2H8QRayB#23(!ZzK)@?Lq+SSM3#!g!|m06*5oe_gE z_VJ;|;4h-zsZ+}c2@Q4w>#*KGttIVt14K=uM0Ei{>LTBDe}f_McQNPrp1z&QJ*n10 zmLtAQ;W8epd+Pa#MZs56TWM7+S~un>(YX|bdzjhLG2293$)M>}^W{a}Y~x08ncZ2p zp4mCOh3so8Eq3}x34PQ2@Xc%FizvPQpecTO3Knh_Os`yzG?bYSoJUydpN&0w38OLb z5(z^PKt1I@5bmeM7DvQs7d5eorjQ3X0iS0?Z_`#JT|sOj;7E}Ubo;}G$ynDNI8&iPK~OM40^M6V|6E#vyXbCgdHLr9khRv{pg;dv zmoOSR1i&wO!t*~!+&{da$p{Lw%l%WUikp0?Uq z+(G%*8I92j3`z+K?rO)NzarM4FyIbb>4$P?Yi~qM@z%X+^EmM^KVZ#U?zrIwBT6zn;ht3OL^ykT38w0C#9HqM?QT`B9b4ot(0$xrUlcE#JP zb@Fsgjh)K0qPP!Kn>fQQ;>5AtJ%FL$aQy0?&zIGQblPHlS>)FZRE6n178kmk+PTw2 zFWbq8s#rI*Qu40m`@_;&L+5(_@-g*+?PdiY6TO-^V`)-@sK$Iq3=^4*FcjM~kH@`n zp~`+DI(J8W@RxLee&~~;ZLly1Lq^w@;h%=wa~y~VK>yRW;ibpiGWn;4G!jA{2}@AR zx52a6{i9j|S-upVIgtS1v5tL*hiU@{e-;AY@HFxc9J`No&XuTbY z#?-CRe!wsfl}7NGjiN&2x{%DM3%~1I`DH81C|e2CrptB^UMAX2)BXB^X_$@GrK9e}d%$5*t`myIbQGt`K_&2=>;CLS(!^O1`$+b3l*i-bX@S4X3|F}Awe37qLr z?K;;tT7%VgSXk(N4{T%*gvwF7F?y~ z*W`TW2)8<{b3|?s$^iXL0kLHO-l7DGlbqr(?|skgoY0~%Z6+@M7ci{@$6k^p(oC_s z$-}5v8olV?Ztw)DUr`MOyaL|H<`+vwg~yUfN-ff#da~Tw^ZQOlF-ZCg^|y?0*&Lw4 zacY(TGy4fu7$=oi)xsva&+|f#CgLa8&Fwl4k@Yj!o- znW?UJ0BH*oBhZXC$6}y&mE&IwUz9tf?48$lwqNUPP{*XWzngsu;4$MrV3Z!9u3&(F^zM z)`O6@`u5+^&l^|-NmW%!o2=)yU9Rlc;os=T8LviLY)Xvm78;K>6FPz6Ite z3vlBD5+-z9N&cM14OhkSCTZ#ASWN_c)YP*eTK`i(Rh-0E&(!8KNf%_2^Dg$9ucGuP z2*Jju&fDp^g873<9CyT3?-$!-6_9g=w3qosLn#Qkcl&6mSSd;^mQ??C$r|sc zKcRxm5dsKnY^+2e90QpS4-pY^tymIR! z8Ufi~y#PiOF=$6MDd}iafiJ)XV`N4!27BawunSXPA>KN-K-Xw)46K%$+R;FVuDKb6|hLqz;wnZyDsWnIZ>TsQ*k--9b20ufu7TI$6?QZhoF@5cj*F8d3B; zJof(1l&$NaG?#L26=DYHWu#Ueue!@MEI%6r#!AR=jxYr@z!S2#}+1s^MI=HA972%n?L&Tiu z@NWI&V)Uq7>%@bGsl9tYb`1)_O|E%n_bP^i`iqOR-q>$4hfGv!a^8`RW7;V-(P~wC zDK>{w`MiC-)HMup@f`|JnV~Vc1iZf-@hY&1tRa;3rJN&cJ6qZz z_5=|1ho;8V)yNtd{TwE`U1gCEX-&WMes4=2NuSh$&%O9w zS(YQC!KD#x%mu{eA&FQjiR<6HgJu5sWwyFlvuiv&ToIqa=lP=)T|_O7$rTH1GeESpX1HuTUw`6Y!@|GPkJk%?0tS>lg>RGHj7^@5$IdZL(AMF-Cs;X%OINeHTb09C z(6Mx0c_p<;2V~ROsB(ZxMlFoJ`8-^)Kh{{5#WRVT+Wb}PbcX{m!4gcqpI3p=>13GH zurgCPg_D9RzQiYq3NX`)ynIUo~oaz3{gm(JD1{UhFYg@|cd2Z*LK?*4dA@+RAK2+Fy+ zzH`37E0N8etV0A*iDR+AYZEOkzX`NWt2qE`pxPkeheCK~msax?!B7e+*T)8yAw~SZ z8*HWb!m3GN?-{qlz>@&|ZSrK$T3Y};C5}-nnxq^S{UJE@dmnVaSmWM*#lj{PBuo1( zBpHVOhmeFxTmg=|SY5ld6YV)7-p_54Rr+Uht-5HN%Xg6~u<4`U0(V?7(n)UoC{KfkdC12%JX1zsJ z*loD8`-(+{jRTy|xcd5kY1XCY*i*;ttUGRUrsmq1yPQVm*zOxL=zwU#>pZ#uBj+oO z{sCxRUs1gY$I6bu&gM|X9XWkx)!Y-rT%pklQ6x?^c|IFXx_!#N4@L{ zWFtZRO`=AiXmGG=?f0!{G@IKW29@jT)MPIOH?EY|%sl~0V4^U)N`Cn4b)CEIbHnZ^ zx!Mo|1=aB0H*HTygq10OMJXow4lH;AfLc_GDt(7XP+D3P4XIf7bCJL(2nzj=PFxG3 z7{dmuG2WVb;*TbSrFBt26vk5%;i;&Fx$sWGV1(NuDo^sgU+R!tEKx3an86x;VJ-2o zLyZml%=M`|fiwY#4z)h)5u`NL+@FpT?Iawo-#0cNCU>^E++Dd-DWHbSub<)X1UzOl zIW;=5*$%v{!oQAA`7;)(~P89A3Im7NXN9p*opD7W$Tj^nH z)ObSHR?)C%4EQ1p#a#jCdr@I~>q1)W9`q4oB7N>+HCXn<(jE2ch$nss&XbXrSjvE# zGktUQyC(?3=$#b$*#xQSyFFtq^1j&r1(lc&ERaP%qvX1pkRnfXc~UUgD&+7Z`6d#( z&dRv8=`_M&i5KA$&uUlVXE-n~EtBH`RHADnWES-K-A+SKeqD1cW}u&=9qTka$-9KR zld!ojqMhmMpOz=t^iZb%At>P)efV2X+MDde2FcEx24<0neaX^!x17EiOslN(BH|pU zj0Z{^O4<{Diw7dmY3Jz#I7kQ9<&AStf?2cX`zAKI8Roa$;`qa{49z(me}N)ClS{-~ zkxvjVg2&F>n+Yg+!hQ&toQdY%We`c&aPU8D_*9a)!7tU1revzy#~%{ZU3HIYu42>s zn`RhJnqb~x)$HKV|LAEUAEkk`s8Hi|Qw^0gHmPZ?5x!ve}G`(D}gbpba|eTi14AEH7W|izz;wN-RC8{u;+tX&^LJ8j1`I zMF=|jr?dq3nI}gG(C!O8P>Ge|`Io6rVNfFSlQ~e8jc@j%?fY#*+OR6xx%(^^ebaY| z6m?KLctQt{4)Hm^lQE^VTly0`6vD~&zIj}$y}&(?2sq#sBW#V_U($Vlc$)^I)N&IK zO#l104|{$4zuR-=bOC@%WwpRCe_`ae08wtBKN@riK|60#llOz)NWikLmjmJ4wc;kMaGbpvEk;GA6l%g_Ya-#m4yK=#jo*MIhRS=(nmy8Fzk|mOGSbW{n2EB{3m~GM# zrUVqkXnRzPiAx(%l70HV)&#e?LM#{y|7ZlxN?ui-{*6BV0j4@_nzEfp0M}aUJo(1- zNn}Q|`FXi(eb+Wb<}EkL`^N5?8Eoa2jVD5titoSMOo4N7YjD4zP6z66_{LN_YnldZ zR1zTP{-m_#g)QrYBu4f;YYI}nI>G#miXkT3X@7aC6w}T+f^rW&YvBfp&8oay zNXTI+ZHwZ#0AUHe9Y2?w;*kEtW$KT1fd_wxOdoX1r&_VK0a{d=!oO)z7YbZ;2>VZt z3kuRNj2$841yP4JadXW1UxFa4R_l3Q2Num8H-G4NLDIYX45`7a$sSKT*UulXrPA-) zC+shG8#_qUF#3XJnA~b^Vu(s+E_Bc{SZ=NYxg@(c%$z^}SN4OY$}Dso@^o+FdOUq- z%FoQ~K5@BICN|%YoY3-aHAVkVR#T)1TO_aj=+vfNko-LV^nY(r1_BCbQKmO-wQ^|{ zH~zsfACq(MXstQIS>o!Pgzh?v32IAN!ubqt8X)|$1hZ;tZ`jPVb4bI*iP4$lzqbBb zJbqxUxj6gP5!0L-Ndr=&s~sVwMZ;s)(2a#cd)$504*{ZqCv@Ww}LFvU{WEyS_L%Jr5e>d@3`u8SYT^+fIoIm4Xg6AV$ zN7Ow{G{df%hW~0CR`Q>1!;;~$QnFQt;qD?tZ?nMcX}E(uzv3M`p>&5JJ|_-^eIM7D zkhvjb|7bRn%zy|71tFVtW7j>mpGnC;*P9Le98ab1%WQt*Et#fy|6CbgeTAld>hTg2{rgGLT9+Mc>5b6VCG>k6(p$o*csoGB?r9r z|03@#gX-GWcTe1c1Pc}*xVyW%yF&=>?y!L1?(Xgog1ftWaCdiyUfKKX^FL?r+qZ6Y z^_Q;xvZ|KN8upGk$9R6@eK^(0Qqp}kiQ(`le@1oT)%)FFJvU*4C+zKG-X&RJWt=53 z@LD;FcGU6;E={1i=^xghqHu}Y9bmTlwp41`zJAh?lIjL~E<}_YOJ%ovxFFVm<0O20 z$)p*uM2feY@DMxV?B6(0f7TJN6wO;>3Tt?ECf&(2{*2qxHJPIrDdh)RM*#arUN+w+ zzAu!YBY@|WfI6FG&=S}%DsD#fazex{t=xVe8-3~e6ylPmgSZ6CW(AF-j@hN! z%vtI>%Oo8a3bsq0(pqf=pw@W0z0d2?^zB(ACl~JX*n@DJ9_r* z$9e8o902h-G z^-!d}d;I16yv4klkcsCMg`Ac1wQfDjs+(6~j_a@EW0FDl)tN~s5^m1gA_W6*(&k(? z#~Gp%OqtNe#&Co$#NQ*!FvbSp+8Nd@?PYiEL_mz7G}%X!N-0;3DR*A`ALdb<`-8Oa zlcG&>@J?4W20LbEwW^a_rHV_zxun@zD#InFjo>fqcc0ZFzOix~7F)gn2A~V58Wx8- zP!ebrhh1{&-oG2}>WagB7VIQef8ufszDI+Hrl@?aWAv@RZ1mFt z#AnPewhJiohB>F_JS^%27||FwznF91wO*-MvP#ktFD~$5RzM-*Ehg2{@Ws#4i$aSF zubMmBFLoBSTb3?yqsgehEiF~`+2kvR>__2ozEzz^*5%McCcJds{Lpu8ZfHE@Urja} znOMW(0HpwW?`%l?S=|;*g`cT_PE=%wV;r#I_3TA;iQH7#*$SYQh7G1r`D+xv8?C#& z=9$Jz;p<9ytp`{xRPA8tCG*r98M!zFgtagKw#e+SNzb8K%|*ZtUkmp94q7Z*sY|O_ zo8w}(h|pwb@3WXO$=Z4XeraB_+rn`+u@fjL^zY$J44RA1$yDEM`)b*w!S}5OTH?1M zG?%9ExCYyTu~_7<!z|8;(8KpNwvUrfo>p>9_d?(0UgHW(5l(-gI-C;-+&0 z6pt`p$SL*R^fIV>h#8yy_qSdUjb5(P>+NM?@#4lASX%mxfclkk{OU!&{GMiwG?man zj^n+h-m%~PGoSUV9DjlIj7w3W+x}0RO%Tw(jGSL%KpK$bo|awea)r?leqZ~4zWeh{ z=Wpj{XejnST#f$gdw&peNqMBdO8h_nQ~hls4F*>9UqAliO@mteG@B=hLfMD71#&wh z^<=4%xp$dy9nAAP?TslAu>bnUnRMqVI+&55_>W2XvRA0{L7?HacAz{HC6_$Za%%In z5~v&WfBwVP1I)UT+$g*2ZHfLmpeJExL?qAPK-^5pY_*Wh)Flbh>S4XIN-3$F-|$DEH!7o>z1`Ax7wlC;I77_86~LGFxBG#HeO7?z#;wHD zwHV!(eEw|%e}-79qEctaoIghHe|LanCr!~BPu(W?ke?9YE0|qL7?Cmo{&^||o-j@y z)FXLEYpaf?(Ae7iBb~L4%Yr26Bh-I(2ng7jY$pvmNk?#wE^>}Cv!LiKDO^l{;0I3$ zk;!SmRKc>%4g>emLi?te85@J%BD z=+zYY|90fawA=JPRkI!s)P*dcL7QB%{3E(7@9_l|ax) zioyS0ZxB!_u-Z9}^yYLQxVAg%=Gment<|fsclt)|r}kj^lz_F&xG%Q{;aZb`rN-L? zKJyW)!UF!Py>=2gYg?PK&CW^q1*;B8H&rL(ri4bC#MIa9`%7p=ZNlpYbb zqOI~*ulRt4RGSvXHCs77_ZR!C6<;eUy-yImr}3Q?^K(0+Dzm6e{Sf}*6xEe&GFobP zb9E;Rd~+N!m|!b-U>9lj9_&Jt)*`_9qu>TXPeN5X_x#bg%Cb4E@o{+mmNvRZMn1#VL(N~K-2@tx?C{rUBQUX()&s-jaQ1BL4r3m zA%qbU2f*)5{d1KJH!zS4g6tvhE3-(z(bb2Tlr|(wGKkz*NF~l=DAoA0;O0$Yv4&my z>DeG56d|tMDlq~U)p)?=(|pnuxiKv&bt?9b%GK_O)pB_jO@gFtQ5{V~!S@x&5O(T& zt-LJfw1^rlwpM&Nuf5 z_CF!{$ecRu(10PoB7!}=yDb|y{Q^+qP_X~g2BJ)X?oGL@XdZhEjuX@+2_fb^QD&KSP~sR+DSF|Jv=x=y|_B@$(pwwLYL^E0K5Kss+*T@~h9`DB9#9!3MK zr{Nyzo^eW-7k@V2N84-U0(jWk@2hv^Hx839KWgPn%{JnKU-+X z^@V~(ogNZwSDtz6FugiCx!}t-Wy0E<_|l361niEcJr&8sgL+$yTLHB%@rXVp40=WQim0zAsTBSm}NQAZ^L9b!hb zy4cuz_xo5tBE`;bKyfyH_`%Zdq!|2Z;r>atRs4XF6&QZXqrY80R3*4CK)TatLWysD z8zGlwt$BcqC=hNcmAD_o-f1H|dDUaOZ5O3YuEnr?BC|rO5_kg%z#?egH0HvK`Gy4A zBpmh~Ynd}yW1&!Pd_}xsSQ2lK4*4M&L9LE&zwPmwi38{+K~Y+ub#QXHjVAEGDJYs> z!K+wpKmValIHU%XM6+Rg4rI5}2UBjiH$G!=vYduST`9dzW3}#W$c@|5v*9b%&#C=$ zK5Psh_=hObZ#qrZr#B|!81{L+`n)44xO99OYOnp9`W0n+5vSL!4si|V=mh{7p1P&y+4fvp&OS!0LFvFJ={aDWC|bH`-u6S?ir*iMDcan)UoF1Me@3Ps-@ zf6@uvqt}dQs~OoK^RkiIx%!A7h#Vd*wFAy$w#z;=7bP_1N+&EX`&Uhbd)N-v%?XVBXPW>DCt3& z0A2DEa`_V<$|uwVEI~>hnPVCSXCmQo!@ab`8rNiEw?Uz+j>i!sq6FW1jXUr1=ev2?pb5lE16rC7$GukRT$Df2IKnxv-I^Ok|wv1@ah)B3XHx&p{I8m6@L!4f1k4#o`i}KUNxT_J#iC+$W!vF%;bHI={a#4Vusvr^I2wz@_PmDSw?(d0vuQ6QRUomm zOK^V=8+L(2+Q1NslI1F{-C)ffBTgL)6N3>OEUhc(*D$~AUg@sf_=TVH?iTMd`Q%g~ zlbP*2BxMA(#vYGmxbOQ#Op4sv<1Q?f>$^rYX2^P^`+???Ikx68g6wchrBp(uWZ|7(uzhQ%4_&9(LzRz3*;ZfJg@r1@D$l@rjQ2O@gly3*q09w zx#H!1tuh;NiJU<*{B+v6@OZHqm*}GYkp^|B6@^KSY)wd^c5*V;iuDn*%{1hfR-ky$ z%1LSM+UPsIr+G0&Dq|hCxy2G)u#iyhj-AX>g+n{9U_$i7+hRo{2l~_AU$b`=qj?AryeODLkLlEx}`J+P|>;MR} z_v_4Hxu)ybpn;m6;jiocIDwYW>z96B_ymHqrf$##O4}dE`82MZpT)@e({miS6@;(eBzVP_&ln}Vm>chXAPl? z^&4HWOZq!ROxT3hv|h(F1(@LMQq!m5Xu zrijlL0GCO{dz#=KZ6yEeB5VNV?$djcjbM~?raZUX)rY=aby<{U1c!4?2;*tf<>8hP zY3R2RUbM%%HsU~sIk&SQ@GiJ=O^=ELMHtdU$LKLA;MD>9^2<;hDx$39qP$~iPu`+1LN2~WX)i7WMq-I< zr}Q?XKFN^wb=L4D>N#I?m8?&KZ7vwfc5W@BLkWet1PZo!Ek2Djm1I_eE*H&iUM^+p znhbt2N=gg;+VmNFf>?-sVp-rvk|E{Cv6O-8R?3(1vr3dqvT03txs;5;J~BMss~`3W zxeiXJbM7-vLQZxHA~x~FC_Q~~e1~!>>C>i_2UvZ5D~|1FI}&3O5qsK;C8#DN7QYOC zSRFqe$t=g-pA=PK{PUVgNs^Q`!4Q;ch;8ZRpq=>Ek zj%4_)n8aPDiHf9W;E4%vn8sj`v*4z(Awi;xC*rm}a@A-wQ_paEQTttGbT_|swnF4n z4Tct1C6lNUU|LYfAqD!GOH~5C|6AQc-V8WDc>Ng))&t8J0^X* zBIg^2ciE+9`Rctgoh+6ew9hARm%CU|7T07Btv(?VC#*XeYK0f6wO=Ve=UAAoyN?t@ zc>uMjoB)Hps$zjg>fY172sz`n)n45OX>R=q^<{zfUtd~J+~eOzUr)x6Q8D?HGqpsIec_d(o!|4%l)EXCE+l;+24r{jV8#3M8y#BBiKmEEliB+ zsiN$m6bElW@tT>e%JwRoMrB!1r}oY;xVep-Q$J<7#xsq|?&_m<3&mB9rV7r}$L1{* zTJB5EpmHgA?Jo@smL1)f{cNa1yGXR|hC<-w}ubR-?`^WJ7T^{}q?;#uwM=9K8==1Hl2 z!x*TiZ)XLnt(3@N2?D-2-e;6UjNr$Rzn1v^Vp#B33II2^mfFD7wu)=Jp!4}l&2L{K zVvF|%B%KjpE?H)}&&G8Z(x@n~GgFsXR-xpEe+o}NdcT(6)@?r0!J!KEO#v9TxA!`j z@y5LG1h|S0J&Z&4ZFf0kzu`%*ei_>IZRRDPX~us8B#ym7(@mT{ zfDj-MWluAL1+SlSlm`Kj^>yu=#C{AJ9#Oh8;T51jJjNj^KNl0c0bJ9;>)2RsiaYnK z4wEkcewWr)`D{yOCqsGK*iuqErXf zoE1UbkCu8bBG>S@o@gkX1nv=FDoJ$7TS9w5b9jp`nH5yyS3UQO`rAzjKfi927YtXsU#y*XJ`Kajv0m9l#FzH&oUxu!SJ+!@wcYl*W5?T!oKB`v7vF%a>JFpTB3 zm4ERtdRltlo+3RkcEA-&tS?NWXI)B0w901inPF#Hl*1~^gCLn|XEQFH zDdll5BY#)iSoM>bzH7nmmc7X?7!FJ$(2tx_5`Zk!MkrWfI{mRBL@>X46O6G1+RTj4 zPM8^8YK8#)p|*@@AMTe9kEB-2&?wk+V@O))(a%dqr2La~ivfQt;iJk-NnUqWq7Ka* zP1i%VYdkD^QdZO8BIaS2y>nh{huVwFynxp2Y7*h~$JNBvYqTt7*EaGMDy%AT+8uH- zody?KfMu+z=KXmfax9$iu zhWW@$TnQG7J)aOR0`I|m3}TFW(Nza3&!c>EI6_y$3s%(kI3y7GUpCVD?~4Pkf4Im5 zM_el&A=SCgdpM?>X0kIp9<*aCkZ2?~Q;-yxg6Uqz`HNX=XJ`aro9VM$Ba>Emx?bcl z#qdY45Z##yE4Y69!T`t2q|_I$=C;Xt{Sy+2Z7t~vFeT#ICms~0VrT_g<(fR`{y5=d zQp&}2*Rzt?%7q%$1OU}J&Aw2i#FBhnr8Ms8!LdGGF&eeGFl9XQjX<$ly6)!xSvh3C z_PVa$-t5QWw5fC7T==7)g7#xXl7%0IQU>UGyTw@xV;wBuvew`-nleKQKJ&E7upqnZYUDrBA-Lf)|%;MUyVbrn0tq zT%P=D&AL4a7(Jn3waUDVjzE4R6G-QuC-Hiem?D^-pR#kr)6fWvN@H~O5wh2-Y0rPeZiN)%MK-1RdU1pJ%UbCjxuf}iy}u!3>>ESQ{mfTCgV=CdZdUh}NYZJ>B#sj4k)<(tR%Kt;3yaJhnnkt@RyM4IH;T(VuuE(Gt`s?c;~g}{mf32x z$0ZE9x{ahB&_S2sp9)#6TunPD*ukRVm-tsROVCkOTPhfCJ7H#AKZJLWC-2db%jHNNJ))V*GvtEo1OYu0Eh9y zyozkM*UruiDe>8n>`KeB+YD_G{9~~japxX= zOgyvO%9Tq|(lAC*@{5rGg$|t(o<`J({6k0#7&r*)Kp(A0^1h;8rThk`W*F~fVGhKA zuP!pxPs3v%we4bZ^9eWfnuSs(jJMk1ls`1DuNq8>>d)qC6H~}Jc<6z2&1U)|BjN_6F78H(oNc&9tpzu^Gx54Pco?ul z9u(2gaB*>vv5|S0seef}Sqo@_2i&JbGw65s028)pux54It*^CmjlskIxOSTvrUTFd z)2V$$-Jt0bBaQrwg>#B8)h-p#u+T}wJ~Ec|;`(TUqWt|D$A7(Ck0;gTB`@!EKmso> z!H|p-@JMLK&XnI$g#qb`fW>0Yt`O`0(4vm6*Wqo*y5FK+2~3y;_%>O$sB)(rr%EvF zZfr4h_^A}{all_DmH803r$-K7r^alpcVlRGkCevb#+qZLr-n-qUfek-&Yd>VaJsV= zw{8yq04a9=1%WRmqaYyq+Vx?`fZ|$pwi2Cj#zjV%-fD4mTEnXG(p^Kg(5^~!r1pbf zTw!EpGPCOqNK98*fMaLPd&w+re`VN0jnJ91h{msk z+yO`vH@y&-F8H2_c7Zsx#Kp(aQ^2Dd?)qFZRmgYF;{+LS3IFjeeH5qp`mZ84VfZ+9 z%Y>o;1_nD}33Fae+m|eKQUrw;8l&M}|IbN(u2*QxIG^P*g-}gyT1;F`jE0M3=)b&B zn8m`I&mDQxD$FYlxffbJk1`1HUa9L2uv=tV6#??9sCJqd=5f4$-W?YsTp^3qqlvY+ zL7Q=^Hp6X@IwnuV4{QQeGn`q6_C)v>pkQqzozjrD_Hh;T+I(8p^HIM}1Y-49%Vp45 zEdG?!^n=rGBGvikjb9sC?W8V{!=nlwzAm|1#`QOdfrn2#Cv|xkPe_I2b8}VZP8&dy z*4=VqzvOhJT>3!(04Si{`iMZOTmxF4afdqdEx{s;`S%8%OQxI0SL4DjU=AmZ?o*!S z8#oR~k;Cw{w#ojboIiRD9cWM|m|Y=z^c-G3DE;kb}C-`Y4!|3mT zvVQ9yvc7b|Hidu-eId1HN$Ipzp`aL10&Of7M_5o5w=0jznTt&ynK+dfpTanI@=QkF zT1$MR4(2C`6stLv_G*q1jL7}57opMnD%!0HG}{EHb_x1T?Z=ag=#(VO1`fP^s}E66 z(u{lMD;c|CE;_p+QzX9H)1)_|C}i-b!+F|K?aSOG8j*Y?4Bq{GrMVY3&OxV4dMVbb zct{V;ce$>L)_b) zvFGfjpL7}7t5yAkl~o)sh{ulw@vNZVI_7v?#p+J_=iVRIXB@{95C`xEBd3E=ZT;jF zI%LkDA%g2RpBC-^1TCPCjjeJ|)LS(8RFStbYlc7GWB*pLp2wBrVMzYao;M-TOo#AU z-6mvia41S2D`Q_x9lb|qTTd)=9u@P<9?ApW?)${IxwRta-Zc{{4llQ|`QQ*4L4Dha zCrilP7Ea1q*}!d5kbWzqiT@J7>T(TPB0`NoSpQU^R+vF|Z6+S9m!h~d zunsmKug@dSMvI}}$equwvx!$7n|dN#kU*VXx?0G@qEUg`mi2d_`o#u{U=0ceRIb>G zvvmXC))jXJetHij{cC>PtdsTIJw#)Z^`};`EUx? z^kM!j;DdnvmfC#6U~Hc+@`1^!5;<d}JPqj%DMS|C|Xdtv%#k^U`_x{z+%!TB;0 z5UZOO&jI{gE_L!h(+U-1CE$`1=|MyNFZ5?y2N4Qzgu8{n6uQrq#Clv*aIlTU|=p_wWJtzM!aiqzF z-pHI~mKBb6Jo42LhY0yOdUTd7^A#(==bB> z>^x~J>3z~y5?F!^*-6sGVDYZMV-WnEGO*FeSPe|))Z!ND$*G!GUVaa;L1&IyrIadh zMFK`JI>O#5+)eCOU0e~?(2$Jq5dSTs@?(HDD92MbP5iN0^ZJ;F@li4RlA#-OC_zbu zt9jA~{O@|K3&}>J(s}06h3`%mK@7mRakdY+G&;&<3=Jta;+vmmyq@kFZAFf;&G8FH zLqm&2fsg0kn%9MFBQZ2quUEb*Se_D_fXEy*K-{1QvOS;B^got#XlNuZ-ke|F|7#ps zOZ}Git6fvKUrY zqQiFN5G@v=b840spmcv9$t%$ykEZee+mS=M&i3t9J?(aTsn_b>ujT>!AF<^w_Ad_T z$*)Fu@gGCxcN}~MMv~oEAJo71%@=+cpInIBf5I?dMSw9$^~DzMZ`}{P7*LJ>cWC{) zKK~EY_@$%Ivd_u<_Is2^Ay@`rg8A|43u}cHRL@76cgI;rPwX1O?R+Tm#yJi9#Ns@h zMyRz}tkzz;QA_q|KPe~6nrw#Rpp>O}lM{5(Y?ww*FO+i@)?|{Nl)cKd+8^-)mtzR? zeWNL_vCO68qvV(E`y8)YtojAZBpK;E3g{J$3n6G!3U4{M!aLz%en33TBGdy89XTD8 ztU?Yu#|7h5&<*myaelYghAPC0u7c$HMS@`;@l&QvVku%JR-#l%4Vna*gZH(;d_7~_ zb!xUC{<$6-rE32DPedx_^!awqOW+Pc`iH3UF^rCM?9{^qlJkN5x@#E?EbUKkN4;{9oQVU|cg_jx3stx&k53J(dg7O`JwNMg$|l#_ z_1HX%h0l8e8E`5sv`V9tD^h?t2Z3qX)Xtt53`H9`0ug$aa#Ot$La=Onog1!AU-Du1(_~JR*ARukP z`P|rh|8u{?n}Q@{a6XsKqIy?<^3pZtD;rb8rQiqiX8VfDx#f*M_?lo(V*8NJft9J* zm2x3m7}%H}ZI{jbp9i^vz>vofFBlj@O$jnHEk#jMFicYIp|H^j5XHo!xN}8CO};4G zU~O(EsZKa|fx_)#*HTcC3}K{1A4;0dW!gmQFa|{wl93Aqsh=rk)9BIRd?uTFQk2q9!Z8PRnGAs#LR38kfFoS_P> zV`wDID!OJnx-NUpv^6!&)jQN~1lRNZBH$shGIi-??+Xjil(ab@%$6@Oe0D-y0Bvox z!tpEcg+J!`*94z5DoHHUA04ct+IH$fYY#kl|GLpH0t#&M;O9)|TE$WthC1Z-bvKw^WOta}!QHoQ~DHE`&e&!(vuT$x7C8y9} zk}Pat@a)%W74o<_Ltm~qznv64tGOY?N5SdFHr;Kk1${CTyjQ2$`=yg^0 zQ@{<`2)9EkMBg9N&??&NYGR^*o`-ZS2(iZ8I>%v!qL6Q_`WWSi8EwlSUj;@{mggxW zkV%t-ePIC}4_r7pMW>e0!6GCqHSF;kVF_p^YrU?Q`xA#`t>6^Rl#uJ|z_H}v>!vEz z1>T>J%7`Dn+s=^F|wpJ#~msbKXP-eHu!${J>A;gBR4ucanTxf zxE|wWg=10*Tf}05i?@QC^q#E6ozV=;kS>uJc->j**c&}<(Tuf8Fe;vOwN~egbg{KI zDU;hu>m~w{Q|@vHDHO>s`B*6TZw*TLkFV^%lR`N`;6Aw1f%I-JuxdTvfU;jAQCN8^r4ZLGNC86ydqt ziL8m)otv+zzK_(K7k1Q0MD!Ehp(#|59a`)xoYJ&+MRq3R#Y}Z+;RyY0W&5VAPJ0<9 z%6EPgf3YvyY+rdK%c&MBGTz{6O(MJg!M@<`$%)Fq|C_oomx{t%-zuhNXW(>7b+UTe z8I71NE#FC#9YMbxD9Kg@{mfIODsY;~``8JSvb#tq&`S=O{$^9_aEx%;M^jLs?brW+ z(=ATsyw0T~sfd*F$z^y7aJ4-12{KrFTr9w7aX%A}1Q##MJ?f-&DiK+=7{ zxDJ%&1s&Z_Zz*~*9u`Ylwg<+&Tx2_rj#pp&i7nM$*?ZL$mw*CJIRg117+w*jpZqR? zln@I5b@8Com(~+H-4Q4d~D(KPaqB{nX_$rvKNVy_!d1- z(C2-%8z{cVo6^y#MtBJeAG>W9xGKuosYe(O^&A+BZ=2cAMID?NVyGLEQih5z2aip` zR)@I^FIW*0JGsUv#eKPm5385P1K?JO<*b}lo;&7V>fOBWe<666;dX+37e-3lW9Hpyvdo3ffUpy!lN{)-pu8&YQ_lLPSt<)M;ae zh(S@B8oZ<6W5sj3AwdX1q^^>ZJvli?$H124(vzFG8OoRl)OIsWb@~Sn z!Pfz_1!9^8`@C$a6%(G*_9Cx`qp2g*1T;rer0()yW%c|aXSiE_$JRugH+6e3DS-z} zhK}rRr<BiCfH%yRm8Gxs|e2yc>XzZl>0$8Vw25jeoE04=E`jS!WBTb32f2C@58Aal=*) zoD{i98j1^>`P4d7_P7-pXCY8PCOJGpnKluPSQu1UDdTXp^Cv)%1)!yI@^o03Yc=7s zYV+ZOf8saV5mjkmyzY%>FBiw00 zDmvj+T}$pHDmvE_=dI=mMJ3fd$uL!xyhf7q9lbE{H3`tsaz}5cq>QTb;9U8{JYMBf z99&pm~nC-tUuyQ#+TEf z?{W9Cs_13PaV(H?j*v|?YVa8(D($d5jBbLLJ41A6Ne0%$4+w3-+J*kPAy@f4P8iG1 zG#R$^EG$j=#S+>kxlog?6rWWAIteaeo%Pd<(_LFTYx%pePX;jqD(|6DrAK94+^I6I%|+Mr zd&`{H*~PiA3l0?6torId=yb`H?6*)vzX%C^1Melyt8hZ+5shaT2iO(GL?TzcpS3*T zBerq_@}=?lSSwTv#;Nh9rV0o`C*`?fO>-uf}5?jXpD ziZaScRu4@X;9%?|P=b{Lk+0_Kx?A?O=@CX1KK$n6k zh^U=|%36!RgN|l{s&_MleTa~0V{V(gLQY9}Ik%F6oqz*mhv9~3`rDwZG3LH|-)5;j z_t>c+ZuhNjU_Wmx?Pg$$Q$qpVx;ytnpI^IbSqWphn5*KiawP9#oPeSMT@JUKEf(1w z&u-iA_Z3QICc1mUt!)>w-c|=Yn>7`}mmfak;Gc)-!Rc6;8Z8C1qi^jQV8At-5FZG} z<-XU_KL)NZ-X)eHL_|a+4Nc9?$}da~5J}%TC&WM9tef&XeFTD*gwI+|S&{pzwZWlO+SGDv`Tn2ydS zX3X*eU!NR5W{}L8hn+3uGx)G-ffe+7{S)8+Lv)TKi zfVb30x9t-)%yU{9U4dnKPQR(m7e|~1N4L4xi~Aa4uW!7rZq@_PFlNc91xERP&{HfnOjP-5m-Ho2X%5{JZNl@l?ZVP%_#}Aa7llLKq}EXl z)jkPi*kx(p%(}O8q=Gn?nC|D1nS^p3F-<{wrQq9vdo_FXRV{r}Hiq#4ydJ2}-dn^8 zlFHWxBrUYz4=E|Q%fTOTFm^M9caLhVE8aeaXq!Md9UTqqRh*|%)={5}nX58~8Y#(> zLTnvcKsg?c?m9#2db(aZUaO!T;hyrD69U|-pQ&-qR8MU_$Q`Ikr+t6%B=o#U?bo#C zG1#uZF)SblgvW{o990HMa%gN$86YP75OTd({`sV!0b5OAM{sGlw9)bYqV$~2H*sbi z_TaCNdS;;2)}g+hjQ8EsC>JCuGSK+8-d<@~2^B#xwLij?i&Jm9ZLcy)}Z=LpXcSYrKR9H_PZz(f(oD7Z^RO^O!e@4W& zw~-{G0jN|$p+X)W=Q)#i_QrA5zGD{jM$kW=)Avz;M9mQ0w_-J6@_?e*jTl6QU}jcr z9~af`ZERN=F^D%~IGLbm3T0OGP@rv)!z5d3lhKDQ!%H&@`|scUTKzt923=j=iv(-` zMLO`WW@!_=tr0TSoE6_ri*P^br%24!%=joopJtpiQCbJ9er`;{4VpBTB)p7C*gkFR3~M=Vivzld+n-Z=tv|G z=J;|o#yBr$6zT(vNMQ%FwTJsXdWEOy1tTQqhzn8t8P{o3ZKIRm0jGvb5kIyT?);L4 z6)w4UxFdUTf2K5`ePmlDoG(#z1O$qi`jQRhGsB^U zyI0&SaSBMcdVO|hg5G!PndhwXyjcmKE=}*2BQ`MP1r05^6jauouM#_dNPzJN0|Rbz ztZ;+Ml5Ok}HO-u@{GS{=x?`17$(Rv=Z{{>!61O?u#{nEac|{n4qNDSWI@7NeOeoul z$1S^E|7IV0vErF&0v9!+F4eNd`U4o3rgpFUaA91MLtO52ogkN9cCTZ=xf-~Pt?lMI zy3HM!BBiMC@m>z3i93&Zf7lyxU@M?yUJOoL3}c|G@ID=>Uz;(aQ<5BS=6^g{P<(l% z7-6%%2npK}AWYRiU~^^0%IOgeVLf%z|7y4|`|VRl+M`V;6=OQqv;ODyTU(ViuD%u; zvnF+86T=K0HQEfpI8Kpv4u_Kg13r8{_Z#+Vy4snC7vI!<{esk_D1bA#mpmKM~< zw>BD{zKSKyR{mk+?(qBSNM&jZ66~VSqonF0@`*R7nZ4dF>Puk zMb*iX{B@*s$h`MM?D@y)`5x{sK8wD?a!t&PbaIn~^O^G|9HTlp7QIscLZK!*qy0 zUQ+wAc8h;a1sifV3h8&)yrSij_vIU9E5viSz0}rqke+L`zi~CHvl^6bGYDe=M?<;VH zuC-ij1?k)%MhecoA_Sy_DvCh{47w!^HeFuwhux|MB)liNvBxf?@vpk>uJ!5V>)#h> z+J6qBFu2v%(`GKVCX7CR3lCj1Ph(%4laK6n-A)2+XshV?$)Xa^m~l5#m(HiR1cr1_F*Cu+=-lTsLvd1_MGk$7pza(}sr8Yr;8krhKau|FViT1$tHc7{*X zr1_6W^9M_-q`XlBPMy~`2ql#}`mw_-ULzcAd$5(`;li|@k>o2>&QwDC| z7*#4t*qyg}=Z4)4KQr1jzqh%9qYf)uu@8IVoCFf%x^5sCd@>4`-$=5~1GFf5*zRv< z%pl;dfF=F0ta(lU&E#EDT%x5|xByX&XNY(8=!RaFz9XtOIw~xkd~}EE5(6i{zoGoRF$i#=Z+AZEqqi~ zGevk^HeVSJk?p$^vJtot94bw{7xvBlA|Rjo+}HcnXv9A?>eJ`-^R%-^+i-?Tml;j! z5kmN8))pJQ_q&tX`Cz0D8V_<=Jigyph_5h|<*1CwapZPGsc&NoDLq3?xV*)FaXJZv z25dJr-s3mFT$zLQ1S}KdO^W!!&{yo^68Z6+!_fYiJlm$nIA|AxK!H4x9ONmKd@;2s zfSo5&sxO{gd_=hF77LO2OIxZgCebww4h39jWF$$$Q1|!a>b?>x8gYwGT&h-F5BXa7 zpebMuQ2&NI5}Q(*nOf0&dk5_=xY8+<2=9Bqoyo}6b=>w$o5|TF4x&yr9-L+fD#cQ=rmV|6c_kzlc7ah0NIj6e2M>Z*v zxlQtUOt}QX?e6uNW&^In25wN~`4|WnSNG?canv_gQ(J6W;ff<<@M-w__3(T>JCD86 zG7AV}PGn{4L+Z|cSJbhPP>zUVObYJeg3CX+TTK|*lNL3ASwLBJg)5+9SedzbjR`q0 zB`z$%Uta6*-|P3toC^huT#}4Bq_rk*$8GEt+%x9KqgB+nH-H;Ez_}6@BAG&17NQP4 z@YrPJPnnjnJgshMmhXRvj&gsae@SmL&N+m*K|rL#5zR6Vk%!~V^xF6>=c44HVqK(l z_s;SPR03zuHzftk8&+Q{%K41C)h-X}KNBsqtJWbWNwr?!&0Q-JQcOga72l})O6UL_ zsyW9n)=hN?kb2|=Rd9dlQ??GhT%a!$Y--22w1YKK(x~BdI`OF zo?jOEa^t@x?ER&AYkOJ5Z|ZFK>9;0ej)B#?&N~CT2J!Kgs9cC(y-WYj01)B_%mAR^ zT)tY4Uj}10pd*KBqn zy8gJJRY&p>E0MLai5rB535Jxu_BJ@Zf@P*MC&;OX0c15a9mmj+eU z0PVC!k_VXn0Yy*DO99c4(H-^wv(y1WkbiGapMSv(X_pE?QHY+={~CvU>A%OJ-uP4X ztD{94LeI^no$Gx6I4#>HQUB83lLj!M2g32X~jjePD3d z$;p5IbCP>+)$Xm`s@<*nYU-!^5#-+i=I4M@ zN3tve@I89a^6dU25AHJ-(8zo19^}dj8yLy!$dtMe597{+_suY z^M|ICa-1`tV{9jIpfw58iER%jCS}}IIqfj{j>Q2~HgE!n7SbA8oc0}#r&c@L3}Tz4 z`Ka{iFK#hgYz7XL-G%G}$Pq2bN3#T{`d1oO!g#Cho03}x=lxc9ecc!)k+Zi&<2|D5 zB~MnO^y7f!LFWX;uT6YE*2e#^i~jzSONfH+hp3l)KQ0~&%Q!icR4bWn;bg8&mMW{w zf`zn^p{NzTZGeAHo$siR- zI=F|nT;I4Tl1P$=8_S&Vp>HP=nRY(cF6c%ej>kLB-T2+eW)4Jx->mhkk$U<0r*HKg za4X-{Yqajw!|87dVet-s=@;M}5G4|u-2tODB%h%mrix^wIUh;cztjd|EPtvESS~vf zifbrO`*B1oXsf3!7&BZ5uww^pl0JzH!Z()c`%KNjrvD^ft;*gbDPl~5ru)8uZM99NM<4APw@C1X63QjSa6U<0ad`jk-}&cH!+l zBuqaA3gq>C{-Ve6>>V?l0(*H3zsS<6Z>6vJk1)Za`HRDYDou@29l;wyX=(eDCyzv6 z*zEe|Goh6Euf^8o3Y9?-jT~aO*D4vVAbv5Nj{+YsZhS6#ppSH+Z~QMBTbEbAHad>= znpsZH1O|B8xEUmL7SPE#x5N#Sj^V0*xzQvvFiU2$xPpccMa}v4kfAfe82GS!E{4a3 zB%K=aj^@#&pqvIEXVXt#$K_xh0)3lA&M|D06J$h>3`hHXnsu2XOdx%sl5*!`VPP9= z8P2-sR^~|2QPL<*G?Mc3?R88Ws*;tZ3h60zk=n7bLj7c-Mdod4dxIc{+iUsonV|DA z5b=6t%Jk9kC}`Y)m~^7vdSQxa)hM{|UpfXaT(T7>?Ijg98#Fk3C+fEOX)au(76i>| z4EAmFQf9;Zsv5#pqT{qIhKee{DCx=3d_u3NPqJKG?F?GZx{tBOv%v~ovxJ8bq z289O~LpLz3MnF|X@X@VgCNI9l7wPtOByk!RFE@_)tT|aC`N{&X(@FF}V2=eWZHFy& z7Yh~>5(RglU=OQ4MtV!8QLVhd#I@H7Q zBm+r!-a`P)K;8NSzQxOhz6V{Gj_f?R+=lQ`rEiXd1mNiH3FIwkxUK$}gD#S`3|k)3>e3 zbA2zMA$6pvw^=U{1g@}h6mgeHT_1u&rgf$^FIDssVzZ}K7M-gBcm+tA z(%tn9A9;)^%3XWs30;^Y@j70Q&{?+1WEGj6^Rcpd<@XybF-4vuJ?=SD9k8b&Sz^O_ z^U+(vlU*YP)4~EuLqZ144eQ!1*BLVF}jxyeEQL93*`V@TPJQRPOrn{HklPp=b6WxHap9NCTI?hdSL zk%(Me-$SV082gc@f=)AFPDuTIzo@c~K~m=DA1yfVR01=zKU!z9TLkxul2=8;eknq% z63|Hvd~MFlJ9d_U(U9y>g&$(>UkWc#VJ7F;B_A9-iV!dt^b>(W+b$+9TtWu38_&B} zYX_=wpv9!e-fr&D`A(SPwoN+T3(22Zyri~33E2M$QhADq`SN=~q$%t%-2LSk?!2q7 zIV<<0bToACnF#zmyxhSzWvOw;%F1}^0w;b9k#%S`h}cCMy7Y`3v44zZIoZHd=R_~$ zN0DS!WDb?_7IJpp45aM8qX`oR26Kya2NV8EBLOR4d0vspllTU@W@ zLQkuaM&^R{qe5E3dPHBQV_U~QnK665cv-$%)(RAEN0JD6b%etzV z{Cl85pBaz)cV9Oj#zqqHqaCFuU1=lAb)E84E@B8W zKYXb&y31WWC>55U!yT6-XihB>34<|_@0hu-_2r%hqAU;b4aUnsomvx>=4;)L>23-_ zR9+0=dE@mY)opo^l={bcxUblP(CJ!ed!ylFdbYFGF0DVt#@3>;Xq{3cG4Sc4J-YHC zs{+z*#?^&rt?z}!uP-aJ(DZ7RzF3iKRr3L?J6(S+bku;6DU9+ej51n&N_>mRj*U zg$Z2MQM@(vI2b~(0hv9^gx^Yr6w>`wG5z-Y1p^{E+N$jOBUj%PUl}aUG5j{3Qon`3 zYday^^ztyt8&bL?fwZBaZ;QXz+m=0Gy5jf_^FmnA>)8GZw5UfTG`RlA7U4?kD!or6 zUAnY9`1HJT2&GX(wv0ankNVLIGOEv!y8LQVdEnCt6m*TM4c02vB!BT|^E=f+c=+Wr0SsP?;D@`j zOk5d`DSiMn=!K6L-|YYZYWJ(vJD1jiSMbeM3)xSKm7V@kq z=;%^47#;CWMF;NG^iLpNKrF`*{P?zcj^^m~JRI4$<1d0-dvWZF3otxMw;nGl{6@fQ zWo5GU?K08$>wgjBv%&IPM6J}}eKu8ej8c0|O0!2KZu~I%vd7XOI|)%+9c7U18YyN} z)IDH|20=`cs5YZv75&EPvyGh&=w4~5s2gB_%3!l*!eYZU`)P@!(yrv`e^M4H%L*6E zdOh?8;L#-&GePj;1U=itDF3(f+3Oq1(sk=GW0=ebmmhH~A@+~J!j@iydM8I_q+L?*HMn#n^ zme|69-fCC&9PIy7Heq~UllB2n&YVGq;>FEeOEg?WwfMq%(gL0T5pu)K$U;riJUc)S z`ZaFwKx&f3Ygz3uhq&+qY>HEyQUli$i=!vY8+h%%N73i`*$ z6%z@64~e_okVjEr@^8QKVA1}eIx2<*i%lv#9fNV695>(+&jb2){O z%7Y@i>dn7K?b5W-gfS{p{?z2ovIA_&ucH(!en6SO+)9&FSCPnz!5+x)As|$qSw?sT|((B)j z_uZv80sJDe1g`FS%mH+tzI=H`j?~(L^z6&~3UUImcb6fQN5_mq+Wr%0jQF6Y@eoZZ zJAZ2%=8}wwC%JDjwTqvJ`c}u%qx24Za-@5Of<#qUkvy>On>OvuL)W&uzxLSgT2Uk# zma-uPU|j^btqXBhRzlK_c|Ww85@F(|f5jShogqkXRdirX;{@%C7)1}xfFugKPu)4| zUCuAz97&~VG&`jr8%g% z9o~vg>sQ=N9-iF(p{4~yBb|N@$wGPHtU8X-d1O1j7&y*+q?CQK+#6{ z0*17(_%yaaJ~E9uNeI)MD_hyx*Uoq-(677)C68Y8Q@H-AyFz_6;@~LEOrt z(I7Wl&cfM_H_y_8?rPKCq-7JHX9f{~j}lG-Urg|38n~MdPFb~h0jAb%jqVF+OFDrWvOPnu|&pZnyE+- zRL-YSjO7{P53eOkEymoBap>*k*VdpttL}RGrqL(~C2BD+3KHqbgeMGb`$#+yJhTs zWHBT0L(B|qm+;64fqJ;FR(ZclZsJi^2WjGDOFNYUGw9%UO@AgkGVOylqjlyH$2eQb zz=q{*!u}+`b%aIZ^#J<87Y|nFq4~!KnNAuRUV|WCsA`O0wO|6bdeIr+tbaSd9)8M> zsj~){nQKla1sa$VWjaXe;cJA!gBW8)tbWW4^_lDj<7<3VxxIXqde{gp2c%~@?a$a5 zAJrp!(0G!Tx$TU!S8XNETyTk6faX7f^>jSOHy7;$C^>DQCS!>wsTI;yU3z&(zA77` zm2RiPUE2yey?M=&O>6N0v?F$2{r$KLn}`dOyyke3ky{Z#+RKHhLC?#RF6OdQckbJs ztz*w>6!Loy6lj+Ygyz=7XRpJpFhg#KEmmxv)bt$CnFZgM4ac=nzNIMR9R5Jkt#cdz zn2eD!dq@@7-rQ_2RW|5G!k|dZ$8&_e*YM~fwy#Q1ST?Jjf0&d-sS&X&d{m$MXsZU{ zYteS}#J|Fw1zL%IPhwe^kE*_beD3~%5E3Fgnhc(8p4XYqtWmnmJ!)#ujN)bM?rBbSNZ)&nNpF=nrIG8ieyd)mqs;+LEa6o?NC?ypw&W?gq)rbI$3} zwAsUgcNeOma!)Y!-RXFB9ohQq59*rqYeCtt$qribmI74eeCwTRT~E>S-DOl(W8Gq1 z47T<>HE3H}jHO&%-z}ETLuUtSpMn_bA4x17F}|}FybAN%mshztjse)OD=qn?#$W7O z*uPw->gt91AGu=*-u|F)xN)0JZb28akz@EdKUP-;QqbJ)Oj7I~xaz)nI zmok{ILxxz5Np7z~b*j*7pj--XmwJD798Vq=7MvQa5Bv4F zc74KDQHhhqt@>&*!2(cg>fDRzQ)`*r{z0tD1Fd?1QeCcOd#)Ripxdrle0#8cmbLHJ z4sfA)!26I9+YF)46Gnc$9Og-<-e~YPa-*VM)6?uT ziP!0rQT*vXc9gus;*5``_n7s!^R+KFYt3K}r3r_vs9OE%c#tLgT zP^?!_YgdCkE;vq$*a+P6rQpKa)HyjPdkezc)!-jHVO#xsBS&zZ;)s3%bq8T7h@$%i z#+e*zqZ@6f9Bba5!|X z6#Sc{lcVQI>axy|aBhrMhOuE?ItfY?jxFw!ah6Jk5Z&a1y5*YgzBK(SA3wb-VmKkc z(`G`XcYxaySLwR0`BL;xeNdm^$MJ-w=QrHGfCYK}(2nC%e}m!gI`!0aeRN(XOu(j= z9SJ+q$Dsijo_$`5I#~znU8!3aArB)866=Eu6fC_|TPP$RM@Hi^`(9bzT`qdN;@rWN zA=mZbW7L% zmfNnnaz>TJcYS~t>v(Cb304tZV~D`3*D~w(MdG5eteUtyyBxROJ&G%LS+L}B=nEuH z9(1=Mng`BtaF*7tG50z)nsXAM%7*>6=@RI*`mLy-`KA3e=wiLkbX86~UQop#@6wS1@v7q`KYVTGJgS~Bbol3&cidz44}in9}VD<~4f9~ZAC zxLV6Qf11OF@+jH6e9r4qwZxh>q+l4`V7@k-ImxI+*>(ultmBaJw9S)^?E^j>e4c8H zT6U}G)!6|Ej+Oc1`I`d1(Qegn|3Roh?6|!f{&j{x{Vr*IxcibfOB(^W_}guh)*Id?8oq zVd%b&HIGE|S%%Nzl0^N)dI*vq^O^S=px1Q3afgSYV*LDdof{~7Fj6CiF}|y>s<$Zc zs*?q=R6!XeRFK!L7gRfbxe;ZIl&r(x(3&%)Ae!X|qWu$}1E*8)1sPwB!c4BNvRav z5kEU984!c=y@Oz+eJ-8=SWA-)TN=tj59FvIJ@Wz=?wz`Hrttr8?Rum4(LMzasAg?? zcX(Vv12%^+ywvTkYFf{hDtWymotFFXdww7j2vktK_( z&$=HN&X<1h;#->WL;-tGZ8kMUN~mFU8Dl>0VfEe@+I{ym849c5NDA%_LF?P&IUDZU z=&>4^-MQ+0@iZbHcAEuvo3eIAip*Au5VTtpgNnKHZwqLv0FQlX5^leHDDUr~a0S1w z56C}-OL-%pm*|g&5R{5B*9wzWAKiWWpGR{Ox)#elI-g!9&X5-@S+O>^|R{1myoT!LNouyk=0{laWP6lKrRrUw>qI7Cthz zafcU~oXoi`o1F5rGEU1Ga3^RzU3}s;_=pVsw;xyzBF!{O7!pl!vrmEMZZo|aPE%{d zhQ(uo8wX*VlYUsQ?jimAnqlyB+ov}R7IA{xy9V--Wf|Omkz}n9E_7peWPJTyQSgQ@ zWA?X4U7~$;AdZRq*;X|*vR@zslh zO-h1=Pi%ya5%wA7fIi1WRg3W2TVD5*OVlSvz7LFTl;T!P|6|qe*Hew|w>sD-s|uL6 zeo7@ve&d^-BmTlB~E=^kC8|MstYsjnJbVg$}=sS9PIXrzL) zl?i9KD;&-;G?P%=Y&ObC8RDANZ6Zk~)AnACMsbZOp%nZAiav{`O^#F#V-W zdT<%ToSSM8Gdp=tq%jLbv_9`9@>PV`@AmT)rK(Tx%~tj(%SrKknu1*4C%aFZjLQIFAqbJflf8(Dp0{_C35ct>87>V98w?gxANT*Ge6OvpCXL(31VP9c^dSaxgvi23TcVni1ZKpdjCHB9x)BcGwMGo z^y*$X_WB~q6bT9H%lDpTm zsPRMzWIt(~v>`$SOz+pvuk^U(`nXKMG`HIF8~xM&l&_620$@y@$X!{`++2#X(hT*E zRv&X+-5p7_t$IBJYmLkTM$fUnUgM!){#*aL8Be}6`QWLto~u#fw@O2(tzU2tjK*9^Sg+Gn$K&d(nQi9N(Y?sO%K z6IkfdwO#bm4tLREEU4~8B;DFHn49WyyQ>aPh?Wu_*_QYajz1##3)8-LNPUlyaCCC5 zGjgzf*g*SOFk)7;A8$o*Hp7ylS8`zkR2rb(S%6^_d9u^qI&S82T%l#rVl7N5P^jz` z9jNq#B$3_ikOe0{fM{~cS-ZtGc5-amOwBF74L9&r$+3<`M(Uu-t^d?|oJ6(snmvE* zP5Z6Fqx7{Pfo$hlUNS05g67id&Za>-D9qv1K7Q|M3a9BNRrY<6ZvzvV{$92al$~$giOUcB?|@R*X(E2tv)A3HxUcx-<_OK`X9ku$N3p;q!oOB z98c{kKU^{Oc7+`5E*5)SCVN%@n!M$c;g&;e77FrP_&E3$c#6t9j%sz*O5@EfOY0vx z>ehESM%{e9(;Idd*?FngTy-0=uY!qasm(sA;lbpgvKc&R-z?SKS`$8Pe`50$ zHJkLZBeVC;)2!JiXc}oJJsOLdMh0k-yo6x0mDM#|4dq}kI4}8ksI9uhkcm`p`z2mg zRA-)v`u3lA@4OGpdT@N9A-XZ!(va!fNGy|I9`aJAc{P#YZ1WN;2y;*^S+i>8$X%!G ze9SRaP7pH2-?zJ1>g_zcOH&7V{~Gt<9Y7vsHU;_v%|Vg0>THj|ZU1#TPNb{WP~=7W2s^Gmzf>?9dddDGZ#x zOi1=f->-3l+^w@fco)kqJt%(pEbm_l-?uZ;Pv?sHXZ~%~T2I)w+%lU`?kAf5Qd~lX z*HpMF6k-+nMJH=+y(bJG?xGgGKQ(rc5D(XB?~}c zoAMT4?fzlTtd9y~w9i9tf~|rcr#89JH<+&SAqv68GwK4~D|(GJd<}n?yYl#Qc}}UY$(lIp+M0!bTbXCA;|S+YTUyeyXd~4`MEL9CdUGpz zv)AV`h4-UD>h!^rZQawJtk&Ly{nS+nJ>^U(A`CNAVV^Ne-zJEvUX6tmBLjyj?@jjv@35Jz5`%h2tIkVRPto&0C1%|0T;=HejY;#ko9s=u!8Wn=8br2V!x$fQ#uh<|18ZZ0m; zwgeq5@@c4|T8ySKwt!nW?zdzD-9*n8uM?J`YxdEz7D1o4I9~=Oa{ccRLfRDHC?!Aj zg79|dsJbq@_P@bhM#ioqYXEmCM4ocKk&1v@Nv8{C4NKM6^15C27DDN$=J+o$UUsd^ ziA1MeO6TWIRC=Jy$Ydo0MbXO@*6R1p_AEJU6B3Pxur*!bAavTq{`d!{s^E&Q*LdoBDB<(|PmEJ5o}-afbGHMmN6B%%rd*`h0>i z85FJ!|9peg5Rork&H0U8B4-wv9H+2Ht2!s=*$!kQKd08A@V{KMDAq`wZ*zrE=^ZbM_AgmKq#cDxPHhRiFYiZ+dHTVs;iDQd?(?l&lo1?Tp)Vl&144O~TuEz}l}i1&Xn5QTq#qtCpq#p`EBW(tC3*0>!lxNzZxSTz&A3JMT-SVx^($X|~xbW$HAu z@9QVlD2IP_72Q)>HW&xDiSomrvH1znTnbtLRT+tyv?8i6^-> z_42(q4%I$7-R_!~kPbt6hF^TEtl=ERyv8LLFllZ^(qz683j(vbGoF=IedS{Jo$JcE z=RFy&bKmxuAatioug-3+7P$u62a-}K8>(J>N;Z?L5~HRVru~iEb3|h-zd*&u(Xnx$ zCX$tSGvp-tsZdu{Dv8+4Mp#Ntebyauy;uLZjtD(QRH%i3kIG9<-?=!yTX4Y<$0*xi z3hyOz19NJzmGf;9kqYNiJ4#O{apCYkqmx3jmKbo<9B=36c6)SLJA=J&h4($nb_-&k8O+)!l)!il-Kgbp?$ z)Nc@bCuKcN?YehV8fbmvuNqpX(Evm~XZ!%Kw}V4T^x9tPoeHQvw--ASDT<+T&90f+ zp8}*@!zcFpFSLYebz;EB${OG6=kkty3pCHV6=v}_>hU@j1$(7FzaD$RbK}e0Wainr zrdKs@1`ZwBJVNuVH^%PfHY>U;QS%dwaV)B1b9NwOQM~)JPK{@DQd&J8_VZcvndg`LT2E zRFXH6s?2gW4L9DkKo+BDMyuW{*UeRO5$$8ub+~R;Zo{+1P|P%MO+eH%y(pawksK$? zBE6t_l$ooKPFuv?g1A4t!1K8GV1NFsr~9mITK%wW+*dHR$<5^Z#dL-`4nV#d+*{}F zJ0USM??2*wr5F!%iN%eTvlwl^j)W#y>JSmJao%Lz+Wa`Pm>x?=D(G|5)6;fQ@Cq_< zfSQ1XKp)6jpH@$BZ+}QhQ{HcGyN4vJe(0wT)#UdE|}iUR-f(E@9+HV~55@0Y6y4 zN$8F|5U)rvz0ioH1GxogxyZITXJMEkZ|zDG=s&b4Z-lNeW*pV@!;_c#-P5F(Vj%f4 zv2XgYS05JZ*C18J7%Z17&wN`x_7+t>_9PIBeoPziMWF|-gn9HK$d(wQU2ir+j#OCW z_f5G~b+P2QK{zn=E@qK1ghbwdEF|@Sg*CGQ)!OmQ29_!HCR?J)N4c^(nLY`W!PDY1 zDQGqch2ZK%m&$`U6o>~}?7U=@p@CB+@j~LFD#w=;#|#S z{W#oWqkO%4bphY)7KR_mOu;f25I;n zsiaaUN<%e3GXhu6ps1QjSj2or8C!f|Gm_1uTfee$iTi>iOYRrzD z1oHG)AV#KZ5~r%uZqS*%ymzS|t{#D8=ymyMa>#~!T7%Nmn8n^3Db})Q=x%m}y$8nA zuv(S23X347dYveuZEbS1tb>SKd4mX*6#C_&Ep*t)O~_9U(C9A?aE@2}Mbg!4vrtSl z`<{jpUzf`qx=Nf&T9+@7qx;|1mw{V^dZT_K@3}MQv!#0ofNS&+A_7C}+xF1QGCeE7 z%YDIkrnQ|HUPlFBtMDr6x#a3~OYQYy6VX&B?|dU~oJ)Th_~%r3A8(*t&&3hGE5%-A zoHT^p#&T?$;Q3ca99WkHkqe=nbz}cYA0T095TplwK&LRqZKcack|p`pGRIA3QqMB) zwG#Gmq$=#kSPH?ugl(J8z_YgK;;_O#uOdU#zgEpdq^Rx(&#LkvEiX-6dg7tWG!)~4 zy`7&4tDzsWh9pi#K6PZvIMl!Mi7?J-e6fH_3YreJZehlZ1hQ|p#b1REzGXC8TN}Ha z49pMSk)gJ-xWqvQkFymEL!vz_+H~GQh6Oy((}m67uAq-fez_LY*&?n3zZ+H&5t`~= zDq~er_Gz*lB)SwhBYXpFH!TnD*dg7NS_C`g$KKIyzbhWZ*Qq@?E^%IvIih9JB=P`# z79L(_jm7|Kv`$bP%yngX}-)R2e_xcK}-7x!9n-g z%r_k56~@$*_s8~WCpDp0213h;<4Uf3MZz5YN#DN}@UxNdGBQlP*yaz+4W>DmPoL}i z%IbUbWR6(#*Ydr};;#ECARpq8_?NbxjC2x&WLQqcb@GV z%PusM%Xms8O~bEB`mUNDFXnomY$chf|8dV0TKA~91oRL;jzGi51 z&?^M2RMy*KeLaxsZ6?8;e*N@M3XOQ8&DYndXnQTe8gNT_baZR;#Qt^l3E~uJ9gP;p z-mfBy^rx`&g=2mJlDqZa>PKAO7q6|WfSBla)p9< zGg+I;8Wf5lWeQ<+&%zQ-w1-9R9;Jz%xQV4Vcyi9nxF>GLAM>BMwFNHu$}152rd>HG z^o$WndG&9$4=#GnDig=fQ7}{}Hs~k;4+b8qboEyitqB`~cx(D%dk)GpmGx4`z&2+n z@f+t6HvWY-izerJZ+dFerzV0rfb%7pS9d=L?VTMfh~fEzn>- z=gN+(($2@|bV8-d5RGk7Ick21H}=7qY^5fsWoH=JJbjYtg=YL#zO4NR;YZ{%h2R~z z7GY|GR3qoxgslcbB6~nxxgg6id%I=im#7i2=h@iU!hXDex!>Vn{%c}UD6_LPZ9ANl z&ui&ZPjn#W0a-LDg#ljk)jTv=q2*=A2{FLH#?d;R*8ztJ#cxCDAz(3>hezM<%50@_ zcdO)%9gosc??2R?MgIs5zm|v-u} z!rN7_+kAOzI#voS3I>z(w<=N zfL__+;Ug*{H4Ey>aN~uqq;dST7BXy=;UpB?1CTFCT>{*+bp!lwknSxDVIT6nAz6!1 z)z7?sZDyjwWdccAsyE3k72}13G%#{k__|+AMCovD;Bxh#TG~R(|A{gC!DRko&91WC zVj3f4QbiZO5+Xsc?)v~VDhvCtCm|^ysgV57&n`YT_U#8ok1^*=5&owWe}>n|5%FC+ z$G1Z{>Xct#=JS=x$$*{kveUcT)Wz)2 z7AgU=ZOa^V55M{^3H0Baw}=q&JHuBQ{r|7>lQV3+IgO z_^~V-d~}oETf4C^D;I%_Px4t&_W28aHMggV*doM#*XEbk3N2_f?j|4y+VI6qHK{wB`HP! z^LWSMi!_tEBA}j`bO@h!7ADofsT4D)vgIw&>VPY`s7Y1}7fSN^mz^AG#56h+!B zefs|tclbX06aU|q_1|U|c;Fc0^6JYU4|4A(DT&y|_>U?6y#HU3lK75C?Z~s)Y%^%zBgepW;UFqOFVOGZFH52y~+O%X8m+eUwuJE`iC=9 z`;9aE5fTp5M9f|?-7L!Xur+li-uSs~q2$jlnUnLB*)|`bCw~5=O&#?@e-d3|u7V}t z`0g-u$hdR%DNkA5DSJs9XjMo%zA}7r>oUR49ewcI+;~GN9myl_;_VZTT!urD{g{2< z2CoyvuJ5Tr=g8ln89bT+({KM6EmdR>Tr?iv{~g4B;ur;j;r6@#%lY~ja0MZVxc-N~ zH5md`hp^sf=REvC{d-f~L!fLc@qa=0?;8??^$L*xIc(qmEcj16{{K7}efbF9l5qkjzVy%FUXfbr{3 z32!ZmSLMx|9Npg;d)?)8?AM*aN2vUzkh4{=0|GQ+Nj^q@!bJY9BdQD@>>Up`C@NDz zj&IV>THtT;y~|3^Tf(~f7%}U_J-gg?IJYY-m02%N_ByXRZOC&*5c^FAm^gkWI21+GzIDUp&k3kVeTK!m*2%F7mW2t z|C|!H>r*FY<<_K`HmvK0J(4HqE)yn}8=Xi?`n0-sT)g{v8Ur>o_qX*Xqr*RlsJnV3 zj^MhJ0pFapAXxK!lO;vc>yJ~;Z;6=OZ0Y|T{+RdcnLhr3jz0nN8Ny1xzeo8UHt#)< z{7EC<|EdNPgi~GxAD<8dd-I(N;n|$0 z=T>BE*2iH6REf5t-tZr?t$`1Jv&k8@pXnQV`Se&87q^2~2A;X2%^Dili=C@xv4Ogo zO(qSegsxgu^aO!!P1;jF`A8lZzjymH(+7V4O}|sc>m!cN&WrzgFi1%8L59*uK$ETDSMvT4A^ z-dldhae*&l0$RN4S}$Twq~8W$2A-Dg$jXS36$v!STRmxv5X`+=WF>1FWp|{*ycM(M z|B4=G#1r%9@DIE&njomQP2ZQbc zXU#Ug9D?~1K>rQVe5;g!s)j_9<@^wQ2iPMneV=mOAuR=>(TQXe77QG~E_058z(Lho z9mfEz`DaKuwcFM+T@9a+(~baWb=_m+t1Z~N7S5iltC&%Tm|R>jmr!8Zc%8c1RC@|& zwPZNcRSXLCFBK+IA6tK3HEV#wkv~}RbUVoqHP$SK=*3Ei>#f2R zj~>qgPfmJnr}m9scWij*!A-xa(B7FC%mSl@vcg(!tCfaw&@w&}r))1NQZ>~%&jM}C zLRHMfm3GEfckLd#rQynVzDW8`i;xv=ev3-EWVqo_Dh^JTTl1MC)LHK~hk-XBjF6Bs zrmv;Zya_lr-?eimFH zc^#|p>c1Xow(-b7@sDjg8Oze4MF*@~khcpDg(1K%^4X2_PG_gq z=N0<0AUZm~K zVil+wCi;Y_d`@d-kTDIJ6xHLRW~rim(*q9?lUbY=biboteJ@h>&DSY86yV@K) zTA0RR;coSVEz~q#Wam*|nXl0L$oX3hZxCmG=n)$2x=5ZNa3cN@DiTD{ud_I@r-mKA z(F7o9%LXcZbg%tds=80O=4=fl`;hX(4Uudk1p*+yR4`riHfu3drV;VE(1$*oDo^BQ zjW3>L)xM!$EY~Ju0NC*?fWo@xogJ@&Z<3#g966CIk%}<5n08M60L!PmC}msCHV`s2 zC^r<_Zv;8m36Xcb@5~l=Fs0SgSD{0GJC%x&uCdjRJ6g{(uxy)WHb8{7`a-*4fS}!- z5U1|2dVp|rBEh6Y^5j99$!&PzK<0Q3OffWtDO<`bzPxm043UWGd;KF0yx?)rS0b}_ z<&T1MQ+hGHBwwOpwUT>^38$dz@z~xmVTx;lb(E@9ccnz$m~3n`PKgeT9=)|GudxqC z3FGkmQPtCJ3sculA4#EZ;JfS)ZPuclJ?W{*7F0@SWMVvv(N@vZTXs~{P}k6iKXQ}S zQ9{8oMn$EqB)7<2b@`zII%qnbi1!5nN7s!5b4T#z*>3ZY?aP2^wn`x#l`;|3bUm)e zv*H!rXRE0?iapNBbN6=~>rH6Emu8-Tdok3DYjdKarvvk-L3;p3krI$z$uKXWvom9jM;ZU^*3JEY}L~ zA)tK<=VL1JE}LTAMurmftjHh@|M0;_6%7czPc)B#8beG$kuGrJe?wM zK`d#Daf1{{r3@HRRNaCfW26pUl>eE|& zN#>`sywIBj%I#q<#AfJ}a2uf?n^6K5&sXfYV@%B-EhEyWut?#~zP|0p{%{^|s$=WJ z;4x|KUhun9Vl@Ns1<#}av0C*?)zqQ8`a;$SA{%6Jen?cGbSiZOj0#v2OpBxgr(Ax> zjiJ5!D^ZI`M$QxyiYYTO&^Q{*R z905O!BlJ?+w4(Vpa+j{`A&8)J_S`ni{x&7~+e`aOJIL{pKQA5T%oQ9>5STL<++AP1^B&{4 z($p!{+2^HiZyud60a5=UJEy)+mtIjHEq;l+(82_h2)Ay{$EERqrAjRv@FrLdPA~0n zuZw=X741{a(h6u@tmXXx@MinmrP_C9+ z0&;I1MR&~Qcx~(wI+S%}HKzA3f8-9kK%94PUCOT58wib|Rr?Z~y03w0BXo0wEb6yM zRXiGl9W7g;H7BtVef~W%o9TvhJ(5k)_{Hc=Jh`O~Qw3pN_HdDRQ;uCaSDh*v{(Wk) zsnN)v=sL>zEO;s2AlTp|0aqCgMDE`!z9Ddt_hxpcXOhHA$zn0*7>6P-^>!V71y&ed|4WMr7`u85lJ|U8ajxb z?BX9KVjAeS01HojR`Pt}N3$9)O_hzzd2#>elndT=1ePL~jc1RxMW+9mxIjkQl9||(WqM=;Ne((c=;kIQ;s@j)EZSdu2 z>{G(i38{$d>7lu~bSv4=o%b-%#b@wVyr}%$N3$(N_SDF9#|nL!kR4`G_-Bj=I(^w; zp?rho?~iK z${j`lHD%*q+07~bJdG6^;SQgztMNxK{*T(eGpfn0>sGN~uUL>KVgVHpkls`Tq>J>f z^p2F!L&SzsrPlyTuaOdZ5*q{v5FsQONCV40E5~*l)eylm;`!^+uOE6Yj0Oe4GDE2QsVF6lPrB4*0UVm6t0TIP&tKg88d-0bz?& zE5y0WCz>7{xa|xh!~%en@9sL|hVCMA#i%ZH?@15k1Yk4K8nd1&&6_E{*JjABFv`U1 zhRv@S(DxxHrO#JuXIz*pRW5|mwqWIaNgjTL z+E%)&Ud(>@;IXZG_}8l=%U{wqo|(MeFdzzfFFOfg)~qmUxSQnq$vY!6u^|j%mIsHh8cy|j$459{7l+^Lnr``IcmjvTssR?7t z(+5=!ytbswfzx`H?kYnYNrTbdO(LJssvzJSjGJ#)<7wlV?;nL*UifQUy)=zRr>Xk6 zQYHX`#-Z;5+Rj#PHqd%Q?hYUFo_pv0q~2I`oXPZ#P||(lS;MR-3t=V1@(=ref4C~S zkD4P?m4$b9e|Gi*FrrM@hgeXZSIKq7Eur#g2+)0KdchB5CKvXkn&h%XD4ry5JCH?J z-<=L!pSKHx2*18Sv%Z8ImGUoX-S+rKTM!U~UA`9(%N(_7np!nEDSQ0w94VkB)9@s~ z)Z?S_f{(iF)&?L%rSBB*l<3&2U$)_ko?jK8uFA{PRvqozO3j1l8&3myer@|WZw2eP zmet%Ea<1S_Sg+$xI+hgv*l`?=A@bQ=S?1{jd^sYppkuTVJH*)b&EyVZy*zYQZDHMU zdVR?A#;>6{1i0I_wPvf%r&!sqjRSpjEns!~NY-3ykawr%Q=r6cDcaR_VlIC~$Dpna z?cVe@30?Ck2bQ`@awuLnH^D3A-*X#~(|EM{Pyj)~B%T#+sCuNoIV& z&!>!7^`ioY>95k$i{GZ_@q^D^MVE}UYLo9JSHL*MLN)VFWfY$bn|aA(Wvii|O^T`$HoI*!H3n}o#j zT^bQGcx5eycz+yaa#o8aau8>9%?@m7DLqP*o0pkI6^)*l*w;XssPj;GvwVMytj#^jB3K$ zv&HaT-{)nF1anW!YTNmlzmW;VeZOz#(5cSd!4{UeLyld?Z;=H9Z+CtFS?RUdZfn`` z`mC2q{R4?Z{e*Y-orm?r`5!5v2WWpU zuH9iW=DnA#4xj9(^ePX=Um3~a@2qg|*QEbapG463CQ!eZcZQguzyS{GNfELt0JFa< zEtG$hW!^^1wDoqe-`whNys7j)?X10Tms)UP24hlS?WIoCi@uUNB8um#=uuAU)Gb&FI!5quDNXXMI+ zM=w>!KzS*>ggICVZZU}In%Ve);Sjy{7@og6Aloubu(|1rD_B^_yUz)$=U(4xay4ar z*3uIB+Hti@kzz7QKTZ%0IH#aC8l)#qZsCeCNf1|}?7|C*<-{*t5*}R7E|*Ld8A!gL z6b4#VZ4rAD`>*{5CKpO~Opms1z5pED#PTnQF)n_2oAjFZm_UV`XwoGjv}`T!aXz0y z@UdTKknzE^j`};a3BL#DdK0wdciFgutDLF4#|!8s;8%jVrW$tsJ>lo+3J+X%vEx4BYuvu(&qdtxVUM2 zskT?TsmJ{`?f=CugTJf0XVmo#V;-HKiLSMLE&jT=W6TlpGJLQMj}(UX_oncC#h()} z$`Oy250vqgLarY3RpZEIW5Pd>sy8&+?CI7cy!z$&iO|B;jyiN|`?!hOo5EWs`A_G% z`tte1f7ZOEoO-XU^l;0DeNU=~v3e4Ljy7_szb1We`f#L7d9KcC5P;VDW5sDKXrxKb zPxl6VYhuJ{#a(qka>=BB{)4Ru6gcLDhelD*_gV31?bG|_+Er9vT|B&Z`1)=;4_^?4 z)i8W+Rg%=ey>VB@y~Z8VtteKeeYhT>3X=g@wRFdbbi$9E(*{GK%CItDmqbnoIJ?vIE=ltSv{jC~Qi5I7Q zGOj-ND{}c}RW>UkkR){uK57^K`sQg3ef_HOYkJnImqcDMt{wBn5Eh3nc@=Z;>@5k; zd!8@>lbnH9OnvPxkP$}GaczCT*8_YK+@8zb(|cgEVfSePo1oP|1uyFdYCZxV6oG8R z45@LXj}msFYv9R}4OBh%T|xUxJC(EO;r%u2s94jzFPnZ<356${nQTY9iQ z_UMKnTTW8tE5zeW>L1$t-gukHjzMD^7hl&kUd4*iVqsk-I2wL&CL<4BmO0gyDIsm7 zZ}hq&I8WYw`O__GJnGVm^h5nBpm308=2)Ho>NTB4Cy(VLiPD$XPPP0i34Qn``$~zcCtMIUWOiQ*$iclm3?b`>{4N#hhlXoVbU&)UUGqIpPbXb{# z&sckV1f?9v#UtxFO<#Fku4o~B?X?j~o9T8Lk^7N*Mz-?>=#3C!c{R+8+m_l5q#>lQ z82NB5{ckQ7A!gAW5OQ66t5t2oc;-p@;GT^Yp)Cd9k;bqaii1oo#BEywUYo7yyVlqV zd%;A!xKv?JNRrKIExvi3=e+!7<_h3OD6oO@z<2Uf&D(U>@x*S3wFX4at43yMNH@9k z|J2tli=B`9ZN3rr;(svT2wWNJ7$X+C66L0Kb|-k5`-N8rJWyQ=D49w;SfK8hUehH2 z2|=0=%3M6}y{s!}-LO14y(#uQN6ALx3cNYM%|!3s_Uvx0lOm4(zZS&3rGruvD(UT` zvfE)3$?F=IUl-YqfTq@T90KP>Z^Xwg@Z*K>!#9F{PWL7d9f$T^fUKkPst(_lEtLs+ zXQzvhqC8pyumpot-N&0A5%w)jXJo5d$FDD?Tuo3f7`C>X!9SW<4M({2Y{aLR`3n3G z79M`sOJB8?prni0t_g0M!(&!(g6}EI-b#v z#S1k0*H?1L4Sp#--WmUPka|8nzKpKU2B&LY4~>z1?u(OFJ)zm#2BO#|BY3H2&1P}! zRU^%+O{Ud*1bicgJZZ`(lD*uMiKBOZvZynfeQqASTAh?0fOtO$_;Q*|(HLYG5Z_t4 zUn`ioA(tqGf|Zioo4zZ*7=kv_Izp#Pwl9m^P(*H6nL?SOr}L;VFSO{U5C*xm40|Ge zblq{ii@)GYb>EvbgLf&>3AS6XnXZ?vc+PZ2(64YBHOJ~*0+D!=*Qo-(?e)1>v=uIX z64ss};xI7lboNO};~8-}?{WTc(&h_!orv$E3U2n@AY8ynkMVJ1uB@z+_B@$MYb*4j zX;rt$ruP-xS|{Yp_6Aq=N?tvk(j77nEqeQWDuPm)9QNKgaZ7qUy@v>O-I)B+ zwMp(CX!O|*y(aF7u6|r&4u>z^PVADe&<9h+Q)WLr1|zQf?CJi$+nmG=|HzR6HI180z&J=$ z*Wcp9V=a4<|J5Fv-47F8t}PjY+dUpy!?{dhzxCEsafx6`&r5|Wcefha|4~$y%eIpu3iv*k9gItulXdL8=$G`I=tG{j z+irz;KZ?+a>OOGZl&}!W0Q<-QuZ^EQf7$M!ik!pZ1YE=yNR|!d&6VN2Anu>je1&*V zW%_+wRZ+(ipQm`^^XNR>`NMRtK+wKEa;fL7MoRexYyOF5DrgLwnN^;cuI$!=E z?1?&_b1P(d@#m()fY3B$DG1RTPCr!sV80aA*J^Fuhs3_StiE%kH!dtcB9q6+AJE)Y zKRPoe;I`A%-%r_ZVkXm&>(4 z82!e=Lc3vN0PTY&Lq)>Rx%T-ve0Zq%U31HvwaA@5ZNk%sA&xk$(GZDANKg6h0rfA zzX48m&DeS@F7LHK<3FceaX$J6$`#Q%nrZ~qMx|t&d5=y2ojT?J^P$rsc%xnH$#ft8Vt>*QsQ+O9{v=f5JBV zD=x@!_yy-=AkRC`vUS>nF&tC?C*9(4;p+jN_c(rm)K0rP=s#v9y}@IxCBc(F3jFUs zghFK2&$9uQs?rJn$9x3yjM~f-qtR&L1~=&E6o9}omn5ZW|J8)bo8F5ERIzKuImsb7 zpvaMrXHR6Am9gl`%rlf{JDE^v)DUUinpZzt8ZFtRS>JZc zY;qXfdN=oUmVQ3`%MzbHV)D)%&sGBEL4fal zEI7``q!B~jrv!>2H?47i_luf};`=$=>}jYN#nGhxkf*Nwuhke|spr%n-#JgX?%5kG zYT)5DoSU3IC!AQ|Fnzfu9yeLu({~Ee&od=lqc(jb&15woOE`gJfuc{|Y8CJr5JCGV zUC@2;m}^ieX$5SAxs189`0SCcLVVGi$M^Fme;h2aVVs6ZR`V+l1TFf=MHx*cWgMro6 zRc=*PcAyH;0wvIDsFgI3;6(d~c>Q1B6?WuW48Z{`O{}kF+=^sToD4kse#m8v18h0y z)i)!*g1gg^gU2&^?#X38(;JoW$$spQ(KSc%c$Z#;-B?&j=a9`6c-S|I zs;(7DN-(U?&5i8>S)S{72yQQJAa0cPpI{k9{jE)At2aq;bC)x7F5t+2d9z^J66VhP z=9U+XKYqUu7@s_HoTw3h>gfL6ONZ4-jn4U7e;SM2aX6T>Abk^pa_ilU992o>fkj2b zA!z;MkY;&3iSdsQ0hf(H=17kK90fBl*cv~hgyU{6*Z)0})G4WYTtSd=nRwKlypm#* z%nQA4(b%|dQ;efdB;fLC$2IT^2oY!C4I~!Rt|ezFw{wL4C}0e%^A-3}bzE#=l2~%m1;Jc15jW7VBV2$*{Dx zx2R^9nNaE1BiWt|H-IWAq?|9k;LGJqnPqQs;Dmeg(d3a6&HdSn!lld<(-Czy>0c-Z zMJXB2nDc)KT`XgsMSwkw6I6MC{1AU~`%y-+==A0O3)#3~$BJPJMsmdGAq&ehM`y6z zHe!mKFZ3Da}2$i z;AyM~1Y4gSs_I}%tk(3(+Z58`>`gRlRUS6~p{hP7up4w3B%R-| zT2fth%8$FL&AU`ctszBU>Fs?Or!7KrG*`w3QloxbhM03Ld;s z*8ZgZX-i@_o1w^<^j%d3anH-y zL)Tu*lr5+yBuZ%irP0@g@3}8Qk$2{6d^Pe(0{Uvt$kQwb9L1sAg>NLcu?fm|WzN;% z?0Pxudd0H`Xr9zwNhI7;ByYbt;yAf;$c6+xrGq=TzCg|L!0ygGc}^WnFc|D4H5htO)JgDO~bOe%7C*?^R|9FL%J(>4v~% zEFpfm*Vz%V3&nQiRW2OImq)U$fDLmAxK4fO(iIAd=`>?Deu^z=Iu!2sXbOM#p@R%7 z-5{i`M(wj3sK&BET(Q=WE~h7<$Lse&_?<4G&Zs?fIGr&nmSzD-C~|wD^<_Pgj}zyc zWiM@qRolNqTh#RQ!1*fj23gJ^4%k(1Ta+IZ!EhTMpD#Y+IzZ?&#U@rZfa$Re&ywN# zmeH*EQ-C+;R^g7&7v)FEaRcyxLRO=12;4LHQq7cFU}3>+r1Ac)!E1rBa=kT(tw)(D z1`_)kOq>doaBhk}1$KxDKdEcoL6D3v8j=)Y9EvTKw1iwJierR=TLike7z%c7jZk*< zW&v9RImneq!|6Q@qNf^*3kFm6KPoZdeBS-|L}kfspGqM`P$U)DUN1h=|1CdXL95VV z=FPIO9w0fMIh7I6mY6$IGFNDcF;dyr?O8O(5MILJjAS-4S^_En~G>Nc{_N9w&a@0GdymUm{D^7+FYd+a%% zqb|W(R7-exFqgk`1_lTl{1$<_ANF!oiv;lcuQ&C2qC$iz@C}j<8(% ziUi*Gq0;~v<2f|l#Ga2`-1`n5(ZxOFW=xxSQHVfV@3-5XTt7|@EU2yKAZsreZL*0Y zU@dM$BQ&jOma0g`<`Jb7RK~jCPWz^(5eL4veAi{4piCyQ-mr4+0TbH4mPTiH zAu!5&0(p&c5#bF+4$U!*DoG+wRZv-vSP7o7&&6ShUUdO_fRv`lUW<RitI=AeL>v*|APu85kT< z?GxhLr(JPZucFqyAiZ~(%u@SL%kX~$-V+-q@})~N^M}hMsTIXqY+JlK_D`MUTrfdP zBJ0Jw+$}|i@@@uUI7Uj{lC%u&#^KWd-Z5+2aK_5ns*BLVg8O{8>YmAZlmw>#T!eE~ zu%SJG*;S|Zm3>-1g-Z7_xr{(Rd@p6n4j4{snxiF%iMn^$RSarz!$FZLvF83@d|@ zj5*&fn^@^rD7X!L($GuRjiio=us}lKtmA)Uw@FMMyKC3!8m;?xjepk$6O)%dQB0v` zp?oEq?I}o})OJZ^Xh{@?m$P;%yujQp&oC>1D;N}1zNJbLImY$l_0%0^T(^&?rq=iY zcO}s+cDYM?H^=ocep%!xKSiEYD_qr`V<6#mdtxi+QYIDf`ayB+5BbRqK#Bp;&~oen zq*$wX;)W^xwpx1{#=VDFs_+NTo>SRv+j?Q;KD*Shkcrk}&j(ejV#+7P&g8iMt#Z#4 zQ_4e%sZ)a>-#&f+d0fjj1&~twsYVg2y^T&S7#t*3oalU#U(Xh(MsIfA{y2Y|yNgX! zT%$(1yA`&j76S)IoEg8N5F>hS9oM{$(q2U*|nl*%gEtt!Og8cceKn3Gc%YM#1Xa6orH@(D&ju(5VyQfz_QKfj<94VQu z=>|yd+V9p~a6;JBtDY|fDCgx^K>Bm>zYZ+67*R8A+3;hYGvfC#7DMr3hBG)6)e2>k zERYflK39vE@%pKI$en2%cyRjDYIbJ1D1UM-L!lS!a_vO`*AE=nZ`;MHk6pw2>%QpJ zPq>f_Gvvqi2442J622OSD+{$#a3eeh{chG;sZArVmJFAM!aQ;Mi2oSf{H^w%iP{A* z+JmK`^natGz+8MC!!9m%Kf>JfuN$Ipf!TJ>;|pciVPW z)Z&lb+I9Vf6TlAYiL2v77P@5aj|X3j7AaM@onUGhl7= z-5(eTW@EsFny3oSVtLz4?^~J;OX~~8v#F|`cPe;7~r-`#ooxN22 zDHHel3?I*S>to1zgzZ(Zp^Zi$`xa$hB##4m88cDi@GW#85jJlxle28 z$V_dYJaLnS+(f7j)E3i=+{(spY-Tnj`5!&|*Ps`7vB#eP9{kld47;1IQ$;VbSlGz& zkL&x!tmw5~t82+ZnJ;A3Y*=%bsFrKlY?Zo*v(P!%m;qJ2!;$>ety}?B7L@dETMy`v zv~*sLN};&soLMEDhPVU(l&`2Ty|xSa{mP0KxCiRp+sYiqLT>K;YslYA{^WAHv(6Ll zhUFV6$H6_<`Yh1a?#x!y_i_?9p3Q>AF%L;bLv);K?*mb`CvS1;g35AZH)OnW{n64t%i6U!;&3D$FY6x$U#=#z zgW20jvx#RekBx2=NASCmUG!!I%BX8m!{BpQl&5F&^W@jDbAna)^c7vAX}XHB?Q8dO z0`WoEngNWtc{3hN@pQ+URT4w22DhsR8Q{|2So!Z@s+pg2uOD{v0m1T&OptSY!FE9QXBOO042|mgkzihv>?nb7&Q1vL$M!$lFfF3}?A;d&ZHqY|%;?2O7@F4DJa!7g?@Y zZ7Nmd<`%z+_yWq>C|C5MG-oA(x`Ubrji-X1`nZ!@^|NrcZ6=%RR$*Jcr|$lXx9|&* zu)0b@BQADwkcui^2q|lJE|kxM;RdclQnSKEJ*);*ll`mFKj1U+@G*Xv@M@$pX9$+J zx3$C)_S8LE*=nuZ53ycbLXtFvIjl6<$pDGST)O3e=#}|;!e=SZv8nKL5K`?ifgQUQbaJt|F^rOh`T?(6 zqn6mLcz`EOdc#@?HB|sBvMsoH^g+wZ5@tjE zftHCjYJ_mp>iFEsP($6hdLbJ&Wg+wTB6om~i$fudR%I#0{^U45F|B3eMh&5?ZN0F>mZ`gQD1j6s2-v4r5~?p_y3E6{on{q7*B}0)$fK>^Z*aW&$y<=tBmxjW#&kVt-rTpt9*fFz~6L!aaSZu@G0rk_Z4GN zhxLXJ4v4xiOOjdmLOK+ukUiXErjPP(EZt@=c(yI{z&K@L*#y-@uj=(o*AWHYeb`ps zY-@@FN+C#G9!?9z^V_*(e4h5czfV4!Ju438$bSk_HbDiWgH*iM>X`H#qQieaOjy4Y(UmUtvuq9T3k~jNpmTe7VZKIN>|%)*n{mj zT#hXj4sR=4B2*I(?TAF4?!;^)yaHm`5eBDg&L>*dKvu%C5b5i@E_UwV z@s&Wp@nJsB1`ruTu44LohetDiaImT9p4z>E2F2cL)U zj}+ouuWoQJkOQnYTyg`o)35<_4a+&K**sC1FDS66n6h3m=6lNy+>bT!I;-eXaqc5% zu$tlA+XInrw~Qzj4c!<+sRri%Hw*t})(s7gFwQM8J7_E`e5T^edU;icjgR{>RLSRG z0I9|;C{#lF$aCc~G2l8Z3g1Y*J>%&joteEVJ=8wX=I7&K{%-(wAK<-mt994l=*>Rs z8LK+1|F8hQ+S$O!7A4gyXH;r!y8@YiT(x1+0cNrB>1}s_%CPO68ac)aCVsuRn|sD2+XR_ z{B3Um(*hC*t!8eX4vN|Y9XhtZg02BD$!jmLo(o$2@~$qn2%`a|W#aqWERM_nbERF8 zXu+qk{mY_vCay=qbnd1@hHu*TI*mm`SbYlqv-O5mVFU4NbkcS#fJxZn-2s)Z&+4!$pMwEI)o57z|w= z{m?&Z%7t$@p@$C}7>cd_rWtm$kx3=@CCt0BeLeVglHUorutkBDsyV3G6l3K~b;sBd z(3rwVs7u0N;O6mRspDi#XS92(HI^2?ue|SeTcQfZccC0itxZhf*86Bl5t64 zhiHtCf*QT+yT1-IJ(w1wM>w8VNqbzt2OVea0T-LTjc9wOhcG!rt` z1WfU*$b(8Y`(U(iZ|8qfR^+($gd6AD{#k-;!A3a&Tjd@)T8)E9pqj5z``6~dYvaBX zDlJG`c3%{^BI_amDy956KD3Thx!r?z$$P7^V|A3hxq+SAbk`eon$nt#>ITexkHYL} zTLPd#lQP!mh@hN^aL%(p_m7rF6doqrY)rCS+qHd$N(DHch3?4M3kDN?I-dxY77^?) z>C@BYvi(8Ox)3PScmvGIkP4%&^eh*QJ~;Qu!uyKq`hl=I7ufX99e>PWCU!6kR<|K6 zJFwDLH>yoo$O-gu#^o{*>x&VA2pHUA%d_vH%VxD!SiMTvAE)7I+Nb`*-?TR?NuK%( zvJ^0E8xP0#$gs-QHZy@{p-lH`(&>|}y)mB&vGJJ9T<^YG zX7|@EJj`s3R(`TG)dKY?@76*mVQ>e=+r%SV#Sd4|P3LIkWO!fqeDy+7AnVG+>{n|% z2+E?hti@N*Jp?5F%>cXh=4wsM?{k1I&|2c#=lE?Fu28S>E<;qlCTlVpXNUhi;EQp4Z2^LEEenZjLQuZBip{YeC+ao6PKXLBb4yD3Aj=)p_vMe}jP0$q zmFZ*Tk-J#%;k-KibyaFU2uZij^etk|YX-OHy5yzq-1h~c#puIdQgShlCHeC9c{C^PW{K+QD z@&J_{i$`bRmDdHSe8*Vm&<>YgWyWsdKQ4Ow+G1r)PaTQyoDLQ{Jib&pUV{`Bh!@;} zsT08*kqb?2;Yj}U{x(Yc#2NHvw6Zwp@&3PQ@HZpop9pSOC!PsQ8&Ww7U;nwym&^WJ z`hmDmzT@yka6|?*g|~f`Y8YQmKDLy{5ThdRz1+D*o)f_%D8t&#K~#GQ+$=&d{NE$O zPBmN{0>1Lv83^LNB`bmyLR96C{Z*fs64N*{=mWpAK?;IwAA7T7hptU#O z3^0~CD&rvIxC8boQJ)}f2dF?5u{~ig<7b|P=5X`v2*A+6r$3wf-W}ZWum8VqBYiF| as^$$^NY%W=1@2-&5<_IS97czr1BM~Dy;R(SG2a&i*b_>-#+ru}D^wUf4o?2~2J zwU$%IBzD?^lq67W4mX%%C(!bZ0FjT4_OoUjd|GD=3gu+p+a}dadK>u^)VB|2b z!})_H{r+7-oM38Mr)g}q!2jOy>j}(;h7kYHwSu0*JSC?-iT=NL5HWpV|L0{tu#<>i zk0__^N0I-%LoIuU_urQxlPJ)Lm@7^&C?x+K5t!WkKQjY={y(2Q-IhL=gxo+2lwh&& z1oC!MZHcr(nAwBjcZtGW{&P zs`8G-pEn3w>ydKxgQ&kykBL-I;6EvKRDp`ul@l zQSc&IyWbP;q$8{pi16+3j`3K4lSf+f#pw9^y*5tpibKGAn+*C0-YYv}tmQ)bX`;v~ zpn-idGkN9N{kJ_2X)9GD7U$|YiH>7Sr4W(|%S0R$0&XHykz?f2m%3#|aE;8H^yM|) z6K_N66qB=Y%F~SJH+$ED<(YVg*G%Sj<>u$j=y{^H)_*s@-1WSyAQdrS!+ab@b!nmY z_Hd%m(0;SV`6DxWFRPpJx9^C;*=8r$%K6Vbwmu}6@pyzHDFt;GimRFfV=b0T-ce+-Y zm4Q28)-}5#l@8M;jds;E&pUS14+DS?DE^FD1{+$ZhgMxCdjAO zOWRfo9>|pth^e(lmd)Yd3`H9m<&XA9ZL679CnfA&eQ?m(;D*`JWR8*Y10&0IU7L?g zM&+E78>oQDBor*j@EPG2x>u({(|_R`wDfz|rtK(+;MTJW(DXV-t>y`v{=RvRS-+TO z@xN3fEkAs0vMai1a{hkKrgJugwho&2+|FrvwtGQCa}P}q(q`%^vMbIU`{4b@&i9X3 zR%1}d?ouaT_{8%v#svN(H?^_>Q3to4`h764(}Q5HMB}a1{NsMA$<@%pMebN7g2iKA z9`v%Xe6EHycFj$_!%d>vy@5FK+c#@Thx_bwrb$L)pD3j3zJ(u9tn*2Gc^PhXIIxpr zN`_ z%=U}^`+)nNQdM71g~@gNe6-tAxpok||BBQj8H*EztJ;bJ*u>R2n*&zF=+r zril0WXD;%(b?Epkhi9XQC;KUdJkFb9rc3-Bw>iVsc%G|AeX2!r3P0(WZ$3P^EFV!2 zy+%o)U-0&QE zRg6|Mal=@(Re&UXZ}6XX%gI{U9h$-oTN!R zX3lmipmnO5YvV^hq^Kkv#&z$$TZ;!aCmpIY9JlqwqJ16yE+YDbQ)|8Jv&GUpu7efq zGF;n5mWL%?__P;+4xDyQ9m24=eD?DX8HeHbzNeS5@B3paUKF@|pAgz4f)ZjREPeKF z?;ZsU-DRa{U}Yw{eb-!b5hdPpMO`E3vn(ac$d6_)IhB_@fF9vx5Tq68%qT znEOLK!v|!4wr!Jx^{c7Xa2M0Zy;$u!danuTjJziq5Y@{pFHGu>Dpj>^PZo*t(a)DD zB}1CQz8#OFP-3W?9SUn-vpe5)hL(_}=||Ps6-JTes$V(1oa1?&9C$h$GEA)JOgPPX zo_aBryr~N)0&>gfN57svg6Bas+%O?qXb$T$oh;t)?p!6dV^=0@#zEynMK7YkS~(}w zyeTWnA}ARd)f^V>27TQ74x&@jj{740$kxB}d5d{`XjyH~R11HTLSoO`a6>jHf00WD zm99l7*gA@NBgBUk{c~SnuE`aNO%y4E$zX~iRri$Dn6DCPlSd>RYGn+!i_QyVj|;?U zd}1=T+|s5kl=;X-OxJ@&sB1h6$KJy23D{h&-rZE2vK1j6XI2Cm&a&sN>@HL_*0oc% z`LtJ)tiy(!7_j1|*sKXq2z{|o-m*sU31V?V1z;8#N9J}Nw}(PC`6%l(M|%n7MK*NaOdE%~B=!2bU5(_gv~gWJ){=ww|N_P@0kFi(}f?qj>s zZ~q>@w1vZ+E^WESDD;P}CF$Jz{C3`?vE;5>9-2!^z?|t6rPKJSqN$M&?x_4sunxgETG24Bx$oL*FOIh^5=!>A2NNKY)t-z)5({84D(`t)v$6wMcJC_Lf+n{Db z(*ko6K{CvH81CB2MSiaUtmZS3_kFG>tq3odGhqJLpR{bb#c8np>)ebT9b!XJ8P6!TFLu6X>KU{6%y0ViV8^c}#f%7E+S)h` z>F$EH@)nc_z2Cx5x>U*V&*pOr+Zm|Y2`=Zl?9T4pr0v(~>GGDi zh;|GoCgSd>mW?+@w*sPQC>3;cN?!uw2&KOt*hED{uGOu4)WKY*sSws>*(4StLFwwY z=`vU_ehQ|ZW#E=BH2Lx~Bdxc%P1>5hL==j?MBA6q&(HF0QAYPbK6D@zGK5f7(I$G>5HJa%v4GHmYrp5|omte|306f0M0>tzq3xDx*d^&Uh#GJSzQKBTk{8 zJriM%wU5ex$eGna(rzZ|cU&do-&8eecD;993z>LS++3JWV}48jzFWK=%ur(aY>6^n zE9f;vN3Cgh2$doK{`>S_$}FEtYaM3~f#5anP<8K;5lOTYFMOMXfqp_5hcLE?;dKw*)7yt4=ca^h={s z0XarY#^GGds@*5<^5ACkkBF_{w>S~PD({-#R_-;T2Htx~p67w#LcG6DHknH;87gCc zj#f*p#PIq1x{=#!p0+=--fiu7KYa$Rk(p0_efR_J3{iho_D4Tu?*2V0tV*B?h&4099C+nu+ldSXg`gG#(Rqxehb z{B)Z2lG<$hb3SJyof*huDG5+PVcd_2<-hQt*FJ>p-!^Ng92ghVW8)a+iOW81#zf|S zUw!n~IjOW(bZD^~yvMjg{l3Ak-vqJpDnYusso|}h^ggSJzG0SU@qgF<1<+0~v5=ur zsBKR^{-e~dV5i&Y9@RrmZ| zfI84`{1$4w)ue+A8Y0Q@TCqwuV3cJ8E?EuEFc#Ufz|U|=#r93=0ewp@o)wY>1SF3& z0lZb}1WHZeza7UP%B~*-oJd5#j{K)DAy0)0l8Wc`n+4+Ef9^CPD5f-No`wFm2O%B; z3dH!|&}CWwz0)NJ1m&>+iPtIqT~d@nhVfgd2G{?4#}@RPZs7Hi91Qy#!d@%5rMP&G z{N*CQy5A6b?RtWs&zQx&o6dDSPztJoA4~@hqO3mHcR4e_e5R#b>MI~bQZEyh%O#xN zZXl*8g&dA+RdB3+DVnPAXuEfLBW}J?WQsFmFt$I!to8znS|3vqxv1ei&VyCDwt!%| zR`G^mxY(#nFV$ZJj^8?ww20E6QX^_Q;z6XYSGUoHjXnI~YsloEr3fktOm@0?15Njv z$d!`l8L5KAvOZ0^kttW}_o#Kq#6=$L9Q1TIv`3=Th6H?bxh|V=-vR;-5;bbu2);VT zSVka~9v1%i!A8GQ1uZ5raQ3h+AJuxMQ}6Ib+C1+N&6S|Hn@kQ@)tiK-$O&Ptq9RCl zmh^dwjVSfy-R|T!#!P%$B*=A>Vrmq;MY}DHZOGG5!KJsRB;jg-);9j3ov$8$1vr!kB z-{Fk?8$I4F9Et$Lx=w`FwPFeJXx;rtgzE!|+pzug2VUMpZ{?H}#VG}ahXvXjSy@Ai zQC0mjmnH{=+|O&ajnr{S(#33qt5FY_MB9?hgoNm+Y}*7sztwbq5Xi?eLrmY}4rs5< z6QzTHW(YYq>qX!i2vOxVOYt~MPr((g1hLF4^N)C28bPf*PakK#v+g?SwN)bhj!Hvv zyOeIDRo>$zeXZ_#+l6dAyF=U%OeaGZ`vO=cc_LtspU!I?y)hNnZsKRRot-^U4I3V&HDR!i$ty6y$*p0tYj8gv^R69;A5qQAII^%(auvIKzu7cU6} zIH|yil56>{=(Oz-B|cR@br+(ZIs*IUjKV_gB&OSp{uH6dQPbuIM^k}MdhQ4e9h@x@ zDcou9b%lw>*B7Re#}0XiT~`)D(v~FTpS%*@7203j7z+o*WKU60)6jjoZRNODZm3Xc zXvz9@Ff1BVnm2hIt5dN&oLtcpe*uGg`a6sRgSdjUF8jh3k-xon@S=k`-AavML8i)a z2yzuwpVujZ5IsawfURb1noFB|O6XLKL0ue%+c<_w;4n5l`i+A@*5q+Jb=d1(7R7i_ zdt`L!gSP^)wn%a!KdbYW5;_`CzCmRDddR!GG&AGiWeufo!Y^?V!WKN(3CYnMPQ#*^ zT_+-i-PVjnG{uP04rVRF^3lp|Hd#S5aHc^AGzTAYa?RAtve#GH#ime?o4tQgz_dlA zejYPWXUbUoAuwOKHhdMGM6e>gKrgZVYuZ6P$S5uzdY;wySE9rny#9}AZokyj^!>-# z7tCkr&~D@2Zajgk+X=H9-joKi;&P?LZFMdMAm;VO0VYSVUSshE(;gYkz>Q7hUb}#p zl@n91Q+Da=gy%3NJ&K(eOFDmJ85WIn3N;$q%zOMafo%0~uWKRK(`w#uf6U&&Aw@J# zZOWQsy}8jO?ER1E%5}?Bu%66+KH&+#m~9uYXQJYfej+2TY!A!0R0z3vL`g3sAY%=n z!~AYZTwJP-f#>o@E&f-EW^_7nKu zKVM5{WR``2uOtkFxJ^6eau!uP9(2ks?i>Z#e@7(+%iHjXvIKfehS+GBg=u9vBD0Wf zNQ-S#ED3$pPAT5cdeb^Zj`xe!;iQ>PSJU{Gl>zfcYJ0MjK$-XPO-zc(e3`KGCEfJ2 zx^nx%We>iF=Lswp90m*HCS{^vSpT4&U&%0@VP2u7xJtVDi{$qO<_ZEs*W(`p1rQ(Q z8i;fvA#+eIyMgBJXaxvSgHi!#l*j|SMgy&c4~!^Y0iarCTvmn*T#H`bYr@)qg0eCL zBAW?Osj%IuuY}(zY#Y@(0}=%X+5%(s{aLB3-KnSBG-oXdLPDf3hV2F}YA1xckMxtE z3EZ}0cM1*?$9Bj3(i*1XA;|mt`>g7OxMw;Y1;ZKd-cKiyC|sgW4cj+}D<{R2djUlh zx<07ZS71XMMTQFc5<`+sD|&3#R&5$_*Fdbg$o|a2DqJhG#*05G1fS)siDJ^wP&4(h z&XNB57tInjZ&JA+TzVqIZiaWbBVCk)v_#y1bbb!|0%G;aQ|(GVnzF9jWu~UWZ@o~?C&mO<;OVFDTmO9tsbx>Ad6L=1gAn_3 zLF}K02dWWquKVwb(LuEvYRQi)GQva-7_HS+$p3A+=y{_sc&;k#S zxlGuT;l6GmLn+wSO1SY2)e72nMX(3fkHv&>yxczwRDEe|STuqO(Tr#mAFOQNU0nQ{ z%$<9dccStC#1f5)9c#-IWHD4=g-{39DHUzlVEJ9zB%^~lz(rYOesaB;5Qa=akO)SM z1ugR&B4HJjLBX730g4U)N-J_)u2NQrKSCWL?}lMjA8K_k_Og?uBzF_h=k(^R;H#P_ zB6ktO634?e|HE;tZROma!XNWkORL;R9ZFI1SWV(oe0?H+B-#{Gzwa;Ttkcrk^XjO_ zsrl8U65A?^FmN4BZN)O;1gLEWvcS$INgt-_Nl#)PWlr`L8}&K z-YxyDY^CbgGqb)y`t`)%`0&{%-r0gJ(o-gxJ@2n7>t4JQ#XD9CoY{(>((@{j8R;LG z<+zJ!3G=_#Ih}gcSj`!@H#dGMY0tqqRP!kNp4F@_u5WE=YvzI_QqOH(7(Lqdw5Zb% zu&tqeNc0kiM3cI$;p-7KQn25a1@(0yO%2yjv$p;c0J({$z&IHyG*gK+0)SaVfmP}> zLaawaR2DW5e!j~&NmZ7wAIj3QDRa52e>?^eJTbn|Da*5G<|9f=Nh!C|d(y(bv|(gB zoGYQS-muwy#B#u^h^(d`QKj?+f?Qu9kVj;hYyKCW_t!uVol;NLNahzZqf&Pe4h6 zA(sZI(3LRkOQ>s)%e^gZPJT}Q!R;6C|G>c$t)h*{QhPKwC@UeQNmODvlf@roiAF&A z$~8zrK_e!Bh0L(YbKpRv0;nha(O)0mIav6TEqx92O#Vrl!URZq57~Mq&tU~I3BKSEwVk?O-5^uU)G)lw492(h;6w~}glI9* z0c{lXTL3Tpa!sXeYzWG%u!_oEW#K-5i;X_#uSJvD_wRQ91$XPrbFBy_a`7{4sd+ebt34z3chTo`QHLsi;-LET}>UQJr zdjTW|r&(nlQ&S4t=Y9?>oG+FV3eUpYl0v6|X0KL7>SDkF=;$ODf`UFcz8+q7u>?bc zx&@+Pp$Vqa1+UF(tPnZyBx!CR-|MnSr8}G-U5=J@i&#gV#^UxfmQUIci|jOxeB;bt z4W=suO61og)Vel=83c_-^C5s5BWcre|HAduO9g4q4BSu`_pjXmy1J3<`UIgFa9@$= z#5AY>J2t+pNpUyvU_yV!OM=&Sa1@fmoGRA+1~MrKt?5k`T_G-8Lp5>RagST*v{PUu znpBqx9tCQ=zR~uPhSYGsKAJd+ZVzp5Oq4C}%K6Q=g6gc7FGKfap!2H*$e@&=mp2z~K>fvE~+qVpOJV#5#n{1dnyTU2v= z8bk=Oe(69LpAHivDU=L05fNB??3ewq@5brEm%DaHXJ$0k5bHMu*bHv@fJNmt{2*g@ z*lH{jM(lgW+pkhE9li+PsaV5<`c>gvwW--YD&!70XO2I5Hif0j?UF2CkE@W+=mK_ z=)Qe-z_Ymc+5|Jp`1P3Jz@SMA>4Qx^p8=LWovh?gwe#H^?PsnE4uG)$Yy=ji_%ZjA zjRy$smMCPJ+w{TZWaaxy6ujITf;BP1UMR5EXr8Zyv3^wkfV=UprQI|&=i)zm_ZHf` z#}@~x8A3qHJ+}{>2;`An-QFk&rF4tf?cZ;pjDRH&Lz4&@_Dnz{?8W-^-CEe~Pv@GR zfA~?|0Th?DD!_mD0_5Wdo#ZJ4I&=%Wq)q`7sv<`>wlI%JgytfIpMO0Yoy1GjAtH59nIh z6AdlhX=UT!vF#AA76S(T0yqZ?m33Pfctgfu#t6+4G2zZ9OM~YX-wX3bY$VSx9dt_TQq<~4O0Pu~QZin1I5QCa#L`Fl`qj=Erc=pW~z)?M3^*5iD ztD8irsH$x8<3_u@J?TJ)BMF>G4!lOeY)0iTicU>EDz4Vx{3Kgq6^`kj=nOaot{)&P zG*1|%Rzg>}s%p~bJ4dAYS(eP<;P@^AAj6Mh5ZICVyJ2e7c5tk!?&Uqn){~kqh9l}O z2$KQw#w!4Ze(x=WBuPx{&wf}&BCA$j{!yCt1qE3V0L! zt5f!5P81$ycL5RApd0kO%&m^ZSvmzNg-J-Kqg#~#sR0IJl$d44iz1aHE4OXkVUS2- zgJxa_Uold-ipz*!2|z*`k-%Di@??y$6ZzZB+j3-0VQ6@bCnaiVXhS~An*mgE6$J1m zx@(SQOG!{f(l?_Ku_C-*HwNNGC8}KWPb*0V$?gDf)YbFq|G)38TO8nj+fTQWoC}P` z3sfD`M<=CxXI(^vgnj@F2ltJs135Yx2u6(iZXxPzU;bWTvZhv62%5gHZO@@;5MV+5 zub^9`PhKLqpG?Jq!5s~uCXv@6XT^pzfln5&@BkoUA%q9Nl@#&?+jeY!%BZRNy{#x7!zTH) zv=n}R?r?Q`o8R1wmynR4l-!FCOcn|7^LgwVW5oOsd4GxBKc#sMQg#kO50mdn|BPYOTH(#q!#UY-Y`yFAo z(m_~$P(+j69H==55q4|e8UeW~^#>U1=gOW|KiOI?NIbBb@%psdyhvWv7uap;gDGlg`Dd)OXr_q0*H@mUxqK#Hys6`B$A z^5Uzis>ad~y=L_PlXuU^n16^XxbRiKjS;wd=g|uHTruzvcvm+Z>{W9s$H%da88V-g zgcBHH8lROF9fb6jl!1X^PR`_&l?KjS7{&`Nl!b#KQeE61!rr$t8+&8S=ug$ecjw8jxg$(dFf(i0LLf zu%b-BLZ?0tOwvO_-}?XUFl?*u#E81qU1BlTbmZe=4z8YZI5w^QPAY;BJ38w0?b~*4 zNg@0!`%jII62uWT6=JdbcyhWh9u5vVUT$x zGBBYv%cmC?@fxl&G6+=E)PB)$(9@U^LqETsBT>+wga9=#HN2##M(Ps6E|yi}GJ7W` z&rfmaWKAjJuvfLgXR87+U^K>$t9SS>Hzp<~2@<48L$f*|E)Ij`J=1GrpAFQ!7mt28 zuf@VwKdzs{;h9rYQ?cJeE5l?Dw{~`dM+YJR1q=bfHeTm%Stei+u;4t0@I>nB4XhC$ z)8r@s7WQ!TR!a=Xns6X~e*V1^)J_5|aXDJr8ALMi@XR-+tHPjjU;v1I2mR{+-QeKh zV78(Kd;Fq^&afjz>)J(#HAyK@(ZP!Wu@#F@5BMiDIdn*GF85EK%<n6VT+ERGOc>}am>VFBQ5$Fo#J z%4~vPJzzWY4 zwnY;i7(oz79ln%&B0xs_0D{O?G&JA<7!HXxW1gtmQK+OLP{xF!0eRuh-t-5s6|iKn zNlAra2}egqu}KliWqmwxDO9g}RQ;OgJqzi9y0$9^+()D4LcmB(4ZjQugDlU^zRF$z z$essmHiVFA=k=rnH?%__0~6D{-g?^7@^YyS7Ipr=-L3|98-gEA5A<4HCSa2p(F`L~ zEDDu)VrxjGTtLkSU?)ejh4$8(?vp1Vz|Ko{Re@`2YDgW!6VI*z!&yIi)yIwmQ2qHl zxp4w(96}hBCJe)(BLzmmrP{#aWoAaF#jgYg3VfXl7MG=d~L z$2o6Yj&*MQ2#t%iHN6VQtNWY*d3FQ(YY6ZNIXVq=>q3JW?S!Gg;R(g>)0~Lg-70qj{)0CVWKz4iq$YCCoo5I z#z(U9l*hl5C{xjt5sFftv}hq(&V0vMH*tXM%daqRIPpKSs_vvx2~>u3!#yoxpMn}ZNwuz0U#^=TG>OjzW+IuvR-nwiIDhw#9TS2AZD46@YW}c+qAqn1rf6owhn6;3 zv=^Yw9iF7lYjsXq9rtzD}P4sHG+P0o)VcCb;&$ zW?=q=dPbO+ddVMQ=NM0W^D7o8v9$+nY+jzFyVSq2{?@ePD}*f1@T>$M3Vw>dWw_ls z@U>H#2K~P5HgN0_^o7DSSI~{GhM#9p$n<_+`gPBfQH{Np00;?znCRJXi+J;w6l)I@ zY9XgG^4^p>3 zBxCv|&dX21I25&F)a$qH@7ayBvs+TeFwP8`I*=N{5DKDZZM5t4IsGifd>GnA9O9>F zX(G!m*Z&FVXh}X6_v!7@cKnzH1P=5 zGSP7M^hx}R)1No+A?QTDWoW&*4iCEgH%6SOkC`Y=Mv&VV?ygwroU)~6DZWb$lY5Ei ztMx?NYay2`WLm}m)lEl#7v$*2nBmdiGoDUtmW)}~$6ifu3A3n>-3T5U2mDa-h5$$D zMi~66tVha|Cs*N_-#%e^&&t9Aq^Z}U1aJv5N>cffl9D=s4{wS#;hse+~10j<*Zt4QhsH%R7FI&bSs4mF%?JkKUyTHNImTLS+|WQ9ilb<8qn3kFJU|(m%vnRmaYE zfQ#X9TjCb*fYdMMO2F}7N_>$ugrG3&P(Wj%$3hv_oM&*Eh^Lu&kj8fz#U+{Fc(E95 zIEJItzms?n0!LOQ-ck3fnnDgquay)# z=%NHR3^yBSbsFW9tanLW>kwSqH%N5)2`!X+iuge5tiM?f7^qdkl3zry8MX6fx?2be zP}u(b<%wB)%dSOgTe|CA1=<~RS(4(rXy4Q$6IZoDa=85d7xoI};c;ygC!aF>VJ7l( z`HM}hCf~^M!hEzg(G5%cH`-CEebdt#2HfkjCifGS7l1wlOX>+0A_Koq`B zDTGSc7XG6fziIPH;>vpVkB(;4ZbZp&u0tm|OGY#gSL8Dm52UoWjIWyBnTB$47f$Xc za%1l@eVKO~{_%Ge^Q2Ud^P#=^9z|iHs%44Yq#9~=yTZ6rn_nJ`j-TdtKA1-+pI0%J zIM2WFXO2X=%GnKxZw+GdZYXhBeJgMHnvzJ6{~%oA+<&-$89_8?SF&phjEIOx?7&4T z6|Zo?Iu_+ekAdS48K=4F3&TuPh_QyXNMbv=R@8>_-A9*`QRwjmiSlRgUj+VE#w z97?sPpFN^`5cA3!1S%!`rEfH6{D9$yyBvF0bI^96=l6?`)3s6t)?LKVC^kAT^|j|^86zG~_3!v8 zLx9;lj>QYwC?fMbs;jRL(!rx>g^cWlN2=kOzhi)*o1pC(_G==z>-};9SDEyk8Z%;- zotiBySX{NxBfP%dTey7qoKeuq6x9r;4_r5RXktmZ+QaQ^9UswuhYMulW!qgixC`N z4;O9wt{6$ab8s?MSBgawd%ajEtwk7^**c*SI6rq>4T~p?ePlG7^5&wA*49|-^NM+A z!b$6Yzv!PLv~9gy9LkVtlk$(yUVS_~k$>9!3xk_F>uovwgZk~QcQtvjJ)*ABT`3mj zI^G93gp@QPJw2kn^G~F$EuYlXaJKlAz2HL!(W^;($Vml+%lr2a52yRW0^2$B6rJjB zHsmKv+XC3AklyQavg&N4bkRxlKR+qxY(+e@Nqo@R)s0hsBh@PNEmk9$H-YunMQx44 z$Tj!f=+_RkCz{_c)8ch{x>1L7<5r;(cm-;|@92LpRx$aN6N{8|@*pt*^;j&o>yi4dQ?eYP&@+v!@sRH`=Y`Xt$P0p=sr(T%Q73W&GF4vb zTXts~)>_k`-EVQa-Tk%tKXJ{0Q?BXXox(8X^jfH}We{iB;x0O_b#LcfNkfS*=_hX8 zyr3HS*H!)PqEp&$iEhI*tF=C2QC4pc{gUj81#hK$Dor_kL%9FkhfNX;u8d5Y`mwaM zR5dF0EtKn=VDXo8`fLZstygRBNUq9JIZrEcGQ)+>71*^C_F?v^sg^yBsYZeH%AoJq7|&UrW#5vRqjS5_BH@ICH0habeT)ZW|%h(C6)@=FJoR<_eIp5h#cQ zM1_l6!P3HXjc%qu0l!gDU#}W5jE@|`MKh8mhK-FKJB9O133u}1!s~J`pq*&f)A?{> zot_m`xu)Z9D`5a_G=jnx;D06i`{}3HG(4zYhad4trB0zMY7G0zT1e_ z^B{e+2V$3v3jLc6^K9CS+mQ1iyuI@LUaB5zdD_Cw4Mw4kK-W`eRtp=~uG}^*P<_Y2 zDCB8bR&){b^y<$X99-K*&eMI=hc+gmzC|I9AYS#l3S3vc!r%7%4+BZ25$V2R3nGS) z3U#+kFukx)1`F9D;XEl9WL5U9Zx{&Q5}^@yZgsUn?r^Y#=9&9vR-eAlB5bx8i=_%N z=F9FEinC;8Bt@OX{j8-_r2F6WUR&|9M;^b8rD`0BZ9e7W|J3|K*XgE;(BXTH)p5pq zI~ZZ6HI2|VsT6vfDAD<;=q2zLJoqcIf})#-%e30D!seo>BE!VAgf^D>Jn|{6vkmI) zTa-TzXy)l3>OCFGK8b75Ud+Oy>>TQ01_1`7)McawVdjN?=HD8X|J~}%_(BxcKnDct z__DpYeL{zDY^D5Urw#kJC%hYfcLZ(JVLKeO#kNLV->{qg@~3(!&Zsjta*O0uK0CR^ z&iB~y*Hk>}w50qMdsPFS4H(-O=p)OPJU1J>j$gm?ZvLDTJ-FYGDYe;^N;owN-N16q zukGl{uh2caIp;z!hTj*C8P|!nI$I!*No#C}lv84tCV!_j+;A*Wdc%ZbOdG1L2Ze## zMNOEM$2?_NG)WG$TvnK|3ltl>WcW`~vIz!KE@3#Gn0WiY2K2;H`To=^Wou*wO!zcI zMs^;fwC~ONYCRW>-R%+Q+6EY8cUZ-lpX51hFLa_35~AL(s1@=rsOxJ*I#<>iW=l{- zx(6-YHlbhGBc9$JgO9qPa?AUH1o!69!@xLJ&mAsu8@{rvU_sgGJ^9a@h` zo7V=7xjhmp^>b1ta~9L&5BV+o3ED<{i4I2Es`Y7RN3t?GzOVQ7Nr@yT=|iQcfAzT z*^FrH-v>;xRb z7mN?rruG>6jQ4rUx5)MGC@z2J!2X${dGOO;hWKCK@`tjs1^j!7v_O> zVk!SrRU2mOoQ*mrimv>mrW(ZGrRyjb#v9{VG|}|Z2shc8d$D}$P_7X(KAkDNe2Q>}CJOey zEIr0FxlGYC%*)_IyWYTDJCcY))IGhlRx_NgwGW@blKta2o5l9sg#ta8Kk0W?pG&>M zxH2~_)?;krLOjdxOsHtx=cB-7Zq?RC<~OI@bQ3Zu+a>LGsHZNw_@7htSdO&SEh=)4 zk$ftz287p2(D?-Ir>@zXKA^^#P}JDo43GK0 zscJq!x~hF{YD}<7d?$B7h&MQRrWrs__k~Yf5tMl7J04xSYw#Z3#<$N7=0i}JZ{-E@ zs|b5ZLh|y_4I&Nz1ci`9C0~6pO;6+L{=xQBD^!S#rp#rcUBu~~TyZPTpS)7(YFd?~ zRI6kiUD$da&SAEoB-C5U(BC?02Xr!i;mTex6Ic&<;cV&Q_;!r9T1ouog6Y)SxFbh@ zO^h^oMgYWPlJNhB*aGyqSh@!aO0=Ph&3hgWo{A(Q+5v9%?CjJZJ*LS3Y%A_9Q%Tc* zp{lCUAS{Pg5bVpmS20D&D!GwCuT>`*frW3$9{mqa33HB5KU|uXXJYmeha+X7gW2^i zz=^nVUY9^agITy^f_dBCiN$F>e1RVYukB@v`uDnY-|Zp}^=T*eMN(N!3HI-~u_+gw zBK(sanzR8&!PhWN4|#m`%GHe@N(SMT zubwg1Wi9S?FV?Iq@LXk zAfk*mlduOBH-c!~!CMoJqW)1P?9oSN))mLF@_9wQ{v`84fsj$`?fp1~wZJ`&-6l-J zl?}I&_y;C3CX{U$0fi^@+sj|CSoLVk`%_Bj6^OmO?8Z+*xbrJx&q>9%ONNe&QB~#z zhP4ptWc_vwBrBPnPvFxq3}^?p;}cXSuFH!HHYzRgyC@7d9L%=>qU+E-EG$Lr4xBGw z=^<1&`k9Z`9=vM1CO2qgm#oDc@TH-wkKMO12t;w?M#iW_pl8}_-}LcpbW~PFRrLoA z@5_(CP ANGKTqH*)ouPLao2fI`sN){AcWz7}q-ck&M`HK?$E`|EFGzTP{ZJwlVE z(Hm98=irpce6y~$8M;|kc7magO$nLA2SCmCYxrXle{S@YPx<7XcSA^Sj*lnYzX_MM zrAIF90k*1V!_>5zA?3T6sL(6TZ(r(QGE@zK=d|yE0bal2;=hKqc^v>3&*hKjniuk; zUS)Rg%6YTclY)TfGTbXSwW$m(gCZrw@fWj5BXt!ad3bmjK>Yskc0xVC&XOau(~T+${-pXP58NYzVnH$o+{jzxR@IUZ+@90vlF8KaDe?J zPNY8Ejg(Amz2QpQiXQS0e1{#*dL4ouguI?Ye^HhU-jU3yG>3mVzYakmlP*ZD>~I1v z`<~BpH>5OcQYA0OqWT{!yw_mjvC@Jah44T0)3 zm$;nZ)6IMPCygSdG0wJ?8c~fWNm9+A>8UCI@$vDh4w~SQ9@xETV8WA@K-f4T1CWB? z4Xlg6t=gXmSnfmIbVV|O<@zxYic!t}=(j}98qub_JW}iBhldBYH7qnR3^~M(z2(b2akwo?n%DyB`^#O z0Y1ng$M~iRhcKGZHF4@mlux#KNiQbY__Dq}zgs_2?W1L6Edx*ocbS3OL97A-@9N6*j*5zFSV<;(}QB1oxJF7-i2trSjutoahCSutch}GKme%IDvOF- zG97GncyN%XTtAM;h=_=VwKX`?NJMF)z8M|{btStm!P|UypZ>Kmq{LA=bjhEZ}c;mH2 z+IJg>FD8J{e_Rx%_(vHvAWASvW&WNcoI` z4)n>+q#qQ7DS(1DJK^NyL~+qA3QYR?l@JD{8L59CCRfw^yB^VvS_0*K5!nawS5%#% z0RW@Q`~hwZ*bJF4ydJ<(5F|q9rHG}#s{+5%3eqt3k9lz{tv2@i3;O}PvXQEWPgImD zbTYvdDw@XoCz&GPksqhd04_8}$ajqy;DLu&iM!bw*{;c(#ML<8f@)Z+?=lyx8)6$H zSgirldk+gAg2@E?%2I%l6%vG45b8LtDQh1B=WXh5C<*M5>FSn`)3}BP2jMkKB2!*V z7&>4D7C`I~6^U?{BK`<&;e~^!BnXsLYk6LO^gdA=3=QM@xwE|;L=O-v+FDw~(2FUO z#Ge4k;^K`2AdGv3vUq`lp^XmA(S2yWY7z=baYCeK0)SG5DeGm=5W{|+u?D; z!g@si&k+^>DGaOUI3m`m@gbfkuAj378yiMITqns;KI-c7+rgr9Q1D6^H<$n4-vvO= zda}27#y@t81JH^_Q_<$N*K!8GK9@HU$rru!O0V&Y9`Ydo?4>0yRtUhK5E24ZFu#O^ zNuyEfL7Ww`-Ko@U%K#ST+_7ha&H$K&W!l+V_eic*%hOna`JN)2wXAFbV;3Ol`a-yV zqN0^1nv;<&+5W6}BK?#i`JZq4CrSJx>8%hFfWs>4A@7@0Qpm|DBXAf3{t?D>4xXN^ zIFVzl*|N8xod^l_0H3DO`m*Se2^kzhX~$A7GMiYWFRgbS&q21&AE8kL)xgMX#E>%S z`(onB#nBK_S^<<%waZKGH4lYiJ@egr=gc%drdnEtxmEXWmaZ{U9`eBUvL%Eqfa(Op zUQ(?)MMLv&jZ=O~>BD1=pz?1CUWiv>-lTn-=B&7zqML{{g=EQWdR*E2KcReBoQ0RcVMnYXZG>1h7IaSMhEs{+$OmJI^|>bRtC zuYL-2j|j=lhKQsDzjcH%%}$NujkLUm7|vU-$A>5xlr?p0A$w5GMPYL@w`C^=z+M*D zc*sx%Q%=qCN1tFO;C_+)%yf@ECaLd-?1|{eqO7Qt5geY!cKv8YC|Ip=gBc`6|G5U> z@JXOGg0GYJ&=@sxgwT5&?e+k;S6s) z=(n$1^pC3^?4T|Vk}6gKna;)|XEd;3S|tI8AEuAN1ejfv41qnsXHX+%sb9P9wdYf` zQf3$##EyTld~w3j#sKKT3#wB4zD`k}VY+x?%Ze1r4ZiffnMF?j%*Wt5b@S{m^0P7- zO>-v*$Je_nUoA}`RX$=k4P_%!K81yjY!tW}hzSNJZdqU_p+BQ?MLBhRXu5hZFb^(7oy zTl);J235W%n$n|y;m~QIc>mUVQ0!fi=Mxl@;`?~q&D+wiRIjY)$3Ll5Hyc1j#To;- za<@{FV)VJYOedDfPn8t%QsR?P6>SRD&}?z~5x$SP=KSvYtNhc&zFwQ70aq@eQc8Nb z6%$R*PCYk5x%z3MQp#R?h(-1`f@~!3c{Bcrh}}f&4~^en{ZF`>-jf>0~2RYms&3y-bU9rzYJXlW_iZPWTm_ z`zsI3l;UbL^o-qeXcaL^pXvL{z@o8zGT$N2n|FaF;L zGovC~;d;aWkGi-1s^ba1MZ*aMOM<(*YjAgWcXxN!1a}V(!5xA-1P$))?(Y7EeC~I> zd+&Pxz+0TfFX!}h_w;mC*RH*5%$#7>JhE|oSFL4ytL3ck+WI;^w+r0^Ya_9~OQ#L` zcHHH;ploV5vxu3YP@rx?CO5Y^E<$#w(dRrjEnbKC(2=$O=Z$Nj+(k!bby}hN4{-Er zfmGv#781)YtNzg7^&%k{%~Zx|30?6t9f(k$ydZ{O5B?Pwt2cw!QgAI>gD`jN`nZCB z`K9#hP#6*n5{@sMIVSWyqFVWFLeoVJ+EXS=kLLpaeYkELh$6jbOcHOJydOJyeDh7z->Pq${8+@S> zEqdNhI?{R;hr~YjOER^^2uMT)S*6N?Dw^d0jNT(lXFtX;HqO0WzLD9j`;!M=cM} z!9j`To7|3W#+&$1@HqZ;+y4IZY2C<Xe4OjAAZygiN*~k;)nq!i63{vUa`w;fed}@hWk4SRlWSm zcV=Ix`bU+qNj@+U&5MUhFZfMOz_Sey;&VQ-xTab@ZmC-E;^mf6rpk6WR;E9UZ?zF; z3;qwY`61w}oG6!=wP^eSLKvH5VxzxHfs}nFZk^7z61wfpV!8$f@}Y5C^kcx*w11W*7`>a|-i03M%+4ZdR>A2u{s zhv|4N4+gNYmtO({DaUgjcfOhD&@-6#SHBZqNfRaCGF78D^MTN<&$54-<#Mo>_fxT! z@GtJS&6!5MBTO+NjrW+w|Lr9gmC0qF4k#Id?)@IFS}MDgg-vO*Ba=2CAz})FE(qwE z*n)m|MsKaRi`-c7c}n6D_X!e(YFr0m571>h&cwbdL15SY24D3S*t%R9?scIM;-$GhXO}l zp-rCNNgJJn;4+`3bMl9@Bm;gpuV^~=MVerhNmpDUlhPu-3$EXBIw0OW8hNU^JxT7q zF>EiCy2|Y8VbUH5dZ$0*P-kltttLoAljwwFAO(hEcqUo{kvcvYETY*_eIHYl z(}{S#_@d{?f({H4?CWLqdrF#EPXu&=iby)?dtx!&6A{|g6VW!v@=huDgMj{AOj4^N zAxQKaI=P6|M2ohp0rwX-af}`)BO?PH>&i86=U_8XH1|Uml*r~89ORj?M!hL^__VG@OgteDPT{6eml1Zr8bpgGo+jq z{4fc={|c@6#?5TrZod)|dU}nq5b8 z33z@XwREH(%-LIWdU_{h{k9GPL&2DDGF|8bAB=M)BC8sxT;ldKu-xUAR|Nb=afeP@ePI3hD zQjeh<+^tzG21hVXX08NHt8K2VFyHItypK#mPhXvbb|Kw1byw0WDOy*-DD^hL0xD3P zoTF9bzlT)~q}tx{frNBn{wy^-TrS2kQW}Or>hlwoX!m~?0jeFKaOgJI51$VOuQ_|L z)Mv({l@u2=i~PU0#}AwfdRk@P{z^GNv+%QXYb^&(R%=d=9Lv}oa6I`Z;`le@W>UL) znC5zE=_7O+l-+_7ge-@=LiHng>+)I56Gd^)|2i}fSRaIi#b@A)h?}&6Ws+AgL+C&p z^Z6Y_7HJypF7@BA*+H;MJ#}gIiR*b54;6yl%{IO&A}~B}v5bFh22`YAWlD^CgK9>I z^@KqHDGnVS{oLXaAPdiVjt8kGO#qrnWUSl!-&jU-}ndb*IeH}|$_Ed?_(iewVC|FaPxST551%Pjrr$|^*E zfKPi`mX8D}kEb+-QBThxut5jg#_BcWI{z0R)_IAFk}@XCHq%d8{^R14B)xfQ&qDmkUBobIK1t=aTIWApo`Aql&~&4|Vs{o3z93VqhF_9u`+*Yony zN}CL=B~R%Rmt}p!#fiPt*%p zB%kayxy}Hs7Q*N1%Ie|a(N*6FuvAG%W?{~eQDMF%C-F(nvAtGZf$MOxVWYjp3tJ$y zm#1CYtzxiO6_fmtdIe(W=alo$&}!fP%KF%Sp4muANx#>#($IVfN261@TAg+2gM|q} zlak~saMMx!T3t!$?~teeIX9|_b3o;1!A1m%`g~5$-yE1IuArxJx#R$%9)QYh0P61U zHnXw$y4dy34(429<1hZs5`ZSAhYMa%0B|sR{c}gY*1E`MOSgF>beDyoAyCI3HXjeg z$F_f9Pbn@U7Z=R;m?3|QRhDFo&FW6HnpiKS4-G=Sit~epuC1>(u(KPlG;V5YDk>?t z{k^WcgrEPn6iq6)GJ4;{S zbZ7cYOXRBLMDPd^JqHJv|3iw#juKCrpo+Tw9Im?cz1&i+AnKjWDTc2C%+NqMZYRbs z3hF)Fc+UE|{ySJlShb&TnOag@-A{TiD-2La^yQG`+m{AI#QSAJNy*4`ZPXRi>axYHUSthQrlz{QiP_x>BQNV|A$s>Atfa-Dtg5YjJC-@aMDVzml)m_fKx6= z0HW4Ex-=q?306OQ?IT(|Obt*%FF?fLZhA^H?4h?Gp9PBVB@<6#*<^($0>&Y>;IClo#l_3Y`#m_Vu1~WUff_-N&@{q z61MFBA4%AZBe;E~))3w$Y=9y*REFcbNsJ%eyM)bKHs#XWQIPfVDM)&Og(VEwTOPoz zpZKHbr(F@u&uLHt9l-NPceLTV1?p@GjHPfpg@MXNCXwBXOr{$jSrZ; zsPFV|K&dv6UUfnGEQCq(I5T>l6uOYWj?G_yKK z-Z={fh#~J}?d`JUcyL6K>^y2ZedmK;gr#+=aKq*wq4$AeDWN)^K`!d4e9F?X`HQx7 zw;Cmm3u=fVaX_<#&g2XrIgp)AkEOmad-1KffwN&m`e~my)l3>hrJ}4} zurWk4tuo|935?be(8@51+OuukG z(ct_0fzOuJ1ktP4)qkQWepW<27wn8D*5d~~Tk8FhF^!Mp*DJ;pCaBu&Bumv1v1A;(33z3knJVwtRWp;o;%<%nU@~l(`O@6$Z2# zV0c1>IWtFCKKU~;C)PZCSMH-{k)RT`)AqUP3pwKe5*o*1p&<#Yt2%PoLJ`pz;+^zB zFYgiZbO|^R2rnc^^O_DxP?yn!7FmH6DhTWj* zYi`plQI(lEMZK0&YB6SJiOPo7m8&M9O?*UBKx#dLKjAAGGC2Z~u?XTg4Y1rRE?>S7 zfwB6(OOlCKgViS?>ob_oCgSv{wLtl8Z8%$7TYsuAxPUt)0~nS4T0=8bXg5T1^1(lV zGP(0AK|fk@=<0mqXe@I66=D!0zqJ)RB_#!6jR?ayA-}yH*V@|JjtyIWWoQ@};s7wj zIIi2W6!08Ey8#re&%F6WLqBX^`jinWd+fmmM@Rc|gu~?&6_M)rFwoIEFShz802SNs z-wdj;{|bUBf><&Zg=+wJ2!d|}+{eV~WN@zNB1miIt$1wp7K~a)g~e2`rn;KRpOJ&3 zr1u+;o{DNL`}2qYFBKUXB%lhDhfNQmN*$RG&RELoSt3$rv)+1`udQ~vg@FUE`W;Cv zJH1clQ^znBFkBC!@wsmSm0r7G*CkmZ5_8K{BqbE-VvD3i_=roynEsoTsv`PA*&*|x z-VW=5cwP|Eb1jlOzfXo5-G>D3pm8yh08|MIIQ4=S>2f_T9;6`3Mia=`6AcG#jB|&j zva8rcrGu>mJ(}%5agoFNiZGNO7CWmX; zshqrREw6`qS-nuo=M&!6Ah2uw{@X&kJgWMe-bt4vVbH+)f$bPq@8w;_G^C=kS3&gZ1wQtA z3vqHCIOhWedt$gB>#E#~zVb%Kb#wW3`R+R)6tyy`7MDwD(w|L)@t3pJ=FA{BLGiB{~(yIA+3ypBb9^BP;M`gLq8?MoW zyrjawB>r6r?+#hxzDp`sEHu#ZcK)f1Q<4<+I^O7Jghux%W>*n$EH+M$Mn+av6cD{9 z(Q1c1KR*|VSQ?qzOG?5TfeWV_=OU4Rbi19SGc&PnyCwYVrm}&dy20tX5*Xg^Vo*sQ zgR*3&4`H`Ga036b$@l5eg(sv1&kO8?2%CRm{eoBWJ-y*{S94*4ACqX^-{E!D-FRG5 z<2->Ks_3wHSJBPBS1#4eZEkK}R8%w$F2T>wKMfL2LdWB+5aF7R+Y$qpSbOE&guuWp zZbwF=sW+TtMmE6fRP0zQrJetMLPWV)z6D?3L0z4Psj8h6EsD1qp41k-wWQmIa%Zz9 z?jLt8`}K<$ssA*ml0*Sm86e-%dj&_$qwd78m^K=D)WBAh4266|I@6-i-{rmEsHrPX zi>ux*kl&TXcY>y%ph$Hri^voUBVAwI-X=D(5bX&~Aes{P7xgFo{WhVyB{xP)`*vJt zKloSXc?xGu(DcJKA9BELBVFdm&i3 z9%dS5S`fXi6klu*ErjYcok)~W3 zu*{S}qoLxhja&GD1RoCbu-PDr|5?lk<9tWecDfB8de#l&%R5n5Cl&@-Rh`g*_-hyx zGKP38uy-lEZu&X3)b&(fiL7zeV22v4NOn1Rxdt!?NLPIli%Jno>UzD(o#%Dl;Yz-1Mo2&MR&$>G%H2%2Q9a# zqJiHb`g5kle|E8|Ibe_kU9cia{r91mrkytd!N2}l1qTyrh;8?BoE0&7F5{WPcO!s4 zBOjcUrCEN_A&JXF>rZGvlA4g3S^|xHp&uJOW~BSg<%~CiUlV0?B|SQ8?GdN7TtuG7 z=SiFGn=|ie;}%$*gef%7+X&odXys~dWSH4aV%Z-uo*P#+?dbr#Wy(Ft^H5Hn@OIsm zz~ID~g~tb51MjP?d&4saultXtgk$*zISfhedqR8xpHes(ru8f|*i z`JtH)e<)(&tQWc|D(DH%OGuNWb$b#Hz_)*e#>L%e+Pf@jx>=~^rb0;cUYHh)soJ)o zmd;F%zIp6^t^h6G5@CREHIut=a)+9ERl zwnzaNKyi?bvM?q-WOi7gDS=L>HP8I z-Nq;Crh>aPykY$R_dLKIio{3W(t zFBZ=xHW(tQt(~#Ubs$C46NvYMZKE{BWtdD+xMT0N6NR@PD0pJ=cN+?KiNKI#rt#pG z&M>!rvsojuVtGIFw&|RsOy;BuFDuT=C1LODFPYDSBh%QXWJ`C3{i8&B=`8+;PsB21 znNj6`EbQ1@Kedcr{em9vCuYLa`3E7K=o6RP5)Ze1J{Su5dw=gHWCXbWt-SjRF^tV% zhrXSxZeOkWh>qtA1g`c{+~~I*w!x^jvdA1IT{jdJ)WwTxR8k@=mVS9I#u@?B^L53> zyA0eB+uruqTsF@V2u5}#??=jcvgl37H6Ok!O$_o@9My6a85$`D68tVDuUMl2w<2Qq z9E9J$2CFxjfm#Q*97_P4NKXd3lve8r{X8K#&Wu1F`^gf?pK!B>MsBbcAz|dqTqMgH zyQ9_7qjtMw2rc&|5RZ>AYrC8mYZNVOvL}dJmR;UuPBuIa<>zMo(O73^>7C^XTe$`L z(pjtQP{YMmgM7kpu`5xFctAe5DlDm#aEuskK@-xt(&+NSg$w}Sgd@TO{TaQVRx0{1 zFfim};rkaKA3Xrch(5m$1|`hT%^KY%afsq4Hq=~}3zE^qpSy-nQ3qSH%NHH? zQ-Rf8f;2?0(<*=6#7x6eK?>&mW2aLhYO_{Pj65C%vbFL4FZXwq&zvsDwzDowvo`C# zj->eBbUvbg;N7+kl=n zDwkuxh*eloMU^!Nhrd-hHCjbN`FKQO*6&y;B}*W6_UGI8KB7~KK$M?A0aTk5oq}WY z9v^1y>686FMK~|WhE;pssLw)Zg}pV@xUxup2M1vqJ?kP02AB&T047Wd9`C9&D2NOY za|Z#!^p2nDreW!RWnF5uW(XD*7CAm_p=)7QsN^k}(b7f+MO-?FQnpITCNscm%WC69LcgJE#V*GwVizy9F zOvrbz{B7`cA7>O6wySa3u_hH4*9rKaCFvrpvo4L$npKeH&ut$ zYX`;DVh6|9eDRK0wwLW;Ip2b8Pkcw}$)WA?+r!&Q2DBUYOyXH|UXsTIZJkCqjeBn& ztR58SClEB!v%oqSGyySzyU}W=q?-}JfE21pP>eX~SsI@}+?@cb2_jT{3_}24m%;4_ zY$4 zR`F#@aH5}0X+>jH1y5f!~<7f8{forkq4+W}!9EBOHdkbzn5ybe zhq#U__Pk+eKZD)R@*pU`4^TbudiwoSoL24z{$1Y%Ee#mD@le^QjHr<&_T=9#>)JEl*2IJ*%wZmU!W$`NoQ$USIg%sS7JjbzGU=n zgnVE$etcS>WAqlY2}0wWudv9O^%JRGidNJPC8es9iK?TJ_lI6P;>Hj%3+$?t@`h-G zj#Gn?s*}97#2zQ7w&TwcqxjrU``opO!eJpCo_*Jr*Pa(6bf+|HudvNm`wH9Ko-X54 zPdwslFV{~|)})PcwC%k_*)9Qr-IiPe2N^%2#XXko(B5y$KH<7cOQ>LiV-<@_+;qH~ zMp1crq=tqD@{r_8AJ#rr7Mc$US0EDIjaBZOK`^pRJmkRw4JS(M<*Vgb0a{eWWz+F9 z{7PX+obYu@<(kl52n26DCthKLkW+nIv`vBaSwVyOidX*V16?$gZ%hQyhwxB)YI(%> zuJedH?P(8>juI&_qdpHw2G@~hq8dP|`yi;yvB-tG$}D5HF*yg-b3exr3eD=?d}%&q zLTh`fnBU6xN(pg%d)rsH57q^@&)s7|dK#r&y0+&i9u=;nh^lo~!to09Y26`7U7i$_ zwC(WELB$K_jN$9l0#zj^525A%_-_HI<=BR~&-%Od&u*k3?BrN=47~Qo35+d5el!G5 z87;v&@oGv2*baz}B9{sYkv}Jv&P@yscJ~Xc>bm!Vzg&?s(o;w>M0)H!$8$kOI^9vV~LJEy8;BkjSNjIgU}ej|QV5;GkV3KYE5N28$11 z@E`mWz}<=|Y!Ul6hTUy(UiT5mfz##SVb-t&+br>E<!Ae2W%M@K@1)rmlb?3EG!I#fU#xlM@6sCwp*_h9OGuOeQ4Zgygc?%`khqPaky2h z7SQoFz-m+29$%SLsmsI<*Xlj3pJa)o5;BZ8}-Q_PiG3aL$RB~y&N3Ip*dC`g+D)Y^Y+@VBK4fCbl z2jl+m=fvwrytAyz_}P7*vk;JecfD=VA#`okoVBNqBHMbBFZZ_qNyW-gW4zhpq~?s=J4+^mVvI~ z+c?Mo{+U&4K)^heoL|dykmtJWcyQ<>D8h3{`5IYBNPbhc+GRz01m;acu>Ii{)= zdE$x3i?K-a!I55Uv(J{=S3~zS5Ec}{prUbax|_#BNvy{v%Y`#g;F}X zoQdgP(ZgTjwk1*)+2f3YuNMbcpC@{zsUu}aCn6e8DF)H9xPEUrg3Ua@7VCInTEd!- zD9|PT?2K0%dqX^Dm=J_@=jRguoioJR$tgOSTUkjVkb6z1sjXM>UAS?1*w!igpyE`X z!cNw6^K*_9Q$&DmSlvB)b+1qwHs(JE;Cw0N&uf+prB>tcuN5F@* zHkyRw5@#$KwyLD1RR)FZ5|>1QbC8nL(aI*Y6o{Xa&f;S&rf7|z?PP+HK_wJ6lt_le z(ftV0H$ay_1_#{&moXT#|5iP7j6DPj^p?LS%^E9&LgrayuXC22_$UiEPW9B^Q!%=G zmnQ)p)?{P|;k{m~@O$I~cvvc1Kxpcx_8)#9q4|>8?8cFtQhuV<@L^!EH&5t(_qvd& z*$F6gn~yUn>``QarfK6~^BSmdZ5T_9oGD!cmD z-+UW8eHQ**UmF1E6lNGmypgC#u>5rZ!cvzQT=*$g6{(w8Gx&QE>+DwujvK_eyqQ^%Dc7RTmwh| z4Bn}B79L4HuY7bT^QFEh3M|3jX-HYUx%iG$|JH4aQwc&LmFLC4ffEyU`jD)wtfYD( zLxCGSOEaxSL`)*+`FD-M!y>O1tH}bVCLm4a=}Ba>Rg8y9My)U`MxE#5;2XUoEZ%q) zXh3m6lT}){6TZBm7{$)vNjSvN9TD}NkJvvf3}(bP;}Z7A6_Gz*vft1SVWy9IyH}F| z#K73xE2Vz8cB?Y)1`7WMM+DF1b>s{U$svzy@NR&i4ceE8?NtWVl?p4bxmPCRT;`&W zfbXONzpn)9o@q!s+w;`ChRsZWFsh~o)5+htdou40Lm7XFC<5l!Vg#NmE@21J;fyhQ|!J{AG#%yHbux!81hVL7!>#t=Qb#sG2ao8J4`BxEtp@}k52IC&lHXn13=~u5GR#|IfVd&hC06- z+4q*YXU!4?`Ab1n*&@n9UMe_dZi*Yz$j{A8OdufTt9f(JxUN10bq^24^^xuEyGGv( zO=jf1_|c1pL89?@Vt`M7t?e7_*O1+sPj)6ZF_+7hC%#7%FV^{H@TQtame6HF%!VC)^>^@4?0VIdhLx6x%w zuL-Sa^!rtP`f{Q*#>`LPDti{%`xgoJ>TXGNcu=+IPBHVTXhW7CICXSdGJnwjKKlD> z1z!&Y7J`VB&Cx2R6I)|3Q&J}HrVOrF^K`kcRAq!lnmj0e1HR7j?v4ET7Qw4&TpGl! zXrSQ)?)XB!*2@O@O1K8l$J&TZ5WoXxFgsHJg#~tw@i;EOG-{(;mgu3$#AF}7d1~@TMoEqL%p*%j$jbXuWwr9Ky0;2%!zIWI8losGp>hVmG@F}( zaJ(Jpa+RY;=6k5WM2^b2iyz`-%-k((a0Z0ph(KF|gMEamx}UGPfqI%l}Fs z<|d;fd5O|uK9l-M!}k(VqeCB>MpwCThh-bgzYKAI-#Ln2zY3>KQ6Sl^W{?dlkw4k! z2jk%fe{6(g1coNvrCVC=4r92s_7`r1A`6!CqzDKy^SM&UL^w1#O_AfRF*vb5gENgdG8UMPd=B2KH7M}sm7?YveB$#j2~21?*A6%`KMpG>xJ zd^(7}YZXfKy<^O9U%hbZWj2+cs!pZ8`8wmr=LN#n8WA@)P#Ap`NM<{r5YffqU(Ywa zo#1xm7JTu}Ex7wx_+7`xrQ>qJZ9}8^_7S!yr=;iFLmmvOhiE+kg?*!4FgZYc9asT9 zanMRv*u@nmE|L#14ZAt;{-`s>f=C|wl?SH_ZP!X?COG%VSJs~AWhr_ik{B+o~Q~HaVzcvE=E4xmK&IovcBmkuW98xV4RENxqJDvYD}67)UoQJ))g%%E#5+8*f3y>nv7LnyAwn7b;0sl4 zPsSZ+Omy4v3LIJ}H6jYN#K#8%LnYyDH2%i@x|fl3`h|1-bxQR#ccl`q+KF?5jU8(G z0d{9Uq)%sBd1yfw3MmrqL$$wOBGhyLn|GW`+oV-KDTx*AQ|hw)QdWYW4fMAS5EMFJ z_9}lDv25edX=VOo^z->_C?G(Pg07Mc1#JC<_va`XN>tph(?Q@M69lu*e;;z9`b%}x zB(-SSGj#PCLRmN2AVH`=5N$g@F%uPfxBW2TikG7f%YLvJOSG7t+?i^88E7gyy~I4k z6-vAC$`ufL5)6-H2Ll$Rc-gIqSDTkxI?J$Lb$j=Esb0Pi3kU9mu>R!)b%w3mqS~dY zmGEx$pbVXJBHqz~K_G!^p9z<4tsa|W`$6U%s&7;wQjf%7t zl0yt^)fI5IVXq3p=0lsZAS@r}Lgt@rmS!B)YU!PQ@!Qd9j@9tGy@+&?LA^bjZ}Gp} z@jJ0DFIHI@^SgsR%!j&Xbq=YKY{_o&iz`A>EyD9oFZ;eYgwsJG731kT$$SE}O4_lo zY!9)N^1kSE1{Ulr*K;pX%j**RhmTT+a}i9?`38p18xT5vr;l}anqXZ*TIaLyeSBeA zx+BtoeDoCEZ=&(s)b3)3{~OZTWWZ5aA~i~0_0x*F@H>e8Cv-3*P8YEhNEE&b-+!*Y zO|osJ8`Ua>LE7W^*IWbzRKA~I*oXeRMkB+(!w76$DoNP=XGMvfeW^GdApY|IA&DPj z2^y2MkvB6x{gt$Ps%0qhf{1wEX`WxJR_$jwr==wv823q8k@q`1sRic!SAf#eDfvTp z?KLw{!$?Lk)7PPr9U@mr6lwh+YD{PIXV4#x-(2@)NZT$RU*J#!#FN3J@PR^oo-VjK za%Qsz<&5p@oUZeRV|ZJhQHUd?cFziM)*MT(QB|$s=Xkg=_?jX8Xo3$_{l30@@a-pUYoWh`#p)bk#QRMtCOtSIN5yp-v}2ke?aSRCckk{ z$hRQO*Od0!NXxXeJ@w%70e0w<|MN3f^F_&rivm{{sX3p<)L9NQIDA|pC?rmvVzZiS zaJF{Qy;!h=CyZ0Qqd%GFs@VLUb|4;Eo|Wffjm_MQNl00`e7JOzw_@_PVQmjvv7`=L zy?{(xxJggN8YSXBUy1!pl*C>q9YG3ZEDa?2pCSHsc42>=ui0Tob|>teNt7~19nn&A zRF{JGcl^y$5f4*M_KJG83cg_tnhQ3SY}61e&ajIRjOLPrI%w(OSr=_H?iS^HCU67{ z`IQL4gmT@?*kIN^x={ZhaW1A4a$q9ON#JC1S=Vs75n zI4;=eygd}_QpBh=UApEHD^SN|CVtpDjqh%#iMDC~*s6Wd( zf0Q|NTgdA5uLPm5_OV5d?8DAyE#!r%C4tiYfN+43NwYJIup z4(g>L`_hNO`!wbbZ0S`vhQr}-*YTX$z4g)QHeLVdb@7l5o z`ZbwU8s(hRZNFXjEalpd_Ge;ZVP}`~UkGHTNs`kVoTc(2z3#ahh@vy@?zOV&r!>)gDv&I3O#kX=13yw+ZkIm*KjOQJ4G(YL!n z^2hfrg+uf)K`4IMjdD*;^;p*h>yYx`rFZIj=}v^GkejrBALruisvqZS!Lz3?(*tfv3mJo80I`xAm6OsRf)j^?H7WqGMNZkhj2Y zQIjvBgGWJC2uoPipY#F~)8fPZyV3S4A$eNiS5uEv589T{lfWSs5^)Fk^w zW+q8IN?8>?qeOAv;>YY!R+3%gpU5TN`qQcxD`KFnub%RP$> z?IXLL-r`@Fcv3hm=^JL2tv4h*$=jL0(sJZR$Y>tru5r)GYOX1DR1WX` z@m#Y@b}tlinvq}<)E8jz!6}@jW3xw9eJqlYQYgw0E(}N{IAP)u=wRJ%vPThl6Wq^n z?sv%@&K2gd_g1^07N1;ZGax#Z2*a?zh2G0(sI~h=t-G6H6R3w2rG}-3Od?U@UP=I(uTRXFJoP^MRPGT-36!YnI1*sLCuyNOUnt) z#L7GI{K4gDt5Jel%FXLw(^O56g>>6K0n2EpkEp$&)FVj~t-!7$vX*sxw>tYLzz1{S zG~AVjxIR8wioiotDz=Q1@+TOVK+@yVvI=GZJ7X=AE@9jde-3Z|+jW zow_J;zcKfy%J|IHwQR)w_;D1bWV`lt@Ns!?k`5pQY<`#6ajf!=1LB3L_FZU?2J5&! zQYE@3VjR?Pwot>uGVR@8bwh2nI`^^2p7CnE@d-P%k{B@YTpY#4>0X-|gRf=O3Q5IW zY?_aRFZgV=^IcA%Jk#14g0;L}uq_H^o-AFiJ=iNdy|qCisDsGtsY@;UuY;t`l-793 z$rcGnfAsQxSP8mAXUMY=1q^)uVck;ASjc97&ZsQcFc5oDv@_|hw8hfbLF@x($Fc;D ziCJ2$Z7g%-sNw4fgOzwhSXK5%`nkzy>)Bc`_uDi9s^~S0??C8)`cAi}wMSUbXa5jt zjm;rdaB*#Af~L(Eh_X@_Q{Cx8n^c5Sp^9~OlVXe2TRhAOoy3kP?mQl(HPVA=ZDWYf1=S=b) zYJr#fiq*^^ryX-Un zhjdS)s(FSdwJYldg;o_Z*gr< zcsdlmE^d~}fZzKmR>~m)g{y@Qwh8@4@ptB4{l2>0I9~z``Xqy z_*|QyQw0%~I5!t|4C(FGqYXGC_(sfTwe}!%^-!s7m#HZS7ec**v&f*nrMKQp`nP(j zt{KJR{X=GEr(v};17-0ZzzN7dZmoZhClAhi6N~=|V=}#$Bcdq!utRt{SRTpXf|cLd z{}uZecarIRo_a^t}-{vWgzFw~WEybamjvJWe-?s^dQow1QGlO_)glE`L;TV^kt zcy#<7kK&-%dkPb4AAoA3SOnBvs!X`OVd*S&tAFVTd1dDsmuq<>;M$mD^enCS~v$n(43Kg1Bn~wH9D2d}eiYIl*gPl)DP9 z0{2r*hBJ$F|Ih8;jww1Fjf(qxp-k>nZm)4IpZiY)GD)*j(cW#>y)Us-A_kgzkFQuS z*GGz_1~2-kHnR7Ts^hh#l`#Et4~>oPSHI*qagj5T* zd0*GHbt5onXIf-s!}W%;mC_0x-BJ27uheTaZj|)(EAM~z6-VZ)k2_bACgjfx91qH3mt7zO;Y!SW~7671AM&4E9vRH#Dww|)Wair?&YuX}sX z;j>e|D^GF7^5l*KH`Mwx_8Rm}T9+aKE(HmSy`nO&cN3E$Sw`7_?1FILpRGBJJAI35 zZB}1g>qX9AoO8zfRTP>MwF5?2r*X&J~z17WvKoE|SlGjSc;a zZ&PA}uCxS-2u89E8AO zg`XjmjkFVS`pP;#Dc4gSFnRDPIr@B!si0;1KY!`vaOp1{p(!9*km%}{nnIaN7pCL& zq;AzK=vCi+{l-#J&@1_zWDbYw`IN(&L`m7%=VKxC$!qU}`SJ4N?5qfOF0rz~5fm(9X1jQQ7P0!noQ^M;eJh;$ zWN;-L$}I4r3Z$10U_YRZm=*|=tmTf}^0I`Ki`87HUisllRGf;Ij!{b$YL!!EP0NdN zJ`tcB1O|mjP^t(mPvJZu>ME*=<9$W8Oofr>jHf(F`#Oo#hT_zuHiU(Z9;L3mjlc;y zl8%@6+>TMBd_FMF-$vr!Vnc!Pk;Osw{7@`}z&RAO+1MdNuXwSM$Y`zIR?BX|Qp68c zC=~zc&@=aGJt|Og;IgF$HN{?qS`^LlX$^R%$n?U3_|(*50!eTtt6)q3zlu`gI9UZ} z*6Hg((mBNvuBFA$er^^|fBlJQH~$ClVUlcVB4R5+od~mi)3r!652WQ-SzT^VXzP$G z#)vk8XceY}sjzw2!IYSg2LuW!t?P*`O0znV7ddL3Z627Cq~*~Gli#wVxq$-LQleor z74$KE6){Nh^7b^#xR0Hi)lm8>l(+2*Yi%*ncOn}k-4hh-bK579UUbp@oBJi?_C7hV zbIg;@q(Z5Y=0Z_2Rf&13T-_f(eoWQqlqo-!?4q(AmO;~XfoW?ygKE3K=BX)VEcepE zeXi52?2*8pg38r{ui^!ZthCSNz@##oq;Yqk)!-VIH`{p0Y*w?y(8e(}eQ?VOC<>vq zoqXFozz$)uWP-9(s71UMI^!9ja#R?@6uKFc#*@tT#uiqM7g3ra9W(k~qA1!C6&+30 zPOKp;Zfu;-=p6q=9?|CH3#)BTV(C#gueEFI#SvaXJ{73mk`?na;4ipA6K4a*GwJ5i zB?r-V@{19t8_Vua8;h8W&E;gI3f63*_ih)5AIisRHcSrws^uxbAop)pl%jqw)NY$J zOZw#Goaa}uB!~5zT66AXLx$u2uS#5dJI~=+bO=Lw43zC~zY5~&N3x~jqQ4~6UscCh z+t0BM&lGuC7i>abA1j(&h%)R?EnZkRzXE6$Sd=jn!=KnfLZr7}i0~7mv6E!kPtIvR z%3g(iDBmzC?P2$zbcNbMmxnYuY==Ha>B3IV%4K@%Io<|`JCcP=p^?u{^C(I}{W;H8 zsS*Z9_TfLZjCgm8RH-p!61}=qO$`N^U+$Mlo9QVFLS<)yws-a+`s#_A5H-aC)(lg4 z9FXtQXm&D}5fV@KGIkCnlu^r@C$Yz`qvCb0*G>(0`OYT+qL#Q{i{6*z`W;tIZxn(b z!%C*mgAx2H=Msb>ua>Be{0o-LfVP$8O9x``W{q~k!*5e6Xy01m`Zyr*5j%!PK~%SF zsgeo6vN@bKPe>4=M1`M6O6XP`#<*;mFOX&Axnz%=V0*psV+=vS%MrrJLv223DzFz3 zDm;SYOHG;vb6QLIz2$76u*SzP>g-#Q6c-o&dS+5FNr^1VEzk0?8#1v#6Qv@}Y}c0z zofs-CpI5CNPzRb0W7$F>UCrkhg(K)uK|jTwmtfHh>>&Ez1|;S+vbiKCBt6w62CF)} z?6<&8RH(w4lny4`oF8J<$8{DGlTaE}QGfyMKYi`$;i@NJ5CEU&n4FmSwIBgdQ7hQ0 z)F&hm;f{`nsaKwcOb=p5DtlVeXB@E$1Ac-V{y*(~S5#9?v@nDq3C+-z8XyTxiUsKq zKzav76r~D+^de2f(4-dwC?W{b5m35{lo+Wh()dA7`eBS|uUi%+%uY`1R3-_OTRUL;XR)@Hmc>?j z^r!r-l}ZlVt9)2qG~ON*Xh^UN7A_8q4n%%u+6r3WaesAL(etz&3k-$(&IG5R;G2x7 za*B#9td@@Blt41DtYbmKeYaUcPua;6Dw@3>A2d@`R=Mj>Y#g$I7VFqI1GNA}c-*MJ zS>Au(d^}T51B_Lz)vbUA7O#!Z%WB%XLXg<^u(A^S+*;3kt-k2lJrF9Dp8M2q=pi{{ zva+Zh;b!boSR0L2xIQj!_@NM%@?(G%tcjP4py3z;%?Qr@Y1nJmiG6{g4#8k(UUZHWxETS-vO5;ox?~ zg3wWe2sq4G1X?jR@Q>d*JsS&aqLF$0wEqfrRE&oc`*V8VVVFUIGs2jxhHHz`wI_|c zoXlYE*ijXUx=rHZfGsEH=fpl3+Rg|p-4HKVoj5IZ1F=fFdwAF?K6~Ykf-bkxQ?hoRZI@9qSW~}{ zwO+)FJRA?X$Opd#_Q>lHBuhg7g4jSHvNp+XlGb`w zsZbmF+2_4e7NL4}*#0PnyOdY?0+=Im7#Gt_Q0WsZr0?wPOfF&N2SjUOQDCLFsYJq`GTRLKst#b$Som9j<*#rr86l#y;4uy z+zoqJQGv12%igTZ>jm!DF|eJtd`^1|i*O3@iYDb;sK=yqYObxV!6o82&v(&4F50>v z*pb|7VnY#S$^rgH8N6VANmt|#U#)_p@nm7IArmR?v9u?)3?fp?0FDB`_ULBYm4y-#}3#Jpry&kO< zH^hWuDrcvq>=FzNjwF4nQ-I6kxvlkrR#YV8DutUmnSQS&Q~XFYS`9G!R_E|Bl?kSe zVSO^sqO0Cd9dfxRSX>PT%8KBb!6?3o1x43hSQ0^P9QUC^eeaBITwh8+K!0`sFAjjr znM&3aU$!uSa=69{!1d(kFkuJ}DujFrRaD$chq8ohgk zl#K?CU8MjjQ_0P)DpP0p-LZGGvIb7m>3BNXvVwaGMm2B{OllSw=iSSDpWpWM^oX@! zLMkv#V>big+WSLLQRArWBc@0wh`J%cnU?jcytLFbfaYe#DPZeSpgI97_`ET_H)sYC zI3%*~ye?|Pib+Wg{i6Y1Z9oI|G@h3NKPZxcmaW80PEP8_`dR5E^3tZ$Li_6rQt58F zDc24F!C57MwAS0a_7E=dFM|o>0Z`eq;=&7Xu%>~Vm6)ZI*Avcy?kRTxwTX(x1|FW; zr#3t~0^~ixnRj$SN`o-(vkc*)LntdVe$d}LKd)Y(ri zpTj{H!1OC_Zb(a_3vrVP!s%}k6?4nm+bmybGXrUcu3+ue$z=FGyHUXhz$p3QBYp1} zuO=FqEle;Py?HZm%H_5Od1}xiRYY~`w0Wk<)h`;8iiE*?d9hRz0+KJ5!L z&UMGm%+C*9!j2ZT9?>1{{x{AsES0@)y4}X+iRNM1hoOxk8m>N*(Y14qgRiE_l37>} za40?-(cy73NOx4X@vIbUB6f@~VSe&h>kZ$z1gW>x zr45-=jQce!BkP()bjyT&r!`fC=|k%S4YxKwOEngjWxstaQAFl7+^9;?wYKJwJ$aJ& zaV~kL>wil#IE;FX;HnEPEX1l9>Q4H!7iFm3`R1^Z5xx_|%jB965zSSBh1Gsd$w>d6 zcyUFE)Xz?J?zEutvgH`r>ryunn;!TsgpEF$T|!T$G>HZ<;cRhLm5jZu4>d(ff-S** z)3vXe?2`F>qbj%KL*cE-6X4+DMnh}NeGUtiTj3>flab|Y6UP;718BEwDSIJ@1nYTC!Cc&E1`?cTb`G{WX;FC z@i=`%Y463namd4q_wb0XB^zDFkCmMYjL+Yuj;X|eRvaDglDPSyR7Jq?nc5^4&NS!h zosDFbi*19VmIliARu{+{6X5JjwYlbe#l+FD$>fpqtn+G*NlPKk$no?=SsNbTXntf6!qWxLziG^8Ttl6G9iYU$TU={VJBTD`vP?`z$wr;VbKNTtIBIpO>6Z=8hpN-RVJYoBK46UE?kj z^{#nKQ;?!U6bhkpWP%Oa=z{BdK21I;%j5RRxx@NN4YZc-w)B~ z44Ghk`Q(PU4EbY4zuLl7OSTgRzig(+eHu?0Dy?k#Ah54jsSp$KgcBADf-EHyEY${8 z?s1W1mG=t7de0ZsC*4ZTSoYbH7dC)M<7(6{`x$A6p1jk&(DbYAcy5|s1 zkfQ<_Z_BR5Uk?jdPwE~~S!MXcCdn+R8ku#;#%ATiZra~h^Q#bo`?`fd_z5oriDE&n zwe1*rk7gr;({BOzE)7L$c1cl-MRoJ+E}Qsy_tmR7Y{~q^xXX~VPQIio-^72*JMln%CkyicKo z!_co172T0@)2kJUy@K0+w}GDeBSRX}M(0P~iK8_3V|k<6XMtyoC;7nLr42jy*7F0h zP=23&-I zv2{|JwqZeo{30&!h3jroC)FS2G=a7I^(3Ue6T2|oHgosuMby+0P*6dQjM~Q z5B1MJ$j)8Zm&&zBT|qEXk&%jb>c8ziIRKKz!DFW&(HCh;Th8uF7?e8MO^&~*S% zD>8E1+fkvLJNv%YS(5$I`I@CgrDx1GK5Pj13Bhln*#i59pgKP5h6g7HSHEkR?a551 z=H%B|;x#rR;_10~Jw0T`?c2CHQQN6diuOvC1f0iqAfSCfpXg!IS{vo)<%rC0cK&e# zQS#r*Ayl#vtMpQErDaCY#yBrTErptRar+u8D7eaABOtH-aWKCe=3>nq~L<0qyQ;(BG`-=^Y0 z&HtNt9-Hu&Q@0?>Lw|%9vf-LEre6P`X|PodZO9})b1^&DkW2S&3J+6uV&f}_x*?J^ z=7SdUKMcSVAn-SydU5wWg<|L_h= zu%udM%JjXCmZ`S*R-dXG?kAHz23iHv>T&j5rP#B5JA_sW0d9H6-K|gB?-Ol?)^IZ zp!;0I11sSi(YCH6vN@TP?XTqYR;1QJq%ERrHQh-+@|D*8Efy|`hALj}G)j0q&Gx!n z)D|J~7nX`zNC(Nr?`@;M^SDGZpY9;jfTPY$7aUzjT6>Ge_D`QM#TIhpWz-*eg~w}= ze8^G1-l`9)3TiaxTA$T8$jhB~Hl&lC?i>e~MT=!z5$R4W)0S$VsrdS8ct#t0mnb{s z$D|=Rub3fOxZ~Q7PZ)=ryh!7ITjqoP0oA^pjK9-xW(ReARGu_MU2xF*CzYXGz+gY2 z_ST7}TDIf0t5>{0benF5)CW(xEYn-7Yxq9gGTCm83)X$mb;;!6YTitMa>kWPLkn%CT1!fcTU&AM^CYo4$Jr!%!RAqOU zMdF%#tIkiV3+7h%s~WM3HD5c%G|pR$7Wp3a_@@WadBA(ogV{8hJ3HOg*}`;14DM@I z@c#f5>^LMEPeZMQI>d_@7>!hU_hrb0Z|u0DP_a~Jv5P}!JpBdQi8T%ovx@H$5G}ab zA-+^r+))(9@`!&a;HONBi!fsNE^updbPjsZQ76Hj+t?>~9`hIGD19kVd47II*Dbe0 ziK`}9*&%DWO&iMT%(=R^6z_8GX}lMNhaDred@DCpu|~im>jdTtMEJkxNvqc8-lXef z^cX)cXfE}1^OWLLgzN;XB_{^u%?jYNoR^QnM-j&kLSK=y_DEZ;p z6l<*DsI`jSIY2!F0tA_IKrchF6Ljhs*yE-h=FeAu6`W9J=dl*N$E2|G=|BIMJ@>lK z{(S3PU&;Wl(FZ=4@~5ijSs?Ngi625N*7Yp>-rcJB?@mR&hcs{AOUdTdjVBlNt}dXU99DMFqId`vxUy7zdJ1AK3rRKB|E_*`;G+@oi-IS2{>a`U~N^L6&9)B;us z5MBhCe)TX1hX`;nv4d(TKN9{1S9SR;!|wW{lAcE$@0Ot?-1P-jOhhY^jh!~>+36@Z=J$A+e z!ZlNqlPtf@xN|l`!9~JtGoUj&=;xjwA0rS4C=BBqDS@^_!Zc~rq10i?e$E2!7e|fx zH+w7n=hnEm&tP6rQx77lsKBj@hHmu4==iS-a+My(I2i@l4(o7SC&rV6@7X>OJDMSE zJ+j;-#T_nO;r>$G=>}+MFv!UTBD!IiIe>Ist%fnGOELW0bX071-4iM^|8+vjSJ2mwybro9GPKIad=yGsZfU_D%+IFgc>u%X$kn7|<-Ge*+E`tZA zjJ2?~E-&|e+q)q+^SAB`Z;4d?J@KVt=+4}$1KZl|+BafqE=tbQgx4Xe9_s}MKP$eb zV5>Z3WhAGh%oY{*Qh>_;Ya?ipA2U67U8mwBKJxrJ+2R+YII^8U*n)$~<4u}G&cs^x-8;Vr@s*fywZy!w`&>Gf7--GADiIcc?lsOIx8n5~C}z=UBOtzNevZS!RO5 zh)Z`!)?WN?J);#WJbvF>A4!*qDK)LvLbUn08+ED=34N|UeH;YdWO-c}5pUQIF&LY- zR$=6^e6G>$ZDNj+25JfM@u2ykc)rrky?sc@vg_*~^3s21DOvZ~ez$&B zKJVf-q6&osNnDO~ru}ERePaMMP!hU9PU`bTMKO>$FJJb2x*P)s24YJ4$ty`*#n zYSTB_g8Ed{FOiF1gk31{RxlKGr(3Z|c8+I9 zK1kJapPA^GFL$oidWN=>0|N)}9IboL0&h~iEUB;>1eSQot{@Ga7Y=0k0o^Vb>;vwb z#L$!GkcN+!{jaxU`3n+G1p_&85=1;42E#RLCvhOFUNDfZAY`g;Py4EX8Zhfm1L{02 zv&aFj(8zmm^jLo3nGq*TC2mfo@>XY`Q_s+78i3YeZIot5d6C3gy}yxgQ)o0hq;(L zcD2;p>LxcgNaF51Foh?@0R*z;XQyi=5!st9P7N-Xi<6XRt{WVeglz!NNEec#`7chL zoe)9+k$=XGtNXt)`DT!oKO%qGhaR{kWzYanDHIrB{UTb$WfPh|EUs#DpIXxd*lou^@Qip=gPjWY9W?4F6=+<& zv?AVR-|cB4xcfxHxW}HsR2B#z4nQSVKBxO|C{vcF6R!q!JB)Uv8ZXgg+zvjY(tZ}Q z{Lv9IFD$5FoFb++=L~=E&K{TiqJO_0FMm-@AN z`1E^p@18>LcJ4w6tChjeJ*)g(v>iRb+M+7o6eV*TZ+|OcZ~irx!Ki5C-UQY^+&hF4 z!we4sb00H%p#UXpgOroL`uDajFK)Yfu`UShWO&(X2M1fl736UHMi6R&dX#r->bJhf z_U+7(rnaHv?jI8Qt*vwVhNHtii+#?hE+J|}+7Yt$8MGZdytR48YkG5SePlJ631hqS zy{IlI?kfT=3TmzJ4gh)9gHnw+5$dz{)YsQ?rAo3!j(zlBOuO+bOmQ-iqrp|0n;ygl zl>p!~$CNB}vK<6{O z#h6Y?7cG1WW6Igxlf4ZmRqMCxJ{BlRkXC9Z&S@3^*a{GpZp#{vunZ^QU|RDU+XGLe ziptflJu2S$@T#t~EURT>(5?Nzb%UBS*jZ}e=tmI@h+%m==x;8tv!ToGS1wp;27cZX zWjdHNmEL}L>QOO|=a*A04uQrW!(Rce_z8`FfBdFpB%U#R@U3d{P)ya4@WcCH4_C&HeJ_NEN^}ZTJDh`i~=#P(A-$)8+wgE!)SScqv4eTx( z$cZneoByc)@&1k1{^4T>)5YFaa(OD6z3Mq|T!1MZ*j0HkfRUNcqi~9Lg5_Am|6^GV zjMGy!e^QC-mh<7ND3c!m} z=K1#n3&<3V=ZgLlCjs3FpqO$D=klL$8;JcumfYzkQQm(p#6d8sN?j`OfG9^Sc+zP= zi^XGFqYx65Cj>CW^n>W8e;*V>M`j$-^mKplK=o--U7MtU014u5EH;{#Z-BOQ1lk0VJAM1rR%dp_u;9a|1A zr;86(8Y|pU3b9q{aedSW~;(+;ifdDXaT= zbHf?2c~e^*EAFBp><0heBbmP$>Nj zST#JK+&@Oj;RbCj$Qarg5DXj}W;)M0HNFF? znz&%y_q~*R0}gA|vB4)mP6W|GIk?>jjncj4ws-6=%uW+7D`a9N=>FU>qKgQkOOwjW zjLGqwxw%GBQIVeC^3}I7a9Dt_>4gitE1@eZZ~lee(1s^iFS{W+adCGxn=H%qn|}E) z!+nkL9&>|^*f&?7n?defDA>Cb$zpT{2JCk6y~3GU+NoX6da#|H?cJ~a#@HBW5MF%pg>AYb4t<|mLex;ZVUGqoH;8}sj(EEmIZ1YFjd#$3RlyK-^r^`16iu9*K9MS~3=uomK?LXB+1swaMR6_B8^%+X$ zCP3~euNnQHpAZPtU`C9DSfM}KiVB_x9p*FYq=Q7meT8L575@7mS&7t-tT`r51^)E3 Lj88q%u#5N~l4WqQ literal 0 HcmV?d00001 diff --git a/doc/workflow/remove_checkbox.png b/doc/workflow/remove_checkbox.png new file mode 100644 index 0000000000000000000000000000000000000000..3e247d381556a2f1f05ea8649105885d53174cd5 GIT binary patch literal 22272 zcmcG#Q?Mr8wgtFV+qP}nwr$(CZKJkr)V6Kgwr#WPob#W1Z$$TNe-9ZeC)QYVK728A zjLZmmSuq$$Oh^C#02m2zVMPD{z=6MO3vkfCS5a5-eE;eQ$OdvK02?h1;43BsLWT@L^%9(%p?D)ZubmK>X z0U$>@67of*2HZO^&)g0sS_8l{2W;0{-+`p&PsZngD)2J*jVWz1^hGFno7IysZbBUi z0DvRYswF}Lya8*P9o;4>;6MVKNFnHv3$9_Au<1gk#yRqiJ25gclD&eD4@dkIBz@Xt zL^#UOvS~~!-7sQ+*cqg-XLs!Xd}9A_G0qn+%*=`;B@*7jiK&skPn|C?0v}OJX5leT z34R;@5SNO76_%u@9-;6}c~`sbGD|HKtS(F@Pi|689t+Dh5Ho34(~aIo#9u(M3pm1^ zJsf2bKORXt*-9EuGM|7Kp2#_=Y6|a4$E9}CCWKD#kV$wS-J~V39K?)d-^FJmp79;f zs9p!q-99I*h$!6qIy9GRD`nwQ-k=qIj9qAOAl!R3%)zium2djbVr0v|y#Q|af`R1o zp@su3_oAxjujm8R|C%6sX1*Oqu89XvQmWUmeq01s$3~bjgq>KQA4m0%U}Aql>*?Y1 zXvQZsC?OSn%AWO!TAW@)Tmm!~c?rREC@Jv+0MMicpt*2Yl$chAVRGmLLiB9L;D<2> z~ zujv4(UCdU9>p(GnP|#i~dT8E22JpaJc$5Mm%Xn5}fOLW7c%nnlH+}(mc4ROT{;_!Q zM8Q9KF!Bh>Kq~x8^1zQ!&WM~~Isu+}VDfm(AYDP-z2MRT7`3F<;4>jO2CUfNV*OFO z#_kkZP$vVKdyK81ydW*TC%aa+I_jW3fnxCf@d%^^L1{#>;b@3~gG3VHAR0l)gvH`8 z^TCZoI-@qip`D_z^aE)(>XfR$ zR-rD#jVKcsRv6{WwEA1BS*P z2=6l}VvIx&!`O;8KrunlN;XeEqD-bNRWX(hwirF6Ur5(V6H9+d(=)Z0Hk?76Txb4c zv0SB5Ska)uqx`EPq++VVO{J%-Zqa*jezCc{se(sgq%5-{tej41r_fj7>&bf9I(4mc zEugZa*$!1#gf_6TAIa`P1jP}$a@6D zY~MW5w0`5Tgu>d=EW=RK!gBzcwSf&O2f&-QcE#;N1jx23T`x@FVuZB%lNvX^qEV(UD+shnk|ef~WbW@&iEv`Qv( zN=u7LE8q%u{;?_8k@iRt2^z7J5W}#BgLnR-H_)v`Mx! ziddrVv0fP8GT*FUrXQVH(!c?;eJC(CkSdrqcoB#wh$;v#6eCO`NE6sVA7I}`5Z(-1 z8ePT?CXgUI!G=&yFlGoX%ohtZ8Y-qL#xwUOrMHT^8VHY}#9&`|TtZHQ79pMphL}Vl zXQ9O$R3TP@m{F&Zq@i)0labnpZj@3S(s0wz!9n4{#l9HoEA?udzJC5ze_+T9Dh7>a z_vyId)6iLz99=%m9xX?Tzf8CUd)jR>R)5uAEU_cvAH*;Z-A1j7}a8@*$pL=A))rDRlzx%=6rKHWy|Z;w%1jcTZ_gCr|<+f`cM)jo+m0 z@#V>F+zn#Zq_pr8DV%g$ZO-b5d-rTyd^m(#q^{)z98(e}5`^ zvbgY@XqcoL`=})_74qsf{civK#{2X&rtI{6rrde#iq~^* z8m%vDJhft|c|Dg?B{hm zdiGr-+jQ$$uW2tHt}SUqRm)WR@b-KBuFl2rvn^Aqp{n?B<#*;+#nbi6@5|5M%Ny>a ziLozibU2;u7h;3+!n3tvw5zZ+WktgQ6e|+b{Z71{=qd~lqNWWhqu!MqOEUV%#-0>yY}0(4+C?v6nniKC(z&(3*~)KFr17%k*+B6570b*S>H2 zo+inTn;yyOaU!@Bd>QJEN~L?#<=h#%=JZ;7x-X}iR+H6r>0I}m`qrFq>9`>CZTPqn zZt-!OfC%?{^@nv~#-<2!o`?HPS!||OuqPG%e6b7~}xgFa> zmrayUB*$m!jg3X~S@c}~-EeSHO~;_88%^^O`J?$fb*T06(t8s(!ZbWGqz&Qbh|E75 zoRkRQ9ySfJ$PRGI28d4~JvEvHC^Pkx1o9nQdVD%o3zpXm>_6%P0FrOT(T-Ul@c@92 zJS7eXu(3M&keiTfY{XM6Vk;t+G=F#Za%vsU2Dqzr^ZKfO@%q}rxp*lAz{x8+b&QlB zKK=9INE1{QSY1We=l>J#2e7CD!m>*8UHo^H&9+chcUG5?<}|Xip*1kJGc=)fx3T{_ z*#ZD?yL0|s+L$;S;Je#c+d6T&^AP?!g7feCUuHT&{C|fyTk#O8%gEyk**Tivv(Pfr z(i8GR;^X6UI~tpEDhiAKlm7RPhtS;F*`AY*&dtq@){TkQ&e4pHfrEpCj-HW@k&)(a z1dWr2t+RnUjja>We>VB=KEftWMvfNt&K7pI`2X71z|hXcnTL?@UqJu!`_DN|+%5hW z$=2zgvi=rG_b(3}11&w>|LpxYmHS^-PI(J=6Ki#03mX$#r@t7y%q$Gt|BnBEdHxsi z|72?XZzcmh%m2;%Kc0Uwx#|8@!2c=eKi&Gb^>4d)A-U=Pr}ey$C^0S_008^|62bz? z?toW5F#NxVmVa)J3;y`mQ)(*+2nivuFp~F(D;0h2OOh6grd`Plvcb?O=rs{4HAWZt z^{3f3hS4K`oR+d&4msiuc`QUr-rtX;VHApLC8t0v1ayp*m=wV_q z>(1Ho`^5Wo+l#OZZV$o^gdO089`JE(u+?^s4Hyd`=1}W}&?3Ego22Egf_&(9o-_66zzu!~)X% zot#q8*a*E`Ws*~EZqulLbG!7Gg0g@s+$e8Hn4o8qez`5f5O-BIETp9oy1#GuTSc?w zvnpi((a-4~rK+W{Tf@+tX#01|SwqYv_ZzKHZ#$DSM6YvGT$!T*VTuh6i((I_9}H?q zD!4ypS8@?Tt?HvHwH1{D~+B+H8Ei~6$%8HzrptvYaZx6mS0 z{zJoH@pBVu?+yJTy+~n~g5QYIZf4ItMgpy+AZpG2nR)hTjkT<$!UA2TX zFcw&9djw}*4pEY?R@PHvD?XQyMI|K!PeWnfm`*vFMEbyh zAu-O9&%Kw2=0#$I3qH8IUUX3DP(kvI&o)I8=}Q@Fw}i6A9GSOg8FUFgpOx|Zo7H5) zqGfl|@pOlQxzzT%T*PKQ zPQ_n!X9V`m*2v2NxP5>Sn)EpXJ9UWLCbchj=?*KX``6=m2U63$@Wtng(ek!fIWfQfDifg0icz`M$-1 zxU0xYXrqH$@Qq0vS$$%k^h|gCjZqg}b>Se6R0E``dFIHJYvQg%nbHQ1Hk~iGB_p=uxt=JMwPI;G=Wtfp9Cu zbugA`bwe*Br_4mQ$CF%A0_@?Q$N5{#yO|g62m6`v2@R3YYdxjwN|`Qy zEGF0r_X+A7>Da)~|4&XHbJTmBT`a(&>o%hWmnWgG(+I&WoR?oeh1rl&T26n8unKrw zdT?j)9l5a_fkU7Zx44%C!rdAo0A)c%o}ZyUbLLn;eRF0HBetas0rwK7H_92Ag0j(* zQc!~U1vA%>@2PQmrr< zUwN(f1}k(TCm@GvXUf_T-Vp~l9T(z;)Y4lxZe?h6Wj=Ss zuDZ1@H;`lj1R17~v9f5zhfsqN3HCf8LPDC^XdrZ%sH!P3Gy-4iI^&kb0yEJT@nr^H z{NbIzd@M2uyG5PLHb3=bd=%?=@NKY7+FVa_Lqd7lb%|#k^Ai}}?&t)a)(8mar8a7) zhDGIcWM8W?=BK!ed^OyCgSbwMEar+sI#ovnJ)aHJu)Lm))J)z0)MY*3fo8YUuh1Du zgBC&h`f@qudPUNHk+_8DCUGqvdlpc!+3^@N^MS3b4!~H z1p>-J+s$Z%Ll=|iXpS^)jH6!$^-=T+>&pj5dQtZ<<34MxJ+*&q)>dSXJgq)ofAKAkhVV;^!}XnjBqzZ8kZ zz(MxE)&!W3M*FD}i5?;124XvVKiqq%5Z9K)$v$H}vC#4I<1f}sWKm%yn$b$b+178dl zB(Wmt;JOwMLGmVe4D}-&vn@1&UG3EfseWE08bONAH|qfX;9wo#OGLu4q9K_!KT*sB za_xA@Tt52rC$tBhWra;iC=~omtB22^S2Me>44hyD9#(1_C(3G!JrpxOz-Oib?~G9; zbkGPyJ_z(TYaiMuVQ~n8#``#{)-uJ5X-hBdRW@#2$~8|EzV}@s(yWn~eAzyPc|gC? zYn2-%)jB+|I*)0aBO&RWp?3Z4gr2Mmqg+{%#9V+Njy@ivsiRMPt_e^fr-rW9$bg~J zFRsHQ+Z5-r-U2nHGyilOo{m)uV9Z|xMj?mbUt@O+Z(}7~ibjK;ny)mm^_G_Y3B@5+ zA@gul4~_}&w9EW>E{Kj1JIt8qrX-U|*!gh#^RD1Fq;8p@eS%5lz5O22bo|5Nom7VE z8wH;hb@!l<@YqSz$4e_tyO$)c@*}W3&M8Mp6DT%DdK>jM+2g9s&My6<<$H#PduF3F zg~Q?kYMoj;Biwx72n^YN+bH1jO7%?So%C`@A<$pE1n@b`a%p?WH}9eu>c0-AFa+ko zB_Vno=D>f*h03_K9OLFIse7mU&l6Fu#*v9s_R`oCG;xa(K}EWvj^Y&ME@n|*UQPpX zxN-!S+M;GTl=pUXgxEa{`MUY=Wk003xIn}X(g7UKl+Tv%4$5ov8$(dv18EN(F_FdLSBgvW-z8%%X(CNn9cEWS>mhrty>%>&LapOl&A*#PZN9e?iBH%7!q3rQ+(PQj? zRt-{sM43U)uZ;OeJivdav)8xC|6V)Y$8p4Y5y+Ko>`&+kJC~r^ypc;8C6)SZO?H9| z+VR*JQiUIYG+ezFkf8R*zD_c%Vn2vo*%R@j=R>zA+#o+ABRLnNJ-OD5T-CnsGWZA% zF{SSTjYwRxN3HeU;5l<|G);E8HU5kR?2FG*{@dm?tX{{yc#X}xt>IeSG3-33Ti7rkgFlYHd;1y$d|Gbd+xJw|Rt7POlmurS@fEjp6Yx?xDq z3qq{8b4A+u8T?d^tJaV`%_5Aq5rkAJxoENvRTa)iH>vqx$GfNkq>hcZ6v^3}|q7P{{&7L|mYC>&#myA3_h-+ngsT6bh)Mw#!8 zK_L}>CI|$L^$*&~e|t+=CpB9L5{52mDAe@^i|r7AFHT@EW*;Wh_{K!+SL>!|UaT$j z8z7K%-6yZ65?mYWR6C7s-}*ok~bq zIx*bmPurLBy2`BDI}DqR|Fq{rmqfVaMlFxWE<^eWDknCgQYk1NR(`>E$O7|-iRL9q zn~3CwL)e&5f=tT|UjSINSByuP+ft(2%^ZgUeeDXne8L0LglOn)&N4I(-oA70p|SM#cUi;O~rZo zob4s;JW?@nhnxV*$!yH%=_{VUEOHS3`hj7eyyk-0R`LZ<%o?0WUY>bCGh$ykBWJde zyj?}yRdUMvkmKX)%Ea~HC+TCJL*|>lzE_fNZ<&qDR7+uY23@{v<;`hnKB1XqDBH*CzdndKAK?lVSu`DpUF-9l;)?mt7ZaXTWAI0teXZt?BK$BHEa?ykZB z+-YRge33xuEln$c(NPVz#$bLXZqQF?4W*r#(TZch7Ew&Mp5Bo2^NRMnGnlJ|axs84 zmv(-`D#={j+}yj>6@v4@;AKmeovB{a;>&}ra(O@3LH9iFUJ0w-Dq9_qQX#}~#(;;o zf0)Ou9`)(66-c4rmU;-8$-xC4Q{ziRTJ-#))48$!q=ZRe&>OX+Ux?CFYPtfHJV&7ZteO_Ki#jMA(#|cjje%20%b}pw0i4Mby ztaIGv9-QA9uW|@8) z=~>j<%)FS*T%yjPQ6yvWT$O!w%NOD1q_v|W@A?Tr|8b`-&UakTlK-;8aqQkbEuDS8 z-uRl}ic>S?5g-(%Gm;4Z?0KdvK90Yn? zK6dBiC=a&rL0>XYSJNdFec1x~;b+61F(pz#1!8b&WYe(%!zY&(SmiGuI(oPt)Zi%0 zD#0bEO`^npY>>DLe5>kKDEyth;faP32rIlMK#B|5?S*0K>0Tf%6YEoySew6 zbE`C1M$tJ6I#aT)o`qd*jwY8i{|(`IL-X%)fmMm4J?g8k^C7yyz>teN&pZ9s1xPX_ zDw}2V!+Ex!-Btpfs$%TJGi09t(Re??)wA;xvtdGBFM_2~=&~Tj-LtcZibTXOO2!zS6iJ3&@_JP&x1cJ#N9-aYsi>wH~tpM;%HNJEd^DWhNt$4K<5Ku@En(c7*Cg#b? zydk>srT5@L{fE+|r;Cf~uTp&vcBgRU@K_GPnO%C2D6p-{OKT2;&2gJ>hw-u>Y#T<N_;A0w z-8DFms16zKH3`x77ErSx#D`bMvb(e;vgq#)n9G1gh}4i1krF^Dc%9+R4iXtypP5W} z3%s8V_5;VP6cMh8meuMeAz#CSA*KiTl~&crVy;MTk+2DtdeC9EF-Aa1T+c0_v`Dl1 zv1%49^xI&Q;Y}+g-ok_ci&(r+#>mlEb=FF`lf=Z#U z+frf=8l{S+?(qDcQTYt{QTl-f$+N>H3H<7$M##j2qk3KuHG^wlYp64)`4J&1*#n{a z(FB-QHqpr_V%=L9@+RR8pM&jJHzdvY1SJSonR)rDzoPLFuZy)`)D<7)Sq*FZbat1s z3u8u{{%SH*GrO#sDNK=#W|xjPsPmiqr`R8X8U)AFDk36?u;yY0|nDnTZz# za#7HqC=Ktc`2||*^jH)@9LJJp;$0D&XrC_fjXLM~72vt!sk125Jbdk{RppUqE{d9Y zkPl1CBo$?ibY05%2}mf6dQa`ErJzY;0p5dt;|-?lWYxo4I0Dgm*;9{T(*2 zq^JhnlB%^+GsXDb&dBE%j@0~5qkbLtjDcma!I>4#jG2YTYzsewkNP5prvO=KtZsGX zr?y;D{7e^=Q&T0UOj<;2U1Hv67sI7KFXQJG-dKN`+#qP@D=2MT!}ZThJS`&+wy{Ej zv@%&Eo9+t!n|=h0?Y$D-?cUH}r_20HJ^qK3i~$6^hQ58xIW(+8;y`d^x|3f$hzU2E?o#OYtOCSWYT5x+mzz_9)=THt@Pn!Dhes(+z7 zt8O3qIsO#@AeP5~-qAwY7l@*Tc; zY`7`C_Z7`D<*JvH=mGRS0_xs)B-&q*-_(fef%2UxaQlP8))aP~ftp&gxLCf@q8U3p z+T(m;S*f1u=cIl*Uh85)O*lYQS6_C~l{3!1@&ZY0;tzWXZ|0)2BBYI)Rm5Q9@subtYrECr^S1{!1w zzKI1bv{q|ZV#>`oIuy&9`PKv+j`GX6oWJrx!yGU|^K%}E5{u(L%V=9~c7E5>@$y3Y z`lxFJtKSSgzT>Zc=6rE)5`+OJvw>{)loRj3S8janJ(}+iT{hd<@OB8~so&;Ot69Z6 zV5K|CVK1W$ua9?F_KFtgJ zahS|j!|0T=g00^o$1&U;G+OM;B)_<`%Uu;`&rVBwkzIerzuFGoezRdlnaNonk~0^% zL$Y%LtEe!_p-E3n6`vb7a3wR{UeJj3E{ZloHC~WqRlrpb?S{L*juaUjv4c%Mz=??N zdS6E3g&bt^4hD))kQ7pU6qW)h@eQ-vISi^d`r?)xaR9341zkG!SmU?dn@5%#yq=rv0+@l4zN%3?*l1BW zr>1gYA8R`!e0j|`S5t}=dyEWG-*~YMRr6>U7{@y$#&Bf2QU`S`vkSC3O+JvM4m0>COmk>g@>s!gmREBPAB!h!(EJ2&%>?9(N@T*W058W#y7VzFOMd`k7;+A4gYzC)I{pNoTd97(x4mJAy^Td8*Qu-tPEz$>1HI7DS@=eZ}NYtECLXz{RXOyUOkR#Jc3N0Fg z0|4p0T?s<@hm|FI3$_|oOn6@d1dNkN=u^TCM2u#ne=LX?Oe6Fu%FM{3eHG|yzzBiO z2t>9fMpW3lIamw-3(}kW6S-Ku=hCKN3TdAQc;IgNVqy29An%wHPC`Y9{_-6(Lk3rD zK34iQYa?Q)W?C?GHo%?G@~tC{dsNB}>P=KDBPh5{T(yTD54i<*7aVbPmA~I|?>B|J$0q zSwYWDCp)+fcBAq|VrfXd>s5sq67ml`E)QzRj&A>@oY}y{XlniUgUaXi4|wDOQs3;~ zH%;!2TV2(ed=g$SkpkVeGr{=O#?2+rt^_8ry8MT!EVVUT;4MP-M%NiI^)wRl+gD*T z(+)D(U`ktj!_`nr_-hfzQX8#tfi}|(H4iIwLeK6L`Eam9o5PsqygxtGxvP_0^b91} z_BExYK5J$YeS*jq9UQ{3)s2QlU3_3o*dSR`Bt$gHb|TbqO9Z#))eyMsa2a%a% zCrd)Y@x@M2dGRt1iG`oIV~dr=7Nn)rltK~{p&f(G%>8IF zBR*5OhHzr54Pgm;)#@d|Bzr@YO*lnndQfG zel>WcyHkib2lI3^1))v-bnoN@IYfMnRrY?%WLn}F)HJgbviGkWHlfkxy(*GSBRWi& z-p5iUAFO9#D>G%$lolV`#esJ=lEo%KFCm|VPlLwBJ1l&81ng7QC8Vg&v^WF7h&w!s z10Kf{QM)DQ$l;7U%K4E%&>2l7!UPgIyV^wMZ)Mp(FG@^$8ZpHt%0dV2OKHv<(Qyba z_fguj8buv~qukysqk8-*b@0a?3P@53wC=In2`+P)M4)8IQ{!qhLicG$$QW?(Bilx9 za2+(bVB>=CzDBn{bDLCu?Ais-=4u`Qui(APrG=SY4Dit$F@+Dc-R zllMYJ`GziQuE#8-32>(qUB1=Am4LePqb!yFcG-cF>JV#T&Dz4xQwWIu(PajN=DFf-n)@X zdc>+6tjw9J{nXOFt^gz>1S(;T75X8SFA`?in7n0y)a4a4-;nAJ*fDs(FolT2gQGt_ zw!9DxvYAekPt88KZ*C$%(jB%nO%y2?^0zMhjGX#2imEf!gdjSHpqLBxJZp)eFM3`& z6QI^i3xgFA(70voUkVF0sKhT^KjWu(;grh#yW{jmF$Tw~3>0+4@F<$$_5r5%@RbFe zVEvWO$qK5ffpB!$IC`LO3KLTTgzza_9K_wL@TWChJW}Cc40bBt9xHtH4Z_{W5x#T1 zCDbg{_IDdlNU2F!so>^uGWh+2NN+er_&rGTC69r&|DIBVthmNAwW296H9IP6lGjDm zN$q)z!70V;nf*~HDu!Mza*k-UALgAxAvq8HmHK3Y5P7?zNRK~F>~ikgi+YAYjG$%X zq}-?|US)*5y?Inw8xhI2th>oExwLzKTozTxec@;+&!2)^Uo*MWH(|Lt=sk&V_&4`o zup;B1W_N#?w6xtP`uS6iqn+*jhc~KL`+We#;3IW3VL8mm(YpY20&3tKv10|Hlj6a} z)Len%NQKr~wLyl*U-Lwk(5zjXQncu>Y|{iD3m$uK>9_f>xwQiB<#bq)zC5NJp?24K$1)-DVtF@MlYWQ=u4T*`j+ z5dKX5B8UlPX)Un|^(XLQFp~Bg6mcGkiAq^OHjh9X<_jz*`Y>8vh}&i0!wd)4USsS; z<@;KccQumM*a8<_|3TW=$%}}p%ffWrYuC=An-=7jX%*bp9X8^{nQgmKQpY}ui)>_J z-|FTIodtlX&_%~V1~^Hy0kT=WSC%d@>-jdSb#%hMlNB2VZ0iCg^jMJ;j)m%PtCCnO zeAU03AevjN0><@nB{iRnaoM>v6T|>93!XRVI(#W1=>5a9$<3L^CDkPhmN$MMFMeO1 z#M|5Y-MGKU26R(0EWp!T_{2=*iBkkv2_PYkjVgx?LU#ioT)j#DXk*pQ6X%~d=breE zaZ()setttUodZ)u!aw;e5m-sW|C_DJ~oTAXld~qEKL~bn_NwjRJ6UQ7OM^- zkVa16w}Lz4oB5icv|3v}5($p|6Ms!gbP3FR`#ci|k|~md+!%xqo})p8a%N{4@Kk81 zsq@>A$*NO?NV|S7u%U${8&$Dxl3ZM3p@~!s4fmZk-7iC^##h&o8grL$mla8Fmf36u z7A(-$2n*Qx?qD@CwmuwfrDfXk@1SR|LHUSPw{H6V2cp<9u0Hu`<{vy5G6krncJaNkty$#Ahj5@nH*?eN?kEph5i zWn#qgqArU0mN+^CuL{J{)mnAlFq2XrVN-ve)&WX-=3DJBR4?$k#{j zYw)f}?JP6Z_6se1yL<2~Z?(|a??me4bUv_HCs?3nG}nnG|;_oi|UYM)3P#i`Necrjm6Yu73 z7W`7_Dmgl*Ogq$>7sNqwW^5EP)C+AIHa4#2qBbcYbGn>VvuR5qun%?gl#M;JddgDn z8Yz9cndlE8&(M&gczMPYp>VpCq>{NZc3EiiK)GZ+`Eb>@`3!}fqt$V&ZEdC^EkDgT z0gFdwciRQ-!bKxc96o~PKb|>wTUIu!qeb|h%w^v$NHoK>88yzKSTNG_FR4IA#w1h; z4I!#gz})3hAZ6A2nld&Bje+{Ci?yuEj=JaqQA@&M*;Kj%)3Qno&->WaGEPGM!#Jlm z0_E#Ghs-64p_<*M>6($qmJCwAEhB!lTLI~?_~v7%+~g<=R3{UpVv{&&cI#fZ%EJDp z3A(0O>Mx~gx}Ml4AENHi^ee^#1O-u$$vdjw+L6d6<0u2@qsTn+zu-jjkjd3x<%l(9 z#_M7M!HxVWXuKu&<#GCT<<*MhY6fy#PhM8ziK+X71p%ToBb#o`?uR^$Xj-BP7)_PMkyA%qo6aPjTSn9 zaj6eT-RS_Ww(&lvT6Ta|WfFOi5CrKeRs!YT04EzJ@sqbVc}jHt-Umwt#E&DiSv9Lr zW<>+J>vix?%yrmxm0cdb8y>2+P-$$>8-kE0Z>%Mh1@IQP#oIe`%=kKi?KhHp>l4Y~ z%vN|6A}@>un?ZWx;ctNt;MgP6_>`bBTP2?2zi%9%u{g~|SxnsDUDdFhN?Rrcljg&J zz^V<*$XSoIY6=;-B1kSzt9*AiJ(XZM6&b8%k>O4nMN7U|4sEalSP797L zM^yT#5w6n%W@0k5+HMG|IEoe;1{z)+NvS+eWuh+bkJ@!*8Kd@L{Co)_gu8hyO1I$L;?tmE$J7$@z?0d|V|{;qTrQ@#C?xm*i_u?#p$ zVA~A?omD6(l{H){Tfd&=!v7@LL;*+DI}u{)cuW0vEzFJW_{cG3*t)FDu72QJ`&C+I zJP~%g!{u)5Hn1mDNe$(wz+(np{#@lqBvF4Ej3H<6O4IUdlr!CF4?mY)$%f!P@ zCRJzD&X?dY@P^yL7mUX%+*ARYb)L^!<%4}0X8;M7BR!cfRQKl~0OcfHbu~|~>H(@u zga~X^`0rGl!3qmYYE5#OeUPakxMH_j{>(C86s?CS1=XyeRf-04r2#yFEh{ry#}r$k z;RPD2m&{;Csh3Kz)5Q>RUQZQLgz9!Fv?4Q{#$=P#Hcwi_a;m=K2l-c6q!hYTS)=>p z!mM#^>98-z8Lq;hWaea;t_%k8`4XeG<)E@6#+K?diA11-Aly8;If3NghlOR**s}^n z3S|pm@)iAG1PHm|zeXu3QS5W1zsB`omz^2zikppVS4a^-Knu9`<|G=ua~N@`8ej{u z=qEH902)wf1k$n z^@;J(^e(AJa*Nhxd1>y3LV-L*gt4LcxWDz%6drI?FL~nVnsd?6AH81@I(hqb3{|}l z=sSed<{&n1oFI@Ob-O-qP0PrTncqIl&rv?@6nSAUi(Zf2JDIV4EJVHEu%q(y z|IQAE4o?EzXw{(Y5HqBo!q_+n{9uZT-5fGuiWPTAHv3{23<_sb**P)?WcoSAdzeYZb7gdKsDd}ngq%Z zR28z)3|OY>bsLE#7I1Up;SXt};j{3mScyq7lGf&VLwE~;EGm%VAiOH#li#vhAw};X z!Bj1%pCqo+1i*OcT`-TO!AUq#fWA~lr;|>y|Neof>vWmkT635a4 z(Y;_?wL&7-O1u5#O*TyM{5YY`PA1rhbcTkT8jA0l;d~F4=67y_DO~>K4#@}sGY^sC z6JoNpu7LI%UPTbhsU&Dx8lsPh9WpBS1?4)%2HB!<9bXa4Z~e(K!~vB_ZrxHjiAa?^ zJCFG)+F4Ussh~e(e$tpF6488Y2nmmjNR)G5;104Fy{7QK(fP($GYz37)T31)NU|A3 z(xe03lOv)dB}K+|wj(r_#@ezpH4G>H$8+b`XLp?LB;86r4j1-ABYVdz($^5yRq}*& z2Z}5WPKMa3*PUz>#OpZ~lM`Qr54J#LGVgX$YfbACoVj7Fq^YB8ldWVQ?sq>y+ zA}iY^i~0%~TiuuqxFa2RQYVjMs&1hmJ}d%NQFurHR#6T@L{`q}EA%jhL6&OZOid}l z(W0>{uKhIU=t`-7z7{p1VBU#fB6BUcOWqB239ckIfg0gNsZ$MH@6i47-plGpeWvXh zIlZwwC>4D@!y$+=(WBfobSfb}vJ-4Xec{XOO~8KK!l+zAvGUZCZ7?3uGGt?fbfQfO zY+_#Ee#);}=$1*Bn3*J4k~EtqgNLD7(7x%b-;hj-EbE%3oh+te1}J?CR@hOJQuyH_ zse(Hy(f8DZ6kJqz?0%q|o&8ATee^XHVizQhm)X2Z$*1jprnI&uJJ4>jSZd|>3Pt$_ zg&O})JG$2+4Yc)naxhIz)#HFTCVwb%{Z~%Y1+hJ6 zKD$CmPo4gsuL(j${)%p)LrG2t{|I$l@PFm98~#Fvg#U_e27k4@T7$d8&40^u0Ajna z!hdtPX3e#Y|Es8}|4SoO=jLGhueb;NFZ~ae851r2AE6Et^_gdwgqJTx&abCnS8xrdj1=unS0GOf%Z+>EiQxS~NUWoW;9FN9 z>|*S1QR;%d$8JeEW$oZUPj_V%3_lZ^`*P%(hBF5oKrLW^C|}ju+pXr#uBJW2WB-vE zYh(SVOy)h>v}ia5FDgAK#de&$W{_2XoI3fNrW@#Q3r?HCRP!kC8 z2&0@X%H2n5|E^Xa1u%2^cHZR~IF;KTcmesEAwP)w!N%9UGxzCY#)#o*XEIMRy~h?MV75XQg8ks;AZq zen$NWdIvmxle9nU0^s2<@6Ti#aUQWrVec*RtJx+*!-6uGcPGA7z&#Bh-@VA~QV?d$ zeg$k?L#IM3A84rUK1jnHt7;`Y+KGEgpG8NlQ)1IAEgRv85tiGrX>&aB06F?G<(tP< z5#@%;E$}6D(qPza#sasF(^+wmMLInf_w0Ce|pprH*A(74I{3UvEb7Pkx8oQ`NE-a}T`DyMf zGdD;?>H;?G;S^>LGP!e$ze>V`lVeM82>lZZRE3&9G-HLBpbOL*B5ZGFj`Al{uCr^R z2-2@}_d*6`8f)h5snvr9lh_Q4n7ajl5AMP&MQcJ8XRb?z>(RrW+)$bv_kt5z&iPW@ zlN%szgQZixF(8*~Y_*wXoxK_PTa&Xre+3vA9*+UZZj-i6x|Y#9+S3rXJ2ovCA~Yf9 zv%9hqy1To(f3n8qVwwJB#55#2bZ#9QR)z#oPMFL*J}WD_=xFXX!H{pgafcL2RzKQb zb^~B1iezo5t~{t$yVMFGXV`H0qCCA&e0xzK3&ql|f%^&Z8c#H!WC3A?=NdgF_Cy-JJl`^${C z;3b=IZQVe+>giZleW84rJV5vwG2nS>hu6T#nex^dF2cR3Zj#$C{j%+e24CPJ&X>6s&FXO5sCs* z8SRvS^`TNC_5i(|3F@P@$@6>@vl$IxE@nw82cs*EH=|(z-794sA>5v-fPU-IM9;87K=}zfxVL%#TlrH&j9nboH z?^=8Rdf)qcuIJ*Xig<*)xqm=wB2+n?{#8iOajG6+WvR8}CcAR$|47ghcOE9_Rivo6 z)dwCQ*P`2v`tDWH$gioU9Yw(c95@XOE3Q+)hPSr;rrqvNPp7RZV{J1Z&fF(hRv6dS zxWa~&J4&;ynhx`GmuR^IvfDj_?1{~NOR!eDtNXX;b#wi>v}2FSqwROvO;6gMOk)Vs zXEBnt@9uC>;{7;bZ1-j@zFOKQZAj(mu@&SB?l7wbx{zt(5fAG7s~W|g{7r=3{(gO{ z330Uqk}4)*5MJTu;W#Z z59dHtacQu0|7uN0wyc=CGvs|DRuGjG!n#IPx3W3oE z&ab9*>fJR=P}pq#pfv!vJ^0z)Yuq10ptpZop4D8-*#Wo*eW2><-!u1Ne$U_tE#M=o zTI7;Rojz6x7MvaPzh03FsK)1X!c$C0?TzbtqvLr=BIuvPOuHqEHygD+aYUxT&_JMm zi$XuWBR)&`WRQUa=XgkY0=_nB7|Uqn@H~!!+EgkeIU_hyNQ_SC zk1{lhCSHCewi6!|OdX{wm=2yA$_kx`JlFs*WkXYQH^L8hI*Nqrky*X9>-TqTjFQ5p zr$tP~%Kr4y*#rnv0#`5rMzt$>KRO+oC z7T!>6EKVtBUi>&e=JHFA!GP9Frf4Ci6;>sUB-h^+AR2hPw#19d4{Os)u{dqPX_-7K zvVdpr26kBza-0bA$d)YO^r$`z2^LQVQ2em^L~w8x1@)O+90%EL;i;aw;XKhuq|LH^ z4dn4L{vL92A*ZS&72dS&?NjktOB3Y}ttC6w9OBLIosnF^LbZJ%p@ff*ocDWUJOy+9 zw0E4n>+g4JXT2$n_J-t{zEkj>|=M}6&jdWmXajdJCSnf3F}fDrl>c8QYF zt}|4^g*<~%IhI@J<`?aRg&_^7xGApWlvG-$8)=iq?3~gT3co|?@-in_=K>B?o(XNY zYb{NeKbJBT0j$Q}zj4X#3T6=3@*n^R-NEO2e_(I}nO6n;t|O50IdedVpHMhG3>Jp< z1drrci{)eD$#>^zhz-SLHX49eDEN#OwWepvpyg;oiH)Z)osdyNyM!J7WQ%^~LbY-a zCl?Y@9l;NnVI@rv29oNJuEdJTgpmMYqGl!7vBshkp8tHQ-W`#T) z?5spyO%pCR_vIMuC2*oMUI2_J z{Yn$k7mm}UuFX8MA2UE#k$V!A`QyXLm@gTVy}TkhQ9B_!8MlIg0fz_=(5)2`g+%E> zwhtX#y*zHnf2=V|kg!5a(;y9>$YO8cvtk$+^+8P*np1)REdh{X-Qljtn7oALP<&pi z6DJ&>5jC~JHnf>H`FdTP#J||*2q-zo3772MSnhnvwlE0EL*m%U7$liUHG(7sTAYL6 z(Ke>4Y==2f9HTH&F>H>>aeM52&nsHm%kNi-f*fxjLhuRu*-kjDY7RtQCO3NK91>`a zZ>c3UPz&)zz6BRZ_H8|mzeQ@VTQHq@6m@Zc%!=X@Q$a@^-zDi8f~hZ+1zeqnOTn?+ zaZM{fe)KtbSq3m5{C1Z0A0(}a?(Um=>m9oku4y{q*ZTpXr^Jf8NJ%q_#8kq`A*Wqt zjKlJ=jy7@Ck>(P23Abrs>_04d)J`1dDmI_VWH>aoS+ZrHrYl9-bqZ4)$%2he z?0*1JHCsIk)Bf|)kXJ+!H6`rc?u4*bx{l^&m{=H8YukTK)L9wUzUz|XbP~kdM+D#` z4etzN^%2P9h3V@01(s0{8unh%&k%n8!8zOolQK=^E@@6zU|2#Qr4t`<87;z^$b`-F z0=d(xNL8vxs3MgXNI)4g0~Cy@kfewSD+}JTXZc6e1?_RfcK`Hy&||byQXFD5H=KVW z-nf@BM6c=MV;#Z|RSbV-J_Z$HJOR9s@hhHUd$<@TMe#sd6>TOm=R7KzV)$OKaSrz{ zFtdXLY02VLCeDV7nfs>4jxn>#9u;`gckKex4vpfG_Ats4{eGala);7OWp}fkMWC}U z7YRY$o_vtg3X$SD&T%XPh@#LTGtUB(n2Ew>%_) zbE9-W*PqahPkfC+Bi^&x=2-V>70Hg)wbx(Ep^A#D;yFcRj=v{Y=&heiC(8Z;<@~Ng z)vHXQdaL0E4KCuGvbT^!i(sg_eZ;pcW^dVhNXO!cnh z&qRJ<`%3((w9LGPMig~joV8m|UZdsFT00p>K&rnXM6)!AB%F`x#_xqlAJI+dFuc=N z#!0xINX8BqmjsuO{QEZxDmpOz>^GJECIK1|K$LfUHu} zoS$_wZx5)I#4rWU*HXe4_G=4YD?4i}VLcI3MRQ4@ZT&G^wEfy6OKm&itGX^rwZMsF ze=khyT(H}Os$b4xLiT{zct`F58o$o;rG4q<+{2X(Kz+#26uU&a8U;fP-82LU>xvb%taW9tq#8DMTm&K*5mf(J*&}gz<@km`}NklpkpHmjd3}X|8 z$GmuS+RJ4CMZuXV_bgaBm8-2d3Zn+H;ixZ#Zh0%qZ4?II-GiKCL!Bs3v}yR8^eOe>K$ z*W#bhS$>thG8&<(#)E+ig3m0pjXRov=%H}{+T}L;t(1>20`Dl5`jb1omb{W6u;|1~ z9Xlu`nI+@L)Agy$MxLZ?aY9lt=_XRWoAmUCu%KvB^Sjhv=428*L^&0X<8dxuCtdu7{%ci+&_sVMvOAeMi|Rnj!8!Rt z$H%KDxdd^+7BMIHg3Q{CAcRB)gDDYtC}lOVUGyZ^MD;hOF~WP*P}*}!|M|?H z^5E9+pdlpJ)ziq+Rnoap8hhlZ@cT2`5?r)~-xke{hr^<=t%%UjvVt?QOOZYK00hR4$Q{@{f8-3!886JaoGRpj~ z)1)m`*D9tMKv3m9npD;f9!FQCU|SUZ%p#eNEeuAhQDTwAZhLW?O zBFFmvQF+_m#wepe51X5{?_!uJ)o0=80eEvrf9TYy84a}@0>NoQ=S#`qY1!hdA*A2V zafW_3vDjuoXcwxg3xRbngE1M+8J{`;D9}H%{*5|~HNVTaJ_pDEBBc%sd&)xW;SI+^ zI~|qGP9jhiH)bL8i=ag%UzbF$z7&mx+`-BTAbn*nm}M3lkv{9whGx!B4MLe~cOHRpz*!o3BaBXq=9diAu>Dt7W*Mx>)0E>sJuhS5nGA8qrkbPA9KT1>GA_CWpL9BV_nnd3!-1lQ~j z0Gpc(65L_Wv2cB;bcttpI!ayOgZqhuAHmzP<4K<5sVcY&70W$ELZCZdL&AkjMzb6A zj>vb)5!tv@7vI5C*f|NjPi>_b=o$5RL?E+FgTG`zsxlbx`IbgcBNrt#!oji<#Q zW8L#UdXMR!i}S47IR8~`cIuHcg8#+R4h(-)8wG^-%>PsyVS<}6belhXAwxm`Kkd{E z1v-iIPH^{3d87YwCmxcwv>~?YXXy2uTt`30=)%<{*PCnv+_4PP>KG{QB;Db4MK{ip ze`EHInCJ19mNN|DvCR+PP`?PCO{?jFjUNuF5{L+ayBzD1W5NHPKvNYiscRZU3b#50)3VHwl@YV!yf$?wOvn8jz z0KlDU7kPOdRe5=cj<=_yi<<)gpc0EpCorJcq8{jaqY4G7#Du*b%Z7BQ5lT{SO)9D2 zM3UP<%D$)cQJD~NkMoAbL+(`LB{x8*-Qw+fJ7uZyo>}t7#0@cE?=1#x>;-HrgkCnL#Q zYRCr$gK?aJhx%i03eqk;vtqq9`9(}e^?$+WK%TbgqPcA=H<$Ns#9`yb6JN&^(^8oa zNR^G*&(YfxFx)j>wH!tmTJHMs4Qr{&Rbk1Sj5R80G_u+c3s;{g_&za$8ky%ASy#}i zV->Whh7SIYP2M3AmGvgwI$Xb?;R zqs!;R#^`c&{+t3Ay9q$~($q0HQ{B`i`O`LlTD)P76ZrlRWlb_TO~2OE?Fa)lxqt7g z9m(+Q_z-nitf1&IO;?w6KqG|RwuD{jICm^K9y5v|p9DH9o<`Aom6U`60A{=Zz8!x( z)lmatL9ZSh^1wzq8DeLkq|MzCU8j?7Jpa5zTp4f-$je3m8%~)FWVN9)!JB%4qXTM; z$j!sq#cRlWcpqO`mPZtCG+Z4^-%R*3LRkhMznkaLU8x8GaKt_tRe}7IWZ}nvH?mX7 zEI;u7$jIu5QWCJqd`SkU%2nnO>)bQIF_f9q!961KB}Wigh6m;m=rF=T9WOe&!5ZOo zAK2Z%U!xw_3f~7Se~QOG_UFnW+UU(*hBf0$fttEEu&(=-CiqJj+nZBT8E>pgZLA@>7kRd!xXO zr1;+A6{D3nuQo4qGl>OhHKRzx!0P0_qrWi{{5Jk;KMi*HAbQ{FplKiXOdAA~&9>3p zXZuW{5Gf(YwG3IlyG*@|XPZ!_0*4?dht=Op6N8*&^1su8RK*CbJD zLPdc(tK~BoGzNb=U|WBi1FtnA3P(A-=7UkbUcTaT`X>hZ6h|5R3F--Nm*RT+dWQPx zI0Z#{MW|x+3VokiUs_y89E+MS8&5G=@!ev(;umVhukld_LobJHM6zDp)23W zYUR^{!%c?Gi!GWh+`IRCV|r|hU~#n3bPEZ#R1Q?;C}-3fH;TK|@Ucdu%ixbk6K~8j zmEW9ZT06Ro+I=M-nT1!nOjVf{R@50v8sJq#RiG>U47RniJ|H|A&{);J%m7iV%~C1%!R8QmbxjKr6ac z`$#1)F;F#7{0MRU^0?vn#j)|0$hI^bLN7{RM7s;ygwYDYVF?2B&flMiIu||pEHEVW z1Rm(7A@p3t>WPItascGC;+*POJNKi6%k7C%mYtbPVDDYwIuVMjJ>i`&#s62=Go9|dBa$V!hnc?i;d&9?N)EgN=pY1 z-X>5}um{ZVfnRmds|!I=NzzW`+ySCSLUB}Z%vJxyxc*AOCm+o4jBA5&w8+ZdhUMnM zUg0jqn*Q=3#&}v}rskP%3V z2u7R=Y9Q$C0rt$jVEihQ3C&uf$B`3dnv;JUNgu^ed?j?3hML}p?nGjq8)@io43e}{ z{oE6il=3#koS9LPPFb}OQRwoGsF0yR*}mOg&F=9>AA92gt9bn+ivEVa)z!k)ofT#3 zGv4nlHa7VSpCY18spz>T4 zf1pO74?YtM(`u25>1+1Zntm~#X;E{UJU0xkoT;pwmT@JY z_N|^Y&+d9|le^SI=O;>@Mvu$l7Y19?GQ_RKZl=^}%sBxt+u5od0y|b!Q zp`5j;C^TsQB>lnr2T|fuyB4*FYUS=*ilC*0dJ)e<-P^HqO?+K!zS&SHF7@>QSa&!&7nb zT*g<%h|wxEzb8L`Ij_Hm@72@7dauo~Px2z9L5v=*FwY7PM`4-=K;Fl8AMe_3p=N7+Pu1DE&lST*LcKI;v%ANLg$7ewQSRt1BejZ?KWp3J2&uc+Q)mkI8zzosa2Lt-XgnR+5%U?onrfL_Y=SN<&$m4HyIy!3 zqB>dgF0t#30qf~# z`)GniwZ+6Po{*Z8>u!QM(p;8&7Hw%q$=sAYN&vO}@`iJ?& zY4@+B0m1%(J_|xWZ%Uc5$h1^|fAlCJMij6q0)%jBpa;`{n&{&+&~;+z`sSAp1bL0P zVS~>ApnO-cR{8?fLjZ<wIWz`#yO1E(P`2Ub0xxLs2h&BIpc4o43 zcGiTz?8*ZWQd;PBiu{<-n+tEg7gZ6}RnVTWo0w1l#ti3 zsRNPs^mc#<@x%EaF-zTnKp+y{j~x+u3QGT`-+oClJNx=RML?l`et!IZg8ZJ|PEY|c zF)`>P7!(HMyN%%U3Gnc>_2={OVfj~*|CdL>!N=a)<*BcWrw8P3URyiQXTFlm%zq30 z=lNHk4*o9xt>oeJ@3C$Ng#JaK0{oAl|H*wzmH6vL=(zYhxS1%pxI1|G+}4nS2?5xOw<1|;R63l{2$=oL<#8M{{Ls5e=X}D@9nmv?nprYvw5jI40f?t001nk zsvv9N58TTk(lMB(3zN3B?dv0;G`(j>jic2)&RIsQ?gS}vN0d0wev?wsKctt}ob{rQ zg_jF{%t~0#hwEX41oSVhNcfS^Iq;Ep`A8mD2Kor4XU>7SHHlr|ImI%m{M?ppafku$++vBVN zF3*;S$D+bS0RaS&VT`v3c7W9SB|qpha8Miqy3D*qq(jbNzDy7jnMEKIcHkB{JLt{G zCjxo_ev^QK((DkD?j**}yO!V>++;r)@R`6Z5^}lkuqoG#CFu#8I^`4(itz|)64!O1 z!I8rXQ-FmcYcH!UYI#i`x~vG}a8}Z+p2ie`34x4|JWWg%I?!w$0m%HZ)uEtu+xa^P zwgug)2~0l4ww|{k?VJlD$;fb4DKhCLP!3$f{WP=_Fx6|8F8% zbSe@={~*kg;@@3Tb($z*5nja&_CV%*8(}MFK`9(DhdChH=Fojya&eQDiln zoVj2iBa=Vzp16jV7P=XX7k}O)n;#_I5BIRqs;3# zPxiPB5G&pd_J&3u8u6@gb6xG{Ouv4e7#Rj7qkZyz>{aM_lKLRBIPxNXnzMoX&ynTM zMwye-V6_kfVNStwDQdAcO(4JUYo01bthtYqb741a+LyRI%gc_dT15Qh&i(AxRR*s2 z+K#_^#WrGvv%Q{-mWrQM%&q0|JS%){bbXJUiuR^Q z$E(Egl_vn2xSB_06P-M-3`k8-2k#t?o0<*+NA)A&U0){H*y_bUJ3v9mOc`x=Vg-JO ze&=S-PdN@RPo{jF&W!%P^&^2*bkFiubqv!L4`}IP z=io|-Y$4S<>QJ`9J{rjKlv(u(L_J}8T1f~3Rq8ZUz03dVOSzjfb1ck&j^?pd1-kG| z5GHA}{Xs2}RnOv2CM}$kY%o9yJ}B^kOUU1xK(;(7%hFja%C;a9@)wDrk&V=B<~N=g zWTl#ryU`ku{zjF?ah*5rn`^_S#2yV#Y=SoH^+%-P^l;ufrv$HuKHy4fLC9^XEB$xn z2lyV|TeONk9aDO{@RwGi1v%MpmXis|T!2+n4=ro~|o@wta$szuk zVC{m{DXaM-U%xw8{u(97e-L}M9dI<#hkd=T<9Uw)$BN^T!%v(|seY>L!X1)X9y7v` zoUf0SQH>%aj%KXBhC;2FlkrN&;L;w`76b7>79lUvykdghZ;BwFPS5;}v4>BF&0?GV z?&+!vHa~wV{qA8sEO@P>PN~j%t1-$#<83vBol-|3VZj*^p zkt`Ma_FYc>kid;gJTKB9SAFIg7B5kxX(D8cvgYSpzFB&$Xg?iQbIeda**+IezO|Nd znkmm@&DERHM3StfS;e}n9%h;)Ath~h_ih|z5&b`-0~R~Z%k!gh!7xn1JXt8CBb6$= zYN@*=smj+}8f(2HTzLeE-Aw5_*-yBS@-V1479?61Wl8&GrD5a8X1QQ?_A4tSDORfA zdF3ZYz%+@al$vL|DsV1Q{?83ZT9p*hdJ^Z3v&!YR_Loa%K68@y-hbWieFtP|F7>a3o#SjzZRnRaM6;*pw5PhY%M7FTj=fc)rl_#F8bFgG&z8ZhV%3n{ z$|%*ycr4kz=B>mc4_ji4vy iioA<=W&^d$VMA?qMAQuh1Ihp1bE=A33e|Ep5&sA2n(G1p literal 0 HcmV?d00001 diff --git a/doc/workflow/web_editor.md b/doc/workflow/web_editor.md new file mode 100644 index 0000000000..7fc8f96b9e --- /dev/null +++ b/doc/workflow/web_editor.md @@ -0,0 +1,26 @@ +# GitLab Web Editor + +In GitLab you can create new files and edit existing files using our web editor. +This is especially useful if you don't have access to a command line or you just want to do a quick fix. +You can easily access the web editor, depending on the context. +Let's start from newly created project. + +Click on `Add a file` +to create the first file and open it in the web editor. + +![web editor 1](web_editor/empty_project.png) + +Fill in a file name, some content, a commit message, branch name and press the commit button. +The file will be saved to the repository. + +![web editor 2](web_editor/new_file.png) + +You can edit any text file in a repository by pressing the edit button, when +viewing the file. + +![web editor 3](web_editor/show_file.png) + +Editing a file is almost the same as creating a new file, +with as addition the ability to preview your changes in a separate tab. Also you can save your change to another branch by filling out field `branch` + +![web editor 3](web_editor/edit_file.png) diff --git a/doc/workflow/web_editor/edit_file.png b/doc/workflow/web_editor/edit_file.png new file mode 100644 index 0000000000000000000000000000000000000000..f480c69ac3eafbbfd85b30ca0b1324d824f89409 GIT binary patch literal 89039 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 BY?c53 literal 0 HcmV?d00001 diff --git a/doc/workflow/web_editor/empty_project.png b/doc/workflow/web_editor/empty_project.png new file mode 100644 index 0000000000000000000000000000000000000000..6a049f6beafbb1f449e43baa5925b10d31410cad GIT binary patch literal 122296 zcmafabx>Simo4rz?rwnq!QG*8Z6pNO1b250G$FXVySqEV-Q9w_yFGs2ys4URYTmqC z_n+Ipos&C9Z&{@AQ|B?xL~wu=pnoG1&6}oRCB}NRDsky z&cDpwtgqNKOtf`6CTeoYVM_bqUu2wb6OuFdCb#wG`g9*qJfM->wMFr9zuWz98|lKiP!qwwLKmMpY*iIO zRK{HDl*mU4kVOQmK$g;V8`XDPHFlfeE!Dsf#0Ox?f)VtYNw8(RtXL_#w9$fw5QK+d zfNzkDk4B%w9UyMwlLuz-lNN1TSg}9z-tfp7E6^B^ z$fXJOljKc`VBl}oEt1M4X&>@#VPK)b4uhLjtZqE->ox-k+ZR#2z_+RYX8Hg2Xx9d1 zwEQ}nE(Is3Oxc*xHJB03lSZC3Z1%I;BB+m|$Ecy_2%X?S6%lDqZPvUFB=Qpm!{1-% zA{f;NfP8d4UA?_OVOTL7tWsjU8I=q{0|}rLc|X2^;oG8U#z2JR!=~rKrZi}Tmc=1I z$i6!a`4f#bRlESg$LW|n^di?Pw+m4>bA~FzZlB`a?f-WMU95KW*l`K+>mmVGNHo;>n4eOiB~wZI;^SE`26M4Z0)C_5U>-Ua zFv$S|{Gj6aIOB3qxAZ96{I47WH)WKnhoS1yM znT@31QM*!>*|SeM?>`!IjL$JxcUuM^I(lA8nuDHL7PG(Q2nxi04zZZ@qG<8rpczConr_W)kPA^u;TKO*ZdX!=kJY07 z@2geJ^7GzQqnopU1C+;8lrtjkx-&Th*0gY z@KEJni|5X^;2C}t<$eDl0|UcJE)AlW2vMK^(r3={6Cxe6ik}Wq z3&Ne~aK8*ARK>5iLj0tPVaj{weM*rEyDwEB_{LXCHGb{GztGpZDgR^b z{~t>$!l1zm4x*0ZSk&1X$zd#Fy=J|omw#c##o)Zn zEmfUp0Jh)eK#QfCf!L{Hs39(9Fux$XY!IUd87jq#yW!|&L7HDCk&s|4B?O+jNa()M z_YCO21|x~ZT8vGdO#q5c)X0>M;v8^3@tJvv8OFx3tXP3cN5GCr&e(iR6^#X>SE(r8=)bIf~nA11T~3aArx`nVMlqJWwT* zH3MXegJo-gqMTU8ds|br^o8`T+=|hvDR}ayXMFc!Vk#2_+NCd21qilDS7f|XFD%Xf zkGl2G*6Kw3pSmMOvsH424a9vU2ujLvi`v>C!U5{BeM<*F-_ThEmPZ#K@#ZZEkTlS< z*noU>kY5>m;6`y!))8Hh#icK>{OpU@CVdU)g>)}y0eEphHM49SnuqSNpeZi7!N=_;^8TUKc~A93f|&E%f_{E#|n{KpNOfV!w&5B-EoJ#)#A@)}<4w^F*jgt*3 z1>g4W=1!w$;VJ7WhwPTl8$Yj8ic|MfvGF0*gSHdnwUsqg2MDu^G}t(-HN)2v*Fs2p zX>%5{0>sKq$!C@v@-L&dvR_tIE8L}t>M+c|mS%(KKJHCB9*u7a3Bguit3!{gD<@T1 z{qHWV;px=zS}T7Q?A;cPh8~dq&0z_@8Vqi7{0ssEE0r6L)}uy-|2bVH*aL#e@6K6u zKOz41-|6X!Vw*y%}{|Z6443I4B0dKsATd3Dg`eu-Iz| zqa5NQ3#Y@x76Wj>I4ds1Vj@{+nA=!ccc}hAyq^og&j4SmrTwz@A_!tiXCrkA`$3)? zQg1Id^TvDwse6mm=iY>Db6A_-R_ z>Mu%@Q%0=NF(e+$!s=~Pu`V7No=<|Ok#BO7ENlYUjfx$J$UbibQf$}S0$+Iqtg63z z!TuY33#Vr>>~V8DRwE|9dhEvGfyc?(XU<)U2tvk750n}}`}HrN$5G*D_~1xaLMI{P zjB|nndqNLO{HH5K?pF>Lsmiw>^Fv8j0ly-7MRP>VM`B=XyCGL1HVieM1+(lRa$wtm zz{^R6#2%1##O^V-(byZC+Al!Z!5ofj#NltB^Z*J1hHOv>O9&;lmhFQle2*b4qa@Wy z4os_Ty-bl#fIYt zp0O%ROv85hx9#~M?(Mska}g7lPZiBagc>A@Axklv7gaPCmp}e3=s3!|ekAI`*^=as zUi_2t$0cCHOyGT9ZxBp12y+bL3&hA!MqGpA11`vJXE;nt|#2~;3y|@60OGs|j6I6C@OrR|-ExDak(?-Ro zhfmkY78UJsXz~(ZTOXL!X^rCh9tI_GSrhWP9E2<{^No7E3!bc2BlF)An_W8XUsUzQ zT;lv2KH(^V0E4}ZKCEdC%OfDrIKeE;_u;9VZm`2(#ewWJ;>9>Z4GmGChs=>53^a^? zN)&pn3M7&swG6bGv)>w0{v3iJ<0PH9bb$pv*BjhG$1cPXM&8V3#U;>QW<&Aey3)7m z$pDtJSImBZbH7)6%WuudPsOS0TQ)2dZ;akx3QkU(=;&w!G*{dy zmjcq2Z1qyj)1JS7F|qOR3}*@yG@30qxop=UgL)3l@_xK}-zL4C%-{?Av>}6ho(>D` zcupQn`v^}~+bjDV(|dby<@&$;kWNgS_b9#jzU#p(f zqaD{H9g`mTrR^1(#dn2tRIiQnan1w*iIcd@JRNC`xeaG+q7xAZjv>w>AYog_D?sa z_QyLbb^~jw1~d~U$bla=MC!-e|F3=S5lLN<3EQ%*JE7trwK{a~KH$i6%>>SH(6UYeB4drpty+Se{eqL`sx+oCmXd&_FX;rtCPb(g%6zir=zPFn$bB=6@L}9)ZCyZM#E)f&yObrP_)X&eUwQ zzg&%k{EjrXuE_K`!@&!)8v6MW-Ty<%{3p6y{)gy3dJ+r+!l2#KOVdMUVgEp)Cmj?? zfJkDTJm5s9aiG-7nn9{cvVa7WK>GOD;WH@L+93W}dl&J8^v>Z%>2*!zKu?1&&y{o{GEhW*6Nc2aNbxyD z<|sh_TX{ELr!~+f9R?oz;@9zS8@vsu_k-_?G&HkzJOe8*0#+q^hNWBCI)N8C)l{s< zYxfrixsjU@{O3P2E6G0T31JvZMs_1Jw{`V@uU*MPPg9Uz&CaG>i<^o0vAbF=>M=R|n>Q9jy3oV{cwW zDIryKHJ5)_&vfAN_U=wnLxV2O8cUHl*n>(|PU~L4`)_&X8%>?o7e;l4d=|07nQkkt z%(wK5FAlH~^q@6cLIBQAjyi2uHVk$S9Fh!VtJpqF2oiss2j_8ESsl^VDNDD$VW2EW zmq|&Gt{SlTD&TaGG_G791ze7V=N#Tk6-%3&Qw1T+_y}DJ*y}D(>l#=lkeY0pFPWD@K#f=;ai1EUyf@?!q;t6ERobq_$}-tNU>Vk?%OWkEWvN9@9|_j-YvW1Lg%y~5@7 z)|Z&XVkltKwk4@P5aq(e51(##ozz=Z95Gk}J>{*bl?Xuzo6qZeJC`^(JS-+HjW9h8 zMVxQjdx9!=b;5b*H0u%T?r zmOeM|nQY`mAGg=4A#HA;2mU_>Wy`4_Nw=Yqy=R~oho>FsW#u9&Vhbx+@QDx7WX9+zKzwVRy_E^+r^h`C5mH7Mn;|sVU zTwPy-RCK~-{h{r51~Kvq3meBE2$zt@WGsl~R(od0GvY(C&1Nl@hD$}cQ&zin( z0(s&x(PAZGWr~A)vSEn{O<#fe2;F)B=6npj0;D1aWQ_8ftlUh(wZCG@%E}=(Kz9U` zLxk|}!1eWV>5W$CUSs~Pd|J|;fODVz^HO!nEK7kGU@r?xK68mfWR_dW*inT zH5X`Qx}C)my~k5u5N*fYNUw1j|Ew~1+D^47d?Cgn3F0Md58p_93DP7RH^1a!CiR3o zOLgGa^WGLL)2i+0JI?&gu*!2tuj4uQqse0W)KpN-^6W)`_tYm0p9`N`J|o~Nbh!3O zZFs<8O~Ez0q-C#62v^AMUqE|u9oy`SKps4k$Dvid*2nRM)WjH>IeUmpxcd2ukU#5M z>R~o=dq21}L7BEfz2gex6{OnR+amx9p7h6C9$moDyFY#m4i1WFYA$usTYD01L}dff z2{A)kTC`qX?}ZgCW@w6$TU;IIIkj89Bg&il2P&0M8Jq;+anWfuIPL?!o9@3bIQs4F zOlAXhMNFRWP6x-vUcdGK?NBvh^M1OK{Qe#L8BnkBrR+pU-DJK~sG-&xLK9d>S(^pn zQqN4TLitKnIh3w;y%gIj#l)bnJp3->u<(M~w zIO0S0(-#+Y9P-ghTx$osC!bcm_fOsa{-QOkUCL4^)=5t==e>{nai7|;r;@lfld$VO zw302!P#1Y`(hhcAcPEprKZ~>ZG=VK3@ZOf~cCuAmrNKBjKGX{xDYTYjxg*~Ngi0ad z6VGjEsQ3yoxUc}f6T5oM$Y!(d_A&W${S2Y`=#RI$t=gq4eXkT*YhLT?Q~3Y zvHAI?i9^Z7>AdX{lQ;)DQsAuY*}bB_83wIa47jsk3pvS3-|9ltlF{3J{4W&=wk%bT?ikw$&gAN-32}x8<9{#||<6HVkthc3N z<~0npIT>MnFfVXf^}8vle`Tg{08mWHLu2l1JRLKGgjiw)ZTt8Tqx9swNiAgo&?@#f z9v)6r0eFg)M9C!9x^F1f+g($d7CME)g88uV5-F*+2aD6j)ys)yDBxb7Z(Ymf z{3707Qbo^d40|QX>F6$+3@4pqqLZigdXI@W2$Wf;CN1hJyw5i}(O_YNPBuvRUBqQ% z5VvVhzaW9vA_JRwt1P@U;ElskFW3k-&snJCb&<)W0DNw91|lL5D!F0?bUaY?V+iqD z0dqEz2GR&67AQsavY=*CWpWI zP5W;H(Q^Tf6sAz54U(BN_2W2L%-tlhwq5z(RtL$i4^d#B;cX&cjGQ3Y=JfRrE;j$EDUSK8aMUH0=~>@?{=1 z!SX0XF>`3;)g(B{|D68&4*&V?MBGe+r>BQXY2`w|na`R?NRE3p;UcPH$*?z=l8$aM zU)^1TcX(tt`Iv>82Ok3iqhbRG56^hJKis;YOhD*KXSu6u0ajlxyy#yLZ(EZt3baFIec*5qET?5an=lOd3!E!rM zC=ycCA`OPJ@>G?k^}}oA#u8h*$E^`dw9!x&foiCS z@tU*J!sE@VqKXRU#}%EN4EbAosc3py1wRa~foDxPO4a3b+4&SeK9A;!@{H`LT#Qam zUye{TB-PfA2@Qj+0tG`Y&ajos4b(IZ>^bpMERcF)UF4lE-(hE7mBAZqjh^ripdiP_p1Av-9k=kqzlSutuN+dV$9 zH zs|@pzTuFeGm=~ka+X({-QA{vKFPv!Q!uCb9e8RepzO9(WJ9Zc$FNzv9H$N?&@aQuR zTyFH~!o~*Lf-Wb<^Bvir6z6ef^?U2P!}zeNTuE{s9`w1ndX1(Br4!+g#?-_CEdrkp zVdPyiC~VYfVPZz**-+#i8wMV>rtUUv)k__^s;n*Gi{r{zY!n|kY&xGySI$NP+x}Le zW2fZ3jV_DVAUWfcHzMD_K()8+at%r6dvjVj9u9$jwXq~6Qv+6hTJO$_BW;%C6s8WjXYZ$9;EPG)38Qy^;F!<6OGISf<31`728T%^hzx)({RZ6~v4gj|2P zXywjX*$zbdiazeEe@cqh!eUAQcxbKFXvZCxtjAS~17@#Td@L<15h^~kaDrq;$2<9(#*-^_A>)_W zMP7iu&$DshH4z4Be?{-OeflE2i>N3@>VOns_1%EDRL%8lu`0Zap3CVHxUZ8rXwCvC zEzMHVc7n;3SsZnJ#7PwO4RnyQKud$7AM&rcW?DW{9UQ6Fs0lG&Q>18*qCFzxSIvkg z52_k=wA`8;;&iN5N`anapE(^Qc5f!zwK`mnc5dx~krhNrOUn}bgD#M|3y z4XS{EF7!sNB~hSN-sirduHKpY^ICKM48C*)V__^~YdNBH;3oi|)D^Z!Q zqd}6wXvk;-U9)OJ z4xpEF{uHPw4stI*}oo-&J0OA8wfL)oj!xfZEMLq0Gb_}MP5`{ zE>vU*EA(CMju;9IBP@$C%d!i*J%DOI&p5q{Op!twsV}sWKe$nCXrWbi;Ol9Y+TV9+ zgk9B8<#k|x^5i`_fupnXptGWXfHTVi{i3U6&6zgCI9zy4>(3=NX0X3xdVxcOr&rdo`#}BBwiCuJR{m zorNkXcYztWl`QW5`7P6zv=xwVe2}D!aLP?cqn8fKj`Y`^6|tn9$cBjaXd~_eXApFs zU2oO#pbf#v^_j~HlOj`bxTXflFx@+OKu((W*FV1LSz@R zN5jzt0_j=!9QWi$U#*yGY7jxn@HB#?o+qsGnQvCmeqv}x-}Jmu0i+fP23ih~X^3ua z=WC6Br|pgHCSD^c82ItdC%odjb2j3ROouA7oUwTUbMgM1*r_^Mw=eLLFN2BNQwW<{ox4R$$a=V*NZ`Z%2w)0XQc$H{f;yMh|Z=g z^TB;WGystCAw&wZ#lKn&e7s!4>R_&xUppZ!K;QR1*0RLw`VovW`&CUXfZRei9a5Fd z1IihW*3FucwCNtu5LsH$8SY)piG#&N@Z0mO4K_%kkH_u;q4iPbYzik%==lsj(`bmz zlmt_s%y!gkn?_tB*!VeOjj=v$dK8g~Ouz{yQh+txT*|)0(ZAtxdm)o$P|Ey(gZn@S z1L7=sHQaxr7)|gHDkQwSS7t#65Ktk}M}u;pLVuAp`&Pw&;`aA2aq&(O``zh1P?0bKsnX{z6!(f+)&k($c9L!Nv&&3=#)*VV<6{ysTrfO3!+ zP;z9Epw7fyXI{2P0V$PI$gzO{n%URfh|@a>zw;y`<(J|B1nhF0@df(pGVd_SAFjow zZ-5H~QT6rp<(n|=&f*U+W@zZcbam~Z(PFOB z_&t#*po964-+$T(jZ7A-cOLZclRA;1y82m$?{0aS@B^m74z)i~lCu=~6W5iuv=DxD zpibRz9^^D&Du3C0v7yypK5vky^SRHn(|^V8swD1Pecs|Mi1)ODNnKFtIS~+1r9^$0 zyu7@ZHa-u&YUqCLdsEy-baGV25TJI$BP5KNo;F&vc6oq@VY&aIYREFns$j(DWb``< zd1`?f0!h?h1)YRaF6a}yo@#VvVt88`BMcmGWW3*(ZXi^%uIG)>fGpaH!J3Vn%R^C;eHw1uTeoID@o_g2_cbrhJRi?zKhoUaX6iCO6)hZ zom;nbdpzHL6s;*C5^;uTX;HFud<>3@!-j!@VPRL$4y)vKlg>2#jmPYwKufq$q`Y72 z0XB(k9)qN;4$20Csd$>U_Ure(8kej!>Wy$ta-i`d2xd0-S4U609p--g1-tZ|%ds|;T~;^IgH=QExYJ_u96$qeEU%Us(4-6#WpsO%yUuOW-o z*){lLp-)A}3Xu(Fh*9GF zIX%Fw?LuIDH9!o+cw||uIYIPv!=%T4`yeypVxR)@AbuLJ16|z? z9*tbO)l3Cv)%nqOQB_`(xJ}wZK4$mJyL5R(zhz=SZ9SiN+D6IBA3r=iY{q}n@AFnI zY|#mIAOSw1=c8$qpt3y9YgfC^n1tj6uH7t_;i`T6#_rc-xGi~exc0kTX(i8D=xqmU z)NOkW4pvp$0VB=Et!T#lat*IGdFH@hD#Hla{E#!%be^%o;y6E`(c&c$xaIwL$Or@T zuodw+vS|~Jw^GRKo^#K@;X^nu>Frt#7~OHm<+Fg9l?KW#DI5?Fh-}r->f7<6>U*5Y z>4zJ`r*(wQKV>)gqqm#Rz9hz&jyFlx?zv}MWyK%@D^VAwM~mJEqYc*z%2axOt*D&9X`{b1>|*k z=s9`pG&nlmcY|lj0>AVtU|d&HolDPcx7-AeMq0|Y@2umTmCN;W(c6wCvv44UukfnR zM6Z>QzV9gxO7QsGjoeGigVF-m4}8Jx@_PcEe4e>xk(GzH(#T|>?del^g`>Q1A?u`u zk5*NPzXg(#+v1nc80;Hv7}|}ef3BH34@-yenptIm zy_j5B$jnEEhl67`kRoRg%0`Ax3x|bdn>IcOK~7s)tP1x3Ha+A`WS`#3p_zIyxCKqe zG-}E+GcXWIQh6efcO}{(we{J2iwyne*--mZ$KCk~EO^a-XWe@V`I2OXt7l~ee406p zfQT?{Khr@(;_1UwQyWqBsxn+BTQ2EtjyUrv-G*c`kGqrAVK&o))#+_6TsJKu~%GN*q^h2d{$HGCmd zZ;G_-4Mr_f*ESY~u)oL>o%D3CKYY-FM*CK-^ULS;x!c6Cj#mT)4Y@a~J1|)?2sUUj zu_ID9k(HJ8AbAxFDIdzcxp}d{oBv~45zR%1s+pWTYA#*T_c5QZ>E|`&qf-!a7NIAa z7=lZdNAPO=kuFLvC5mdj8|QvsJ*|_^yOOX4Ks$VP8BLlh8*z1<`beiE#Sf0S!wave)uM8+MQh8uv`|r9spr6U~mztZ)) zsKqkA+v8*Ix|yT~2w~KON~{-}+-#Pm1PbGWE!XpS`)sw*PM{mC(pZPBGL4GmrAgj6Sv-<2EO9j zNP47r)2m+QgmDmI(z`%)RE&WR@4+ICskPrbX0CD?)gQYi6RezY9mg_yXiojPXK=utyp=1ILoMgeLqN%G~kh z|7vkIaLzVBMshsFvo{F&xO9@th?DN8>{$J)$229Qt+A5117#0+@p5rYff zCI7ZMGVxvsIm-V}0xaMqz~7wMr0M48HR~9Ew_%d=q`i>+VE@TF-fhB~Hw6+~=U*R+ zskp~*2(-8h!`pO^-V}7Z)_}o(XNOYA?HsRz(sK1Y1S>kWToUfJsSxe_<<;}Lx-{v-yGgzj;{lkJ{B4I12v)j?&#M?(>JieFkhAF z=(c$BquY1rL*|s0`%f!N%3DYFH6CZwTXIs0DToSM49XIWmdE}u!%Ok2c_1YaOl{pC z9Y{w%7`Q%?$~%N$gRyw+6~!0Tkt*6KZ<71`QU=ovLy&h0@b)xies{ z{fva{5beH$4u?uI)F1;2jR@@DV4c_W{N2#%Jpu>UICv^43K5oY%x{3cg4+!Y^?8MF zKJq?asSRAC<^JkLJ)HRn4Ab)s(cr2l_6|aR zo&6)vhZ0n@qYR}G?#dB@m-0|am6Le4dZ3@SB`x+=61u^5ZBT^Kf1tlE?^+=$4kFVt zHwpUCGby&t_XwQ}&ee@Bk~v>L1$i|J+J3)5;UHrb%qwpm@%;y>WalhJ1z(br&jD}V zhM%YvlgE)$gkB4+w}v$=+VeeGR|-TESSL4}ZS%^(vC46Q>27o>8wh`M>{HQ zKYOce;%yG7#3RDJzuxPkW+Zz!op(=nk>rFNl~{Z?HO(gP?3yD6TQt>w%}v9~hvw!? zyt=xovR-MJm#B7e~k_yY)-Z!S;8^qf(flAn0rD6!PPddh)3aE0zSw22L#y?)?ab{ zwmS7qTqopKEQb-pk!o=b%09c9bE{J zH(rt5NQ_X%)A4gYo~LAIM`mt+z}y>sgprZatuh(=m{0#~vccbVO2E@|1CUKTBI)VP zz83tjZ`u05oo%6jDdncKf>zBjPToO7si+>3B2dexd|{QRGSl90f9R*bkEW!Sn*wqE z(WcipvXA+~N(#QPAKJd>oH)o76r$8XaJYX4?8bzIyF}24=pG{4kl_86C;KGDC&J@- z+y0VzB8^k-doA~?OKy0y&*|3#kI0ey_8j0rWtq>5H}i`5YBe4%#d%{Rwnn`LnATU4!m>7$=%yqgw-%2WCsJv}UxO*z_(zS-q zek7p25oKX;wpx$((J(8V1rw*k&_YAKd`OElhM#{V>dlhtKCA6Rou+MOtLJv_E+`7w zFm8BXVyIc*RCK%n?)a75q$g7?JcT%4ze);7^buOAA0ACp&}c z&A5nC8U9@V@{y-S3$(u|Eu(kq@cGu{f-=5E3Oy3vomqDLCj3w$JJMYK_(wzMsPGRD z1_hP^oSR-mh!F8Etp@sGaUPL#lXdg9xW=7Brp`o?<2s{P6jvBUur(XX7B0f5!$%vv zF5`s!I{cXwF~Em1Q=`pYZfw(gdc zt0(Sm6^Fa2b0zGnMb(+$>g8AmtChA4QA4P@aC4an(w(;(d5I6jD-7C@`8EJ;=Kk#a z1g@Y*0S7)VM2q0PRQayM4d0ucZ8yDxa?DG++HETygH&8!}kQ3?f z@bJWA()}YfCJqCapWmD?G(OZnPNIkSs{AAGT~%~)5K$ep|PH|-Hw)f?$EL|bOT8b`+}*9lVf zh%#lZX_SIFDBNjbQO-P4|4M5ys=c9C4J-cKP_pWCY)o<*@K@JHsY z`u!e2Ve%0Hjgq}j9}EcR!BQa_8J2a@)NY2erlzQ?% zy+E60y`xAGUzEUkTdsn2{)8pwRTqMSBqk0*;jj?2qK5?3BN2K-u+=z%RDED9+6hx- z>sf+P{cRVr={SlhK7R$#S&cshLVXny5Q=y^GJC{_ygnC{aFfo;(q0g6M5%As`AwwYQGEb1|ZUwF_G6_Pds;;&L^OS~_}zUdo2T~~^n zpL)qH$MB?IuoTNdt$Sb7s6U6LWHiD1<2tZ5x88hjy8obsxA%j_ZIm5AYrG|o3rW|L zSQd}g^owQG;S*d4nodUGjPOrwn>`8#c7AJZeYpt z{k}?}oGL)J@slf2ePUtU_E8F9f~@?Rfb6~yhJe=x^NqsAxkpVcwXL)5jmvHw9X!vDLH8{I;r)tLr@+Dw|`SgE8z;ruuJC46 z(->8?h7FLLlM~l`Ncg*dGuw1v56e3Dv)jRvb#qP*%JcnMkDotqsW+ToAg?+anw^<8 zb&i~xWRO7m@NZni>gpmGg>0_tQN$ohWwGV{WIF(SrZtQntCG65#Rh34tMDoDQ@50> zTa)9n$sPH;21r4sq*CUY#4_3?3+5P`_aL(3UDI4+J*Wut8}ZFLo7aioCzQ zMml1fnwlE#kFz5u3JiSH)b#&A8i6RBy}@XvXNE0+1DY1oy1s`G&-zTlZf!?+(_##f zV3C6n^7P^Tn$~aZIBK@hPn77YS$G0!z^hE(wA;;HX?c0^NK(N?;UNt7hd~NrzD2Lv zVRlYE3&xpN*GxD;;v&WbMc0#EL{9lUHh{zAJx;tS1D)C8$-Yx$w7edbRK~gX z=6Z7C8#t(wNyJGFrQ_kRqmIL2J_0JD^`v$4hZ0crJT>$Y8Q0(z0 z@HW^&c1RscEIh&Bt0csa+Pf6!toun?HY!Xd;4^1Z4zJt8c$rew_s42xM5q|Gz^JaJ z-<9oaU0V|95l1WO2&BVZN7?2R90&Lc#)wELU9S4$CgBD}F4VByw0yxtpOkpF_A$G= zGCNWO<4fmjZ*F2yQ)*>&`Eq?Xgm`RuINg64p?-h6KVh)KQk!5Es=)e~3|G-{OGVH0 zPQVD{wa9a?dYs#XMaJci#=V#3xNA4udwQG6Is-#4>{jiLu|d{({WXJKXZYA@80lc3eZwyY&g~9RIRFR$(XRwf-O{e!{@}PbUmH%bjUYH2Dsb;B&iR zllr`9=A8>W=`3Fbs-Ax*Y5v^aSzNK?l>4FDy=qIj>)h83*Gb`f|HvmN@!g64sD8(3 zU_a&)hYyH=n>NZUkjHF{mhw2?dJk#f1Z+RvK*#0NhZz z#|tmLt6|YLBpJJ>Q}=@1VkOKPDmp)R^gg42ltJ53H|?z{LEvWIWE6D(DPPSHMaJnx z6d>$uWA`Z##$(8TvDNm--dwq^3`JuoLoHNbHmkQ%zn-3w_IP;2K|DEEVqMBXyd%^k$W-5FPxd_E)a?w98o=Vk*p%55} z3ix2W4Brf#;B;xNH71v}Rh0sxBx`+M&h zkhb&S~zyb1xP59d)pmpv~nvMRSLl(atHwboXjoCn+B$W|97QZ&26T7CWaqK=>8!Q-FN@KTw`2ZSYI z7ojbivtMunhR}PGpPtjF?Fa1FoJ3`wk&?C=Egt#k=&vXWCqbUdxlGD zaWiuxH!mei6FHDNW;XMqeJ)SigUr`yoq&F-_|!+)E zw1gg=D6y-*-;$K8ZFm`pbeP0Xu=4V%#v!3wQuIvN*OzuOF>rjT=EsNuSL(!pl)375 zX>qZrs3_#~OO~nj#D@wj-TETRLS5xlmiM4G=%~SBRhDdOpY~Qg>pU2lsmag#ZE_#o z8;*p>4jmqFBAmP5Rvg}l_kSf1h+pOn`a*HC+&w?_)~>LnvkzA|qTTa_I8`iEP}#Zd z=~RBOQdYc2~;`EO5|8<-DiQ~zwaI{Z`~?&H5i>- zkFts{;+W4GXF&2jsqDX^)Ys^Rr+lB7u=f$cSa<)9`Da+?fQJ7ljO#m|dY0uzHeG3n zh~hY;xVWUj#5(bf;vu+Pn>(kX0*Wu4@K-g-BtI)DJ(wg4yNXx*PG6>6%0&Wp{lSF7 zMYCt>{0O{7w1O-bd|+C-d=JYjri=4i0ZRu4vUz;9I( ztNK*My!$IrKVo+zVe4Y67XcMHXa{|X$dq?ykN(ECxqBE*_^pd?UeCTS-tD~I^>S|% zO|fJ?bf216_X1?2DCRta`*Nk7>ljV!yc3lJD7zQF5}rr@$#=PjhrKoKNoHO(uFaky z6=@@L%4h99xT&VY;=rTl`|SN~LGzK^>_r!oe|vhw)J>!>Qfu3cD4noT#GE@=trlu#r@r5ov% z+H?tIvP)^$?UH_hmu)k(H|b>?38p?sVTINO$d>>@(_*UZ`2hqmY>euA%kA1 zpRSV?8#KFbmYHh&5b8rxx*+!~RoSrq;aj^|^ZoKLc3_H6QEmEp-zyh3^O;Y%Mi+`R z0C!-4s8S^UW9${L(44=v$G1^tk9)}|>*k+PH9KBbl|V+|{ZX=#MmWlL$eW>2k8cYK z-rH|6NGH>s2K(&_wB(#WB19n<|8Fb zM*LroDpLsiIrSrB@mhM}k`TaVP0}RJP?+O*sW^!#1Ut$cHR1haq$n(bF@KZ2WXz=1g(Jo-woQW4_y53VcE(u!91@$@sy%6k4EO)uU zLVSwEohJu`M|z6NEpJTP9fORWNVcXv`>;F3?E`W{7?JV!bqZs+VE(DuLoFGhl4Sax zrHm_8*!R?XZ8|=(0tf-^MXJQ)&lJ1k6B6p5th4RPhQpQr4tujZ?_iSry?e#n%H#Ne zsjs}Sfm{}g5~yn-&f7xvEv+V9&_Dtq!EHneM>@S_U?lkdu1?Ri9jE*A%ZU{rK~rOH z$~Qm%clXD<0;!G9h~qu{FBlJM>te0#J*$ZziGp@inKZOo9^H;2LrzW%E1C901`G3^ zg8^TC++@c2ktS((#Ns3W7P32jRDoSL4Qa0$-KPW|O(<$PGp)L7GG znw~#Vvu;p~&%Rv!J~b0U=385wZ_0TY`1sg#@PSz4jvD$%9H?|L2T>lujz`fu${oTX z%_(kuQo)`>BU+1GS+a{+AhGsgWBIRHcE9jVuW9en*lm8|rHus3?Vzfce?&DINb9qp zRv|^3VkCMvC-b^nBl?{xl+Y@BjP+}$UP^A7aV+9;Z6p1y#)se;n|cb{MiIP<`E<;x z^+~OP_&2vBNI?ALknQEm&ntmi`w|M)BcEsSK6O+1ThOU+V-ANT31MebJsD3+g#rx} zj5>PNsupm}Vevx{tDjTvml&i!)mgjJ! zmfi@e=m{HWzQ_5hF=b`r@@g|o$fh3W?Ch-etQdI;Hk=YUZ_@WxF7gxsd1}G7-WYeh zcmZR($&-#7eW3Z2;n}kSyNAY_3PHbI&66ms7PG>xnuUR`rLZ@M$jD1g?lwlHpQd96 zD(a#D+a-#$$m4JygRj%pJ8_^g_eCM%Axe-+xTFAmgFy7^-umkLN(n)M2C{YCQ=1e= zf@E1epiIq+e_PZV3;ZW9Q^>ZdJ*9b6IM_$nX_sM8K7V;c&kcqYF9J|)O3sxk{s~vj ztcE`ImMSzhja!02F`w&m6{YruUg#-_+*1W&zB1~E(f8gPD12kkaX_kLpy!dx%^f{x zX?fDXb%LN;Gg4tcfXWEc``^8MQUd;9npJTGwM5o!gVnk?U2ZfJ91wl3owZC;dCMm3 z=kuQXEmmDF-5@?$!lp%{y*WjxvoUOEkmJCxtb~qWoPYmxwv=XSa3@5<5)L)ku0Q$<7lxWO8o0uPhC_i9> zDxCdX1PaQj(2Rkfy9uT7cl(ot38XCzP00hymn`mX)?LrVjeRQKSF+elW{6xwD-8Lf ztbZ2KX=}b`%JXM(!FnpdtXRjz#Z$4YP!(_}7-dh}q%I3q)FpiyOv&WxX-Ud(NXXtV z4rEUgrZF%OISCGD(@|<~kr#ZFKX093TbV?KcV>e+_2^%-c1K6$9|S(L#&0X^koz$e z7D{W)&!O_UB#Y2_2D=v1_cQ=L`0fQPAD__Mw(Rf{ZAqas3& z3A98L9oo_9Mgj?tG3fDqEqphoa+w<;`-?vItqJzG_h#xb&viIil@gw0Qh^mzpVsFZ zHs{DZi}A~&k+-}*n`hshO^G>C-nTQYQs+D=QscB=UMsx+mOua5J?D*qUehVx2P}cq zulLrR#MDekmu`;3S?k#MUS7LwQbrV~tEbU%EUNl**G`b_{M<6TCCS-~MIPs@<=xBa z@-N1u16zJjdBVFE!Cn>U)ViM!3h&INXOCXu*%G-b`T3FTzLbTxDI>q&d3bUSmn_j( zOP0*DQRRT;_j4wFPFgnBFnm9U)ePE6I%HnHE7LL3-#{)*LN6y5H5hP|@I5?JY#*2# zzv#;w9Y$44iAM>}{E`mrIXi3J9VmTqM>X%dA|0g_X0iIVNLbTkos1*noow!yPGGd7 zgG<*cncY%ibYTH!K%Y(5Ht){S5~Cic8ADf3n-skCNmHsn<;KIW;U7v2W`(1-{wdkV zT0Fx(b?nVz?!^Ts&v{?FE)OA#V~S5!8S<1@T7N26IZ5OqO6l@a%x{~}bLlYmh00{% zRES=OS*PohNvF03DT&?GNoW=`3TLn7eeG*>Q80;8YMS3#zfF&`JQ~W?tL^u6Anlp) zw3{BO^*qLu+uz^ae0c_bP(LOz8Mtagf&fviN-SQI%yp*h8uC^C6d+AUy?N*=Fe#G@ zA{P*mAHUvVBr8?oQkOO*Fx?0`@IKY>-=BO>c2YcJ+qronTPpY!AJK8+_g#)eh_C?X zLHZ{l|8HNMJ)fWPD4B@M7)T>HIWI2EpMsPR8*Y z_v6vPYSRi$4Qa}7nVCE?#d!{2aX3-W>eCy;Njmk9D-Rf?*!y5KRzP)o7*vJ)(sQwJ)QGI=oTioHeFs1z^K3;!k z+`91d8!Z`!m6=+5T0cL397-Z&i@f3Zm7V!4WO)N?t0p{%UPG-xqo-2ebRE#C;(pF@ z8OX3f*ZA7Ct>(Nti8I8-Rk+a>h`FOd8{X=V%pHpx$2r~TK0#8lu#r#DI7lk!NIYsm z;NWdNUk0I1UtLl+!jp&|<(<9~Z)~HKi1@UL#UZOC%V46-jhNRTsPf&X!v-o@ z0F_({2th;l(yAj@4M&rSMGD;3%s!QC+oTrBP@6I+k1CL z(iq*psTUBvpMlzDhDywkqG4((3Drjz=~2D_FSMYK{Mo}kvh|$r1iA0d6PWvXF5me< z%cCl@@`A8$uTMM>k&q0=3pIe8)K;en-7XuNO_}x+w&$KcZxuy6&3g1M9aqFty-&j( zvohB-YM#ZR0b#VZ<&Ng1A@fMdPm}gr#%h__m1k$;oBWDI!d6EFt7Y)KxfwFx_pwC+ z8_W6o8*hq~j4n=2jKw3wUmLx6Ks4)~QdP4QZ|rew`!;wy>YF$n?>;_88zkFjx|&17 zqm*YKIA&sW#yjUfoI*Mu|+>e&81`~UNWu}!$5hTVt zX;^ta&v_(#iLZ-@jHk_$-hXwq_eTDFZnAvTdg|kIt_@o0k1zS_`!^?1%G0a{YuhBH zNLo0au{$|Miheyaj&iwUyC`*~9iX+yk!0>ILaqCmO@!n06rn!&Fbs$~G+>BuzLfdQ zb!U#T?=$5fgqRJ3r83(f1#}cAmF)N8OrD>eNEl0h2<5Tvvp;bH;;ny9qVnSVC4+Ze zR0~Qf*PL_SD$hO{MdgZ^wR8NANpaWAhdFRks3<0Lko7E8&cv<9Gk9$$H9Hm!<=%$2CLZzo@ zC+Ijvpcb`6%;dS?3|`dOT(8KnzVL$~$)#se}HaMLR^F#FsF^!8%i-T{tqd zIfXLTf$)Rsx-zaNmf61P%oeiGFZ7{vga~3QwEMizW_L2JYHvun;KMGVw=W**CtSaF zeMjWn`oYvLjA5Dv8`bO5@-g6_KLm|YPcxZGOY%OcH zUb;eFB-Wgn5z`| zoY*jZAnr3@Ur=}KOMUXdgT&0N?3wo-B^060dO~5gPA$vKFQXYz>bd6+6Ioa%5kkkF z3bK+R7pj{X$=yg&!1R>Jklm zr%2-Kk0qEl889`=wZ5R+46<#ye_Bi?LhAV4bL)FT>xdK6%OsoL+hn1j%Fn-k&j#hD zd!Z%hI1AJ{?L^3eT@g#dyl3p#P@IB`8>TKAC6^68V|QhD#*j`x`|`*6_TZ^d`^E&#>%^tA=0KqtMuVaE-V}L zjTPFjzA0!d)~Rbz?KeOBhQC?dY&A7MneD_j^dWI6!7S)YQj$e&z(lLzIIq<+B?8pl z$(Zo8Ew0-A?sGY&>Ccktl~vf5%;N&xFsRdX|pF zL|ihIG0j$EOjUUM7e;X2Ft$;gwtj6!axS10g(B?N z(ybmGwQPk1bQnT&nJ5YMu*hwQKJC4_JgO1coaUpTn5pU7U7O*&uSW_JTJ_+sqwq}? zaFq*jYTKx4mCur58K7F)`t7J>(hZs>nqFG4Y_3fR3t<241rv+MbyYBF5X=l}GvvYQaOfGs&5tl;XzHBlXvJ{xsZlA;=%Y?lJBwq2%p zRCD&Pe92#U@!8}(#oyznh!X`-&rOmF!12w)i-Hh4W&ctJPGtlh)2$Jj&ChGxN|!xf zp~Og{Evp{uIjD{IC!-*7*C7l

    UHk?R^)7o2We1MU-X^p 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

    `sI*i?;UCXKrhZ}E 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 literal 0 HcmV?d00001 diff --git a/doc/workflow/web_editor/new_file.png b/doc/workflow/web_editor/new_file.png new file mode 100644 index 0000000000000000000000000000000000000000..55ebd9e025720ed108f16d5b6d61926701510c10 GIT binary patch literal 85526 zcmZU)V_;^@vNjw$nb@98Y}>YN^NwvBGx0lh!86}*_&J0 znt^~wB37ojtImyJ4jnzp*f$7LDht~Op`Xm!P|lXeCP1J{BLor?8=E*nE(JxvPzKSe z^)`akVTz)|M;l(k*TIyW8G|aRK459qy>5EGZe?VqKkrXw@f^4xWio*5*UJVH*+*$n8$)CjE$?2^3& zr;l?=$&#}O_1#(snUlhMU(G^{j90V@d`^-7&nmMDBmvzzBB8bnvN&Q-`tGBLDXE8);}q@GX#ui8Q>38_7D#qFdF!6Ft{T1C+(nC6G-AQ4;X z%g_Oj4XuyhGgY5l znC{qu5VL}Wl;Ey^5D@}kYd{uzKwCf*w&B5qup^+#LE#;Q;E;n{iJ@(wp8p^Np zXJ+Gg?U>{a;sq}(NNE7u1jvLSi%t$&kCYfs)jMx2Z$fW!VJyz5qDh+)wlK76=vycO5v*GMOe(up&%$XC0qgz+3WL;9GQBP}W(qLza4UwmYww zJox?KyCAkA1me3xi3X;SO<~#~yoHGa<%UUtr1Qw!FdVSoLvZqFm2!?H5x$e7a-hjX zPW&DFOY!%S3{@)HR059tS~5ejuwbvCyntkuq7Hv00w}L9h56I{*V%B^e)#?)I-FSi zu5e^-fr^_lE@dj^6y=<H_X^rXVk-(ygtMZ*^fc1fM&JmletbBVV}CSwO91V7u!IAq0? z8cQ!!FcHIWd2Xvx{k};`TDIJsGlC+a9sD0IoXuPQ@s4kTNDt=`_>P*V8 z>RQE4k_UC8XJR+e_?Wer7_{8A$3LkntP61S+Vt-utgBB8ez*N@{w;VOu==BM>6FzK z>t6KU`2O~E4_6hD0x?^>b`UZ))djMRhA&-T`m#vDvdsF$GK#gF^_A74;jXc-vB|R0 zl5c_Bdd+&s2Fu3XO5xb@7~**UnDvBr_Q&z+sgvvD*~78l{M=IX{94gc5l2yV!PNBB zqW!GDu^^KJyCho)dy7c~TLYUcGq2^cJp`T>j$`a-EC9EEaB+}#FL)34<@wyX!t&>i;cmkI^f6pXmlgqwtr zgudBM**n>0*;5@29jG0@JIFgKJC?lNUkrWTd>)?GElQX)W1t2@s$(yqwg3Wk1|oHh>L4(tMn6Hr<>uB;OL6s@PHdY8BVY~O5SksOk6k?`ft zPqXF5=BgM0jN1DM!|_6*``SqhzN?F~iu%OdMbJj!$7rKAlAMY+NV77u@j+psp{U@o zwDvg9#?r4_wS34}TDOXdcsOE1L_d;}@7}1oqHMKPXVwn@y|K`FOiYSlB zFl?=VsMoL6TOI&9#(gi2V8Ok_Hb56h*-oqBFv>g|r`zc|Y`q=zut)Pnr9l%Qha_M4 zF+nat-dwP)VyrTGb?_whcF}lh!?J_~69f4i)T{bB?^}u{EMR1Zo76;I$)`t%!rDm+ zKI5L3i%=YM6LaEqIsAEaSE|epjHgSJ}LB-`rw5kU4^M5;y1X4I)Cz3y?6~Kh z6`YS8s!@2xuTl^xZrT3~!sk zuQZq6_zeZHz0=;V&huj=SmHVOdU>flsBatg(@*P94G{h9a2g1CIrf?6UwQAI_ly>= z%C~0yUYUPA2HoVDs+u$yjT-}jElVDsV;Bq)`mp*F9h0AW506nCQ1x~pGn?nIVMr}yhilSSl={fE*2JpY!{%){z`IRTviYgk_cWca6sfr*}x;eY!6BIW(3lv~lt!^~Dw)XL5b z;QYmdpNWx;h4)_s|G%pLW%(bZTK`MR$;tMgl>brlUzEHI|BT>2M)YrW{j2m#U;J>q z4F98hez^DeKWrc%f*?|&LaH91z$|D(m4%NXdvl537knZz&~$}@@RHF@aAIMkaCNGK zB7Z?6cA+3ulZvk(P*7%$o&` zgx3OUYDtVuBL3n1e*{^7f&dO+BDRTJ!kVZstq=Kwv4b&8!+O!=k|`g$bjt|FR2GX; z3WM!~Mzu(r&299H^E|BTr94YK4`hZ}Q-UYU^K4XBhlj@MSBEDEUf0TZ`q7N>s%X#~ zg@H23+#YFP1kceeY|@zGVke zw_jP9btOxVx6=}nWd2Q2*$B$F<(np|ln>YNAR&fgZ4)0xz$(2LO*QPyetwyi;=?EK z5{hX2ej~`(MMGQ@3BBC&lUi$$DrILGhMIjlQSMf5cQHM0R>KX3@WE=iN{|ADY zx~%b(&up~wU=&@gwonnlkrIvL;BDnwaKac}60tA1G-#Z-!a-fTZuX-Axgl6wh2-W6+RoefC z{(;&rRa<}8w^J5G*}CR&)D!5$;hOIzaT3~THhR~ld<>2EGbwBS?{Q`5E;t4kg-sOn zYTMa8_fNr=#wXjzqDg7WuuCxNfj&_-LH$3tNqvxz#`|JzeGx(bpP8{uP7FdMTq*zR z_VAPAEexslc3x1+0qe3_FhM730_la|Z%QNg#O1+?lv*mOt!|Av-efqGZ@w&<(E)TZ zV#|-M1h^nNF^jJ%66(pa(KvOC9;#kz^hM~tDoFdW0aaDvMo&ohwF-2R|5+3N)g~ z6vJiyLwm?iVn~OYqli&l67>i3gK}z8WocMUc|1cHRo%P)4+_{>(KbN#JIYLYcU~>Z z*tly9GE``b!)b1D>>6?YQL#I<+Af)$$8kt`v7?%GE$uet>Z>g2r@`(ESxwlFK(wXLgL- zBG5FC#hz>q5aMVrMpKo@k{_GW64uZh0zY#$G|2cNJjTCx){t>CXWoAHsgi^=)*{Bu zY{HKIhF9V1Mx(RHiD@Si%PIphDR0pxTy2lfV{V!=T@x?x+)$<8-~I9w@3`3ExIiw- zZ2Nost+t+DH#*}QVQcZG>;H{77o-#YS2Nd$rpa=CCjtQ!y$3(u51Qu2Eh)Phovl3b zFu}@*zoM-C{^8*RSU;6Quh(Wi(S6bB{x`~flZLu9;0Prfnu4O;XvTe=$-_a=Frd0Q zC%PP7+V`sW^dz3|S8}rJzU6FAWccW4*~*B#m#<>Nr;a^60jD8xTieIp{pKcrPyw2q zO#$}5&yJQ*ScjpeU&Xtae&N-wON?E)T&_#F+zamg$c8zq&Rb=cD7*+B1N`;V*vmGU zQg6JJ_-cV0c{Ql{XO-rkWi2YSPozGM9s-8Q<~y*Z09$PHGXg62$AnQonkiv2C#O*Q z`YvI6Gc&sS+UN5ngFT5jd_FJPZol`7E{l-(=F4@i`n^2~PHrw7oe{hNxG14u=)G>K zM2gfT^v{piu6Ti!k~wR)w-!0t6v0?BGBQJ!^0qLj7gEN&{Qo-m{(G}K<_1ORV45Dn zC#(3=v;V^$o=d)WtgO1s*0zV*j+5RHt~BjOD9Gok>)tRGvnNEzZUS1Ska*>i7Cf0r zrkBx?7Lm-_&$uXpA4jzf!8uwB={|usPo7H*CPOjuoE{)5+0@iD27b>6Rx0FnpVzM= zD&!)nmW|r6;^2(|s;YxD80(~{Qj?SPDn+s@t?%96#nQ596^^(3dvg2< zyYjk}%>U!i{+H|?VA#htcWu&k!NscV1C3MAotG=fyr|{Pj+%tJm=WRh0auLQ>Bp=4 zBndOt9-18ol(sb81KDLAkm^vSmlqH-mxT9^fs0bfVgo;dp(POU83oe|-yAkp)6&w8 zvb{I^`cOwfiLE2n`U5$B2{6iO5oFFF|pj;0rG&|9g)VyM*7Lg;@P=xZ=MBe zR3?rlJu{J6nqvRGNijnR3kw(QPG(zdF!L40>9T;~R(_19vn*9`mqoy=6GKp6+_)zW z6S~3rt$#OeP+`!iRV|;RssM^}K2g)2Z6e*JxS zD4wWVy?nr!X&)j9oPK}DLNJ{ji^&JI?s_vutKR(MX6z!z|KlVVnrOAb6i0bycl$OC zv*3#D$25)gVpg^V(gkm(t6X{E=zrTz$e{JeEl1fqO-(m$a%yU7pVvcOZi(Y~IGfc5 zo?294e)95hrKP2ZZ1RiTq*o<2$0GDXn<>Ed>P5EQ2-sRh7^}eA=d6d#eqe_&khhkL zR{5DqmSE+?i?^vXG@R`3=nepyXuJazUerPBZhBr-;HNemxM-MbqQCXYGc>h?# z!0#s(hPVJY1h^@^_9J$r?{EgeP*kIjcWzX-f2WsX@)!h_AnzC zH2b(#@-$Ov$^Du>jNwg+g6{nNvu3@z4>Q-g*JsoT4bKWy`_kQn&Cv2bd7Sr4zr1zM z>pXE0rPbcIA)d)OfV8SRe3MOV`u&|sVz621f!+c}5k%Og0N-kc)Z4v{dLlw*r=3;@ zD~Hkg9NubmWb8pYO{#)V;n_G()8LbW|K46?E*G@=gcf9sIr*GaXObvHL=PJ=h{0oF zP)A=7uQxP>f;~~i-C2#YT_f}N{FXfJO0x<({PlMcULYO!_7}K3^$|PO#;4) z@^LvDLm!cjb9#IIr4qC#O`f*XnE6}gPe1)8;8e+V8<@%B^DVz+{fwZoio= zHNV;31<}&aO-6GT4MBhj+VST?{nuev|9dPTKM8W=`V4jF<-KhrnhW!^QVFY>OchD6>FkN*@ zdR3+Fzcg#tGFe1h%&>DWNAz{m;sK`f2(ZZ12gQo z;aXSg5ffaJ#s|Jd`Z>6;s!+BX>&=`8opV^Y=+?w2(R9C-zVA2H7C{(G#7EwmlleQg z36p}$oS3yutRzY=GuS%o0;+%tElEFX2ghXx@z1~GeVO$xsR3n0hrsm2d)LNgj)H=M zni@s{M#}kt#to|WWUKIB)Hu0W(cy`jdb)}}B!w+TJsVLe!S@&J1*N(oVn$|6abY(X z+Y=!sbz~$PiH6R&9b@X^u94#$f_@JmvYcSdsJ#mNq77^n8eO5yOjTAfkO-+sBmwRkD z!_FS`O&al7fcSm>Hq?IchWtl>n7TJ(v&F%+IN9qpq?Vl$%OSV%_1p!#u zmQcc{hqcQlpZ0;#QEOA5w`fAcs$=%S>=W^9?EFq!|+K=aDzfxfi(#e>hZ7t>ACrQY>|TrCez z`0j9B2T{aZST8Gv%l3;+V>1kZWn0bmQ(<6NqwQ*#AyvtM+)NI(WL}rz2^vjfHklbM zm=Bgv|9)pz066x!tC*22tDr4r772oKOady~$mdH9*-iBhg-}=5plmabm7H#&P^_h` zHFD5Y8Dk?uug)A}AzIPR$HV>j$iVGI$#!X6n^)~!p2^p0mXl=^Bot0gD{L&`lD5n& zguuc*E@|#=)jP1+)Kb->8ttUlg|hmHgSl2bw(=a4rrYZvaiMS2nnA`Ra2#IJQ?n0T zsc&ude46+4YI#Jw43|8PC6tEW^vkch-p+w$>bkwY?xp1A<0BZHFz$99r>CbM7#PSp zIx)7k)J#h)w$S(E|LovoWt|@zqv4<^E^SH2=h3s3RjBi9X}``3;s|0iP8tPfFN9b_*2#-u-17$>sQdjD#Vgpu9I|k4G98^X~LP0~X5EXw*AY z)YL|XOifMSUd9+uBO!i)7kNS7jVd4hE>E1G>#`6f;zeK(xV~gf^Ov34?d&{=45V`!@Mz7XCR+Pd4V; z^WU;@XNF?E|4f#QIn6Ecbuyp6E9`o&)SGpfxC5dk-%j-w^r{Ej4?it(tWkCHac?Vr zL3c6Pc-Tu&S)>cueCuxa=wD`s)c8mu;PUMaQc?o=h%A)NNx1PgTT|H9XW^dLxvLlt z&!d2{&$?NJ@_B>@8L37JG_dGYK$egDK-*8_ST3Db3R!WIb4BSja8@%36ukWD= zuo#uo zCVQG=b^t_^h-}O zJi;I&FX=Nze`>5A=Sz2~YXR*ZM=j=80?#an=}oF{1MZe4;!u4-O6RcPS||jRmwFEUAlv>!7|AD_dO9J3i!UThS7L1Mlp(~d+g(5;DnNyDpDtJT;N3Q1ypZ+#mp{K z5)%`_uVWC|QF`j%v)Am`YR=c1Z-1tB5n|w^ziBurDLJm$YM_I=o}Zt5IVnhR2>b9* zPM``5U*zY}gxoHcF08vu-$r#OqiC!*V-s+)qtx?)>@mz25uUsa`) zTzf8LV`AXTUS=mm%c#l3^cw@in!Z?d@u-mf7nxE1e9T<|CqQkpdR4Q_9)ab3JAF>&2XoPr0C019^j&ZScImPR063=0g*$e zFE26{Dhvy2ZO|T8x1aLuik2s+==^`Wi#ypr?{>zi5o)hRv<%d3u<&QPQxnb1CP$1f zj{2twlH^XIJYU$l=Ii}(dj zE?L+X^?j$Qu$YutMp=)+n zhH{WwruO!{agR|%IQ~<7kHm+{pqnTU%;wwn)pr;JKa3-zM{>8*){19Jpv93~TzVKB z70%r^v%gEkizCIjIGq)^N0SPFx8*ND;xw8qkHTL2d}p|& zBSAlY38~w2g@M9ud(HN)sI0J2Yy8G{M|?;SN66>ZPk~oFm8PVu47L)$%bDrDA#tW6 z|HkI?6hRUUy*6BtJ0=m|hJ^?k2sbR~YuJza7eCJzhG_QOIX}=xIaZY31wk0bUw# z>QjjfKcm(=z^_O7Zg%)1!mI>JJ9-`QG#UQP|0vT?Az$xhnM$vI3aMwY79*9G?gW2i z$mFxu0_!_n4^i4HN~5K(UjAGtQ`$S^L9nn&{o&6BSOr?m6)_0(@R=kGt2fh^WC}!$ zr8l|Hi?Sb``bwj_MB}r-e_~g`L$eEf$?%J)4iQD}Q~oz{;9=p#+6;B}2^$|_G(EQK zE_upAi(|QbhL0F4%J4d)<9bqOt1ZO?Lk4r%05>pV*ZOf4rcHJ& zt`T!_@yXiSIZ_XLU=ZTPLz878)rndD)AwmA1U&tRYfP1-R#|dO=u7+R2tGzHsC-P( zpBDZ7RUS%WJ5S5YgB#k}7-yp~V<_{>%Zpd#a^+)ypV2#p9ag>!FVy`v zw;ET)JG@hRqtT?+H~X^T(#z>U6#cl}b=f9{;l2Ii)pq}wc1RUB{zQ8uZJjFKtzpSv z;kScDr{+zg2e2Z}<`&)bQVZL@{Am^Fc|733q49U;>x*+TpVBO3N_PNVkUzK{h{)2;q z1Wa&fq`x60O{IGAy}#i$g&djRO(9dbQZJ@(G1!0I3q3S$G$mv8M-PD>sXL85<5`E4^wa@txtG=NX~vYu5@ zjtS2QID1q0?s!$u^SKN&}{YU9>c24DQ}f@wcb# zPb(_xohLFzyE1Zn_aQ8;^}ZcHkUa1Wv4PC8bj3Qrob?CKw-slL4{5C~TZtQk^{4Dt z)kf_DpPf`h8h^>a=Xu|?dFXT&t%1E_g>TpDcPBPCVusod#6Bp)`W*|5@s^jX8phCm zcn5|=4g$_$HWqS+n-(orN=222eYMxv@8!t?O*42O?$ny)p+j45kv7!MJ{v?w{DecJ zWe?Sh{=^utI&S#pH_kW47JQp)#TteJ4|+_Us@_X}!&?;o@8-K%TjK2_FR{_ai-a5# zrZBINH7v+!dphbI7%Md-_mln)EWSri5Gu@JeRm9BDG`qEpc?M+9qYDchn}CCy$-Rv z;ovNP%F4?#b+!F7{^RscY4?+BHP8%+PB329NCr$*l>~r3EYv|JIIcRERc&Qdsgx* z59It5M3jmcDi3v4F~7*##qAHp&bYp`MP~w?fs{S?z!JmuP;A1Am})$h31Z@kVb#`V&Fe?<483yf4gkWCi=7?wTQ8fooIZeX zaau%PR9vhtH^gEK78aJTVJntAy$~i7)jDfY!y3=#=+(=)^PGyw?9K{ljG5378-@TD3im`pyF2=VJwAitImui zZu;YPTgXR=P{Eu-ma;`PA;Vdzc+UTB=7q?A9bj+4V|KdFk=hNL923Nd0IzAeb9mTf zdonA4_6cj#OECVl~bZ>3?OZ6aXYN8i?mcVg5-QIk2GgAe3zL`zqu}SC&@Q{T#a zF%}`OceEtDtIsMMm*v*8UK4O*d~6UhwYnq#=J>w}S87xIrCg?Wuxz1tyw9r~Z`V9A zG)Q-%lmKFSNkmZiJxk9$=CFT2q$B*sv#~+!D4UBf&3gURL)Cj-VqCm5t=Uwp zY(l_e7aq5?9)*)HHp}Gqbkt0;ulFRG(^M)m$@ElVQ?m}!)1A;G_cdym0OUCBTRD|G ztY`|$gPTBWI=i!pVbO%jlaBYg);@l zg%?AAz{9~YBPA#Frme;X%=lSaYP!lHLe-^R7+DvGKgh{7!hJ>7i3YLrdl(0Gl?<1r z1ny7J;AnmOUv9Fax^jJucGg>+i{}(FuHT~E)@*f|oW$$^894M<5paJ+Bn3~>%>{Xt z6BBDQn~F7p+j1+%z%H3nG`FWiv$gH3dC!Us)3AA}ht@?BZQxYY2H2MgOFJ_UBTa0A z(+s=Oh^hW?typP5S`(-rk3b;?QK5*?jTw+mK+JK^6PU|nZ@*A zb|&gjJC=mMJD4DQ+YFbqm2WPsFApxPu(t}Fsi5J>eKso)yMpNBB^9%loP(X&drxnl zQXr-H`hz*JG};`YZ^>EfF!IYa9rAzr;We>j@-8xq)tn5_^*7e?6D>pc=o$d4f zq{B?k8+SwG!`lcpj8+SJRVLDJJf-!`nIqh(6-nYAtBkEDQqK!1>|LGDaW)IC$zm4r zST;CHb1irxwimxzTD37TOf$zD5uB1Sauld+N}3bh&ZEmlz|KOC_l<|%ywr?BNAoMU z78=r;-o{rF$A5HRAsdt?=}D{`DkA#OKQz~qP#nZm#CNOQ58TmWe22fnWN{_$E=6@$ zSj)?dNkT9RXx6(QP`TP21NRvi7uyU1SD(NzEhx-BL))jRP|oJe`hMLfUk;FU)Ym>8 zxI*TTQyL7N#ck!Iws@V_G|<(uv9RzpV|{7^Txn#g7?sg5E`TA1Y4k4Jnlgs9qcE-x z>2tax;3%tWhlST*GerI+wV|QG2Ijs4SWEH59OOZ}+cs7Pxaip@Eqll2;LkRyXP+?! zwZ}1fF1oVPPI(&>gM3h-+OV}vu;0cm4zDp7jy#kc*_uLx7cxtxc_L_uTrTW>{EBf= z%v4S$MqQKBZJN+X3goK5p548{Nru+loLa^6KD=fd z$VAJyU92+!*1^*dLpO4+j!^mT04DptDP;4SotEA?o1@DHsrcqkglj`uT%bXI_ zS`yd4FtGc+*<1tjBk8}TWZ1d!vF-)8s;^Z1^*u9hh0Bu$e`mlE%F(Jyyh?Tw`m&!g3Pc`fL(TxxbdsGJLvbv|H_4}M z+|Yk3rPa}w%b|AsfUaNhGysUJqr`8PT2kbYNcUT@_Y+M1?9sN?^_0P^8aD;6Rd8Y! zbWQ5qp-+W2!0UV`xKOBCx}&7O?-SZAG##FOyS#=MvnH~)nx9`8S9-{b_3<$VXzGub=2YnZNq@eVI49ut1u4(x4fsP{aYm44nd z998sC#qOf$c==YwpOWcKO_eIwvncy>nR#Gq;854-9-DK|l)Q@* zQ{t{nVYDs53kZg{gN=M*Hlb?Fp_y20NN(cZrFcs-z8zD1=&kYzf<@!(hK7EmCn<&c z?L68`&JS8{QH{8GnUD7m=ee-k>5Od?zFRch^&vu92Mleam_M91=&kc8q+nA{P#z(t9FBwxntNlkKRtheIbE zi^e^qZ}#T2a4e--ztehePyvbHg@xQ4n+#$fjuqU%>3Vmd!vMCqnn5ebmC*;aEowLI z2c&|SxjCk}ES+`(OgS?#bpnxiLT-OsH5mzinr~Nj+mxv4_D6U@CQdGY%+k|UH5z>W z?z-vq82_Wfm^zQg!!^K*FkZ;P=CY_YdjOwqu(>&3QAus4YK&mAf zYg44z_aIyhg^owNHyyur+RTP14IYu#P8F!jnNoB!Dj?d z9h6mw)HKr2RKIaNaG!TVUb@h;{msf<-;b(Wj97$GB%x3@XObpknWioX;rZ7W;rQc#vYyh z<@?vBqhjL~!SNCtD@2_FU4{+;4AjJ}J%Q)K$OYGSo(z-2x84LPDMKnoy%MFLV28WU z64At}+ONMOL1GN15LKUa84?i>({z~Bg<$rj0ZwYVh8+H5=KC8W&On?|zcC_5m_MD~ z>)#f3zF*EvwR^};ON{jKGf9>19x?7M^5rwIwG8>W9Xke3e49iWh5?3WCi6L{Ku`ihm3 zB`YI#-NTy_og4=|jqxVoH$J4U?i4D2S}u)>s(${4tP{9JIQPtlxEwzm@2#{}=NupR z1L+n)d%z&twsBremX{FTi>^nG{+skL{i& zBDW|qx%S1rE-iW@H^ItvGyDy9ocDvLl}P?x=A6LVsqyD&$2pbv^_qE-B)JNuQacCH zhA=b?U;|mB^Z=H!2DZiG4GPc(ZwN}KKzu{>Z-PQTv#i7JjlI@Q@{u4MIn%>l+&}4bD0y^b-1#=#FyYyde)=07 z96+~mSf|O$sUS7qo%j=2Ma$wi(uYU~Z0w4Rhz}_mU3+m2S1?eY^i0>fwa)DB%-~o9 z_O0_mct{yq+g=(R<#&n{veyL+mv{hva;%+ov%eilUiz=^nC(>_5Ao}&=nXGG2o6ZC zV5H(>x*BmkEbXc=ZP55`#3UD)>x6%5@<}%t-o{u;Kw}XVETA7*>iVquQQ+H=&#i)O ze8EH=WRQDCqxg+bQq&jE`Bl(mDpx<5SmIh^k!6iFm1ZtNdF{IIS0_A^zsGw0QR8b9 z|4K{z6d#$V4j!R9q^v<3b*v57DCqGtvZ7X9ttUp6$%wjww13u!(PZoz5<*)>OWDeT zol0ZkgUyEU6I~Dit2~Ftp;cu}^__!+wY2!Qq~x-xf^eDuinjM`+mX~0h%qxWqp|lE zrLK3@VW%fG;25&Jm$bFntoBiNb2E2xQb}5UjEPi|FOgAfab@<&fBlGo(>gouarkG} zs<{3BzNV&k2*H9=P&>x;^j=5n@cBjhmbc@GoIlry6V0S#-MLAko22OimE;uzv@lRa zM6a;!}<{Oz3j^s>-E*QvKoluv%|p-SKea3u5%{eIK;HO@IXB^Kvo> zIYs}Mx1_rJ&3b&f_89LMac0dUZO9=skxxKu?sle!6f zp(^jBjItV#jrP|?b~+99fdC_QkG{H!l4F_xPMb~W_wc{mqiJ;lsK0*t8_s)po4*nb z@lc*}+z~F)DW)VRtZ&X2sTY2MVkRY$JMZI@)UU(VA~e)xU1=0N0SlO)`hMwJD?g{I z^qJPn$(MW}6*7TKOc4yrJzAS->SY$g;rkZ9 zUpLzO{X96@qTykoF`jGE@GU6Qo0?^|ADRs6Q&=Wvzd3|+6zq-I{%0% z?(2LPb0JvNG;q<^qJF${uWvkNoRUlXQ)bV))V7&X^H`bT^A$gDXwN}~GBGDA!aKWI zswP^D-Cmp-sXfw}z@1S3;|%t@nXs>>OS)Wt?)4(qEnBl^yejXo2)8rc)zaS#klysc zbyD;G33Ybzk7xTX? zhuxnY^0C$A>{QYgth^~y1ijhoJC`7;%wRMMnpfuD*%{Fwqt<2%090cH z8Z5puAFJU%5%5|%T@iQ9X6_ScD5(C4>MzHP)m~8g%;W9uGdFmc$tQ|9tS`Cs(_K56 zFO8#B;{7s6{~Lf0AOQoq$qHWwMr9_l4S?HiFe$-A7dK<((9O*6_`o_>t_}7Oj#q>j z#{k_^vIP{=@qW1DUE*^pUO?9X6aV1-tcHYk{^di zSd9BUo*xJa^`0$N8`K#3A-G?AUPWQ3o`!|x_3cumeBBvev%Z3i#renV`Ue!-Oal6r zwqu-AGb08uE)?+2>)21=jQs`a+CKaqP_5#VvkMbVFo2agu;@LO%q|S;X5A-k<7bSD zsn)FD;2Ubtw5++gz1ED#YYi?r{4>Wpg2yG5}HST8CGuo&7%7ALUJp6@y zd_2Q@S+nizXtPS=oV5J?jPE z)t+6s>3Cs<1Ch5{5xwmG6p93Dhf316nt$uDJPI-hinoWm8c?y0GE7Lt#J(zbT2`(p_A(k0Ob=G)VY{`u~xp?z9fQ$q|7Z>77wVYxP&r7*k!;)OsFaH{V0u(Qm3}7q@M(a~W(Yx4@qorO z48*|fy?))=kR=PP2V?RGMc~v;{)C=P3P9>H%8mKcq*TvMd?6~wlwzRcV4>Ks=F$Ji z(wrBBhC>i?;_K*JI>*ka8r1cVbo850&qQMzxtjEMB1JLDf4tl2Zu|5^MKtBR$XW-5D4yZ|j7pa`4vfg^ z7#*x#^ib->wb=eZHf(*+d*Elz-FH18`9e9&f_WO|zGAiYYGXx6OP3S*@0NyB$aSge zjzq$V)=Rmd{0lMvzaVXgdzdS@++W#qT*0^QG6eY5yNPUlmqovIGbOmtetzySpT~LvVL@cX#&y!QCOaySux) zyF+l>lbJjB4%w%D-zUC9e}9*Bby-(c<&oYlB)M!>snvwe4QR9aG8x4LjG~FPBwU;# zw#jkZj1`%pR)-&OCBCh_zsQip^X zkCPOx-txm2fkZ<8K}qxrtT%q}wYaP)6Ntf8qlP;aD0Fk-W18p})qJK$AcPM-K1uRi zs8{roz~c`B5)Q{X6*U3!WHU30##mGVCs5Ar)DA9$(zpQ>mw@5Rb{6sA1!t}>iMJNW<6z10Fr z7bMyP*<~g~gv%PRXsM1e_rlJc4MKfk#)N5AwvW!f(R`NEfDFlW7JmU6vW$TafH$I`drg=Ht(4G0F?e&l z3(e&d%#PBC6j!Do9fjm7o9a}H18;de?=jq|ck2iWOM5^hp$$8L?K3#E5Ame#`Do1hAzKUi@cD&I~*6N1F(}lNo#Cn$X)_q zH=q0lNFe;2ag(1*nzokTjX>kYgTX+k@)i_-bW+E9J0j?_%9jptw&fj+ ze45`-cGDVwC*svm|4IFnb-%K9Wlr@J2=T%2!A1I62jxV*xDQ;;SUH5Zsp}JD0Ki@O zoBF@*PRFPKP&!xx9vyJ#H6x@{@wa$=Y>F)buoXzFd1|z8L*6x!c2i_Xo4Z>Rkp7J$O{parz%rFex*8wi z@O+e2%L2Lt!tbPnJP`>~Qx_uCfP$2#P*A$k*yb>f3xi1ubBZXGbNI&z`&-durhppb z$g_6J(7y-F0~!u0qJ%CZ{YTFvf{!HxckM2t0)%^y$;T#Nscm0U(Gd z*&}~OA$)&o0v#gW_5p}sVDB{^pg5NVu+b{0aPOZBbO6XY0)KW;@_;spdfN;Be-i#? z@BjNL@R-A{lK2E3J^Pvp6&db8OS!OnSPqQSN;AsBOG$+}%9cCI^S>^J`%anm@tw`H zs+Wz?k|~I#ad7OKt1ZN*tZ1I*M|r##p9u$$`m{3XSEjdUFM|(J(=!q6)S>1QMfiHJ z4Jsm@uPOY~zU7rw16rIqE2woQC(P$zZ*UTNDMw7_Ve5v0gn?F#m+`}bwC{y=BM{Ge zdDO3};$o*W!7}N|uPSRa1<&G};f%$PGE0G1s|H)v&?8_vy`Unb(SQkf`^UnhqS}#d40tFInI(%^WMNZ7e?c7E zv_Wo~VyY2a^$r}|Rm#!O0OD79C)}29AoZ&pKl7b9Y>!%^B_WO8%;u~fER6VTAJygG zZg@u`Fe-lFc+eV)=(<^w8roxccp|N^ktx30ua9^x9Nj5C zs^-Rj>v4jwPKsH_b~DUEen z6*8*C@44mQjQbE=ZyLPRNY=WAe+1c6E zRZb2LbyvodfT4)3U1UY~`@^4LdE$pK*Q{^j6^Jp>6yFQQ_AFC(!etAY`WR86X;M8{ z+VL6K4xqUARsqHNFBpu3gh|h^bM%N`ZXPnjy(~l}MJOBQ2~Pu? zNFcAKk?yB#^E+}iWQ8_Em~;QD4<|6 zdpVL{JJzs<%K;Hmtu;pQX7`Z}c0tQ6=kl z#DgK%Gxf6sNm7U02#o`TE^5{MZz^!1`*d-+*l2o1QV=W>?OaG++v6 zxZ}@aMwu+3AtQ3O)45BlL1&}u1{M>S2iG~j+jDWN%5U>drPOoySRO$Khpsj|K|nxc zaJr$Yr9y*(q{jvX1n_0?OeNCmS)!U`HQdeabFca_Hr!EZa^bt@>>!4lh|u=PUqo*b z2Ykkq2|6K0D>u8B`gx}WKl8rg zWlE*Xc1`@LuJu4*r3U9upV0TG>_dq+k>;?hEa!75g@J+Lc6;K}?psbxK#;A~?%5zn z^dky*u`CmRp0(I$rB11FG**?Rn-6iU5&Z}EYL0odY*hv)69Gw|^=$x$C8nDD@D|uf ze5E8|qrs;!8^X%aCi{mKw2y=q2mA@kTZMvfY3t5HeN`A$Z?y%xCg{XZ2{xy$5{oV_ z--zG4D2Bw=y?{=>bZBVk32<>Q-vW?wcQps3{Cd7@4c~$bSiE&*hg67h=z;3 zmOegCNN29g(JAUks-SV*%MzQ?lM-iNhxCcR(hvVrhGiH&XZMs9ZC&!z1G`s%&UIMo zJ|q~u=4jsH*){K1=tgALgFAC0N_CjtRxOToCIYLo5Wx%fpt@F9Tvptn1%^(iF49iBy*1)uMUr&fc8fr-egC;`D5-g10E&LrH46UkYkyXppv6S5+l5o5d?rou$__Aikia{fhtb zFWPtjnSxV^jZg5nbW-a8$=!bc6oZZVnKKK5Xd(VBl4EeYO@(0$%4 zr6^bgBx6Z3{dz-`&n{=D7B2A zlm>ab{$F3gLq(Mx;PX&O{;?epf0Hj4=s!FB1L{{S&|zpgQts8C3sGg6VV{jmUa2>l5AXN)%?2eK8E|ExExgym}5 zx3b&2b@mz5@=P&`^Su`c2t5RNtj}L!qA%yu)*Z>&4gT<30EE=w^IT^x;blSo`Ds}c zfLcmWp~d%y0)%MHzj3SD7nPd25(p&t9yOok03Ln)9fhN#(>zL`N~sJkC8%9$z1v@h z)32|u_2N!H3E%OK!)Ih zJNV|wxQDNyBb2CL#14AVudWIhHKG*6eSQ{Oz#!mF=xrrMNl8gcO6s10g!B*J(vT-i zYr(+4xVX61*4A_{shM$v<^e7@KpAszaFp51k91#_x4%Aa_`w5aQS`J2w6(PXn(JNm ztw{5bfDycZ4vdfY@8fiLnmtdFzSg4yyGQV4?(OYuq50w5uJP}6H;diOHmmeciIW|UzJr= zQ-Hd3beGx>oEr6`VWF(+?$@TQu_x<4KQ|Ty&0NV)Gvo{Wf!c{tVQ^8UMXomB6FZ1pjg0HrZbzNk`P_MWt+&XI_p`+5LgSW zElI&x#@t=@9g=WK)=2O7xv>8v{*9RrRt!+S&ico0_+OWpw4&$HWgW@T;$Es1+Q3{) z2=LFs5IC@AW@f?A$*2x`uhcTd@9&rQBGfMtCGD&_8IB9;?E=A4^~N7)ScXRoL{`zGFm0{DH8Ih_1$CF4C-gULy38MYUVGG5M_&qtJp z4I~Qf9J%f{^bU{AJdmCYupAd*K_teC1kb%Z6^$r!BUKnnL*;^QKM$L=6(IZ(x~Ew|xquwHVv;4L9of z0E5Pr9YtRhZe>WlsDpO-E5Fx9WN4wyd=M}I3u4XlEgFw2WPH4RDMF>m#4yohBrfp! z2iL2)llsWq7k8}k8n)g4Wg^R1fK5&t{ry*M?E0F%xyjEKuDUtR2t}TsFA-8=qxE&4 zotQgnU%%X~Z-)ncXVlDz#W@Te=YCtMNvcop3l0fctTKSne6YM?*dAj)XF7Elw(ib2 z)^tC0gDt@5$A_1?YP}!46UGi|fAkU)=Kq|!emB}g%kt_GO+D%L>sjy`=ay02vdQ`6 zX_mI+DW#hEelxFp4EKBW7R^iJ*`R{(t}Wz5)kF2(6MbHuPc@g;*A?GkMOVx9A+!Ws z>h6f`Zn1S2U1u`faVGupl-94m0=#BUlxtmaDX!DrHUWW#(+;#oGg?-K@oXwzNe1~AVLmlBuJJ-EdKaz5Wy zUGkczp%k!`s#LS4VXF&pTy%8_gX;mw*c-pt;i9$QcQQz_|8y)>Zb`$a_Ouy!MS*FT zt=fh%Jiq$Ve?no_8t>Uua@a^}HVmtzqqBLo+U&yL{(L2-uC5L!LmSbqhahpyrF4}( zmyn*BA@repM#0C&kHVPFmpJ};GazU43cAg9Te@$q>GjeqH74PC?9h8`)%vu|EyQuM z993nRgZJ84pAH|Zl<~B21CPT_yl|5dB8_?^_Jrf1leWIFKNk)spOCKp_E;_`)yFPs zUl-b(?pBtuH|4`Re~$gdjxSN5JmJ2IvFg`j@~?ATcUx>m>nk^_ennFEm$@s_@!~cW z_Av-JWawHn+Vp#zpwRmWkC)Ofc@SI+HciG;K%~=W_A{Q0eQ3n5cW8ks98Z#^ie_a+ zry%^@zP^c>bWhJOVvZRUiu7fv{uOe+;9=xyOj;-|Q$C6IBpz(7vfrk=5*b(E`@xL( zi*~P_u zIl(KQ>9iN^NzDx1jkb=uNDd}LIUV=w-f3jtaA+j8TH8^`Qr`LqlwCy9m%S&I^AR<_ z{sZddl9dQP8h=nyEyTc`=EX@DPs3p_Vc99 zm3okrFfjkm41(dLF&;UTjm^C7_Y0YgN=@vs~z;|ByZ)-gKOoJ-=o=8914;WEWYEpL`fpD z;rIZK%C+VtA?;{NY_K^z)eAvi+^E$#ivIY|>Cz-Q&5CU8oDmO=agBhl2Vq}cP)hR! z9i+EM(ORw6+u*QRatjKY8yj^n>Ob1#014eNGhoe83YmyaMbM1qH*oy z^Iq;ksCGPb(8Wuh&|TjQ^0UBYFnRJUc(VtAu~l>{%lpf!mNhjk36&msi7CB+9Rp=;tom%&OZ_r>%OCO_F>8jf|=IV&MXgQLW zuajiDeZuZHQ{O-BEq-~STnI5INyr`KAdIYOlt%z-$SCZj6_?{iO4EkUwVxgSd1s+eIfv10e!D?s;E)s%8zFI7MlVtMPpiWu4Qxs>R zb-Ldtfq4E^0ufT)jccKYji!aQqp31$pRz8+HM1+juqPaT@0Zfqi|y(6*k|bO^{UXy zuoRa4=*)Od1>#f5eyx|a(`F8ZZ$zi7UZ#c%8}Tk1YdoN@@RgamCn($f_j#$I7(1q| z&AkolmP?Fwsw!3n1rLhdo`_&VVy0$`d!6CDK?^r`y6e(pc!KEq5NNq*|0pnljXq*L zvt*3P6~6K>VJVKjCg1r*9CYa^DJoF6iWka!yPI2w>iW6_ zMI&^EDo?2Bw7ZMT$hOMc1V6QPq31l+k(8tRD(grG{i?WG#kt$EmEEnats~RXF;@o7 zJ*){V2}k;g#JGwx0>+0@M8!Qdl};s#eUyXUBb4ibEu);I*5b8sIsx-nb{lu|Zdt!T3>HC*H!)I*jYq3J=z{8o!h{T@hbunJ@iXV`}e-+_xAvzI3j-5+E?j`#I{I zRlwQLXOSoCvShtkSAgoKo^CH_1c{7{?BVWiYGzh>j~=>p#hA&aH!Bo#!qa{zx08D? zOt*+@Avr=hCbrgWc-z0&r+zcEI^KS{ddZmd|w4fkM>NS?MwB_dxaug47Z^;1oo7TOIhkz!XFhf!(OTbwVu0)|l~yUU~# zp?hZ)+_!oP<@z0UCLLMy?z&PFhnX{P)gKEDj|C;PP?MnzbX~uF8wv@{7jiHbLj8ms z3b)a=7(tugqL<>tGxD!|)k+7bKJRwM_q*j*$H5kt>-1&!?-qqG8&d0@OT@dy`{{|y z$007m^Oj{BB*dRp>1ll~)gH;4Jz8|1L;DGdQW9Fq%`smF`)ECvXuVj?4A!>nBnTm^ z9k8yi`@=E1Q@Xd$kK2r~sIy$8vD=yx)%~L>Za;W#g^(_YisY|(e}t)og0?Khk!x%;P*($0dC8`g#=>%| znKY4}lC+s86hqM|v6MbgRPcV;lYZ?iZB;8hxeoT2CNZ`=J{O&`^8v+dOb01;|AUXn z7Hw}Pq~UcIkwI!JXFLbMC3c6HlN9E-xMhsgmWhK#CTT1+gllZX($fexyk;Mc5dGoo zcnxt|N6v4@6(ho>5IWQ#cDc12NvnRf*rJjWyQKmlF*TtlL)57#fQFsw=Y?RaSM|NM z3GEX;QiIB2MZlaRQVh#1B6ma+qT5>{^0HgEQ10t95ASlv?g6g9Lq@=WLd(3({=pbh z-dZcIkuZ~e)29??g{RlIlU9##y+svLcSM@|IOymQd7j8q$r?nJZfc!;_Y4wjZKbqjF(g@@$c!Gzi4l zb-g`C^t-Mo%9;D2|dyF_ild13Vm_ub07 zi}j(<|5X%frc+hd8*_*EVPqsl84K*3Wx+}S55IhsZ|{D3WaRCp`|akAYx7~O-s?JL z+Dpae`6Y>=-^z$qj0_RP9|)fZ65;nBI!;i1%Fsg_Yu=DK6xj+o3DyMUrC1HsS=;Us z@{z=VcvLDg228Sfnn}}!tcr;e+FZMt0EEM5g&|XsQoL0TTdP#u5<=>$3Kye&FFHq7J8Y9}_-GozjxnH?FmkX}m z%B#gmZPB!P+z8x5$p4}IZ*Yr38-VuIx25?u$30w~oXCSio1!1Lyx62mJ!S~N?p`$5 zH#ndHW$->gBce~Td;Alb`Ts%8=(A6K>!}4TAc|W|_sMiRAar$2ZOV1%waJ!f0Y~gO zw|d^@K5A#@a*iy60=|Q6xm_|_ifn4I`bPa9q;XI{FF=pd&fWJD+Mf+RgR;35!~BAL z;X9w`*Ylin>|>4Rq^Rq8uxUS5kW&Qe1nG@${ zq?p+MU_}}PWQdJU`+Au1dYG4>`BhI0#vX1tW1?A4Q=TWOri|@N7|B@Z)gEg*hGV1> zs@BVq(&0Sc0KrE;pls4L`Hoa=`JgO*3#h+O6bBKM&RY95;#csmJM<>e<4`@IBxN>y zZqG-CDIDH@6*2Sh>ozYI>*-kP_cNuqEBis=P_(N2I@U0aQ z6ZJKGhy^N@dyuwL)}Qll^#(D&-kq=a)8w>2q_jVTWZZ}R2Ebh$CDZ;7H~xE#w1Iq} z=20@I%vmn)EUes?BVQIGId5j+?hVV{!ZA+WE_xGS%&ZUZjr6ywSLP@Q? zpQ~@XDe!tMxPSUBU*)vcFj(0o!;#Z)zU+sZUWZ}rFJZXu=WgpyhiJGBX5>Fh_qI3P z#T1r$rOBpI2G&1|Wx z`((NOavWb>80mu~+-GAb%ZKhmKWhryuNjkQ;l5w{)psuYi)IDiHg`;N%Y+sAHaAY_ zVWdv}eXh~DPtZ!}` z*74bOp_|g^%Y8hCV7Sp5yJKqRIi#1%*|4=tJ*+~|_|2{jl__3__>fSJXz zoGUx;;0j9tgMNdv^(ipAO0}yxai8l$8<4~Ll`$+8h1z}{urSzYx)_oqm0M#@rB_RW zbzeG%BLc>5Qc_n_+y}-y*m8|pjpl|v$1e?$I&A74W;gDNTA=VT@&+f3Ev*j?_{%K! z+NFrkKI-Z}cwrar4;*Z4B z+CC)t@@ky2oUMgY2`uHAwyF+|RuK}II$kCXP)ilAnJe0rVkyz&4q@syD?>l{b-Luuc`;We^@8*rN9Kuz+BTbuzdTi8G%C{Dp-o>&OPka- z;aHigEH;lNPz!-g8fB`6KNz(--&K{7nY1Nt&eF|mmEFoBP@qGXQ$18likwrV%!&`3 zUxRfgtyI}E=CsRRj6A+LQz8rSBO@fyu?5;-)6G?j@#1RkQuR*jypkBr^&10uyKq}; zqmhtDFUSl1?U4M(FR>;ACHbP2Mc*@i^Rk%nqEf2(G>Q8nlUmq86I=>^NWxz5>Lc z_Q!ZE23RC*v~q^QmjWg;@paBNm%Qb2s5Z*EZY0yqStI#cXct4EGtg3i)7I`!tx za;|0Ggnz4->HK0p2U9?V5LlkCWp+ynP{17iIuzCLwTu=gcF)BUn{Nl8;S7VTI|==Dal z&3vvI{W~=^BuIbYe$%R*gcqVT7Ip+UemUW>^`29d!bvIzMeg+7`tHXo_bZHRBUpi72 z`oQt&%2sRM%yki)r*4r~G|h?!M8O4$4YQ)rXf}jAyEBCf20qy1V8p{$NJXMGdN#T2 z5}0L@HJUEE=61KoE``!itGk2u6t&t~JC@1P_R`RB=v`>6o7@(P*t)TnTiaL@v|=8g zyI&=kDaF%hx(Xf3X%}FyC_Bej z5zl2gX->Q88)?huNWGWgZUR8k5|C@7W2&LOt0|+b_{j##TS}eVMeBl_J_ZDq3gvRU zXaoTrd3pu#t+|P4IUR+*%?JrCJ25JT zK8SnA&r zvw}c%hNu````~OCF}?gTE<#OD3EZF#DkSX~o!zHF!_`r`Wy;dXYDIR>uA4nazpJ8l za6!C~;Jx;?M@)Qn@3r5>Gwt{D?ne_de~1i4d+Pg{+f5!#|4_8*SX|n}QlHXGM~%`4 z8d3D`z{|P{^0OoE45FyiQ|5O_WdgB()L$wms4?!$;xzfqK`;W_l!x@MwyoeAA^rCf z2BV-~+^FTZpnO4sq}9wKoDA0jRNt zjX(#I%DbZm=G#Ez^PWRNCVT39c`9%Be0v3@E`)l=1c0|og2;AqzLh2_{rRiH)33LC z>!uPV=OaL0#8kmJiS@#Eu;OS z`0ZgoR+z}>#*A>+K~OAIZi^PAgP zBLQFBpNy&w1WGGURL;4(N(i1ffc%G?MFIhKbq0+U*Z4F1 z_>S%a=pXtyYdUIp4TzJV!2N`&NTc6JZokD~I_$R|H8eE*Z7AS9FZ}MqIGTbD`T%vP zujy-NKlJ~=1t4pnke^L8F4NW@(wxp#I<-nDqrl@agz>1O|HJ{XdvM3jaj2i4-|x^d zO%lRulCgxcWH@|!LFq@xAN1paeh2p%oJUb~sp#w2Wna7eNX3}$cJ}3TO4aA;|HdK^ zF>$O31n6a(`*j*e)@(HPaVSiQ_aO0K^t!nShC zHuk65@6d{mOUttOJ4)n6{!&q#aIh1oddSWpzxIN(#!{dvWdc^vEoXu_d>EnYXexXg zDKu6;)d*YK=BEBL-6S>IQ0ZlHBq4CQo$o+{fc6PZns~3?1*Q*N#ZTJnS<4*d5^Nv9 z< z0tC^;N`%q1w>{=077JwRstxKDVFhznNiD|7%;YhZOSP2Y$I1%0KfakZ|ByuM%P`5H z0_hHSjoHe>h8v2{HB`VC8Ro(`F#njo#8$Z($LAxy zN*%yJQ!MsbWPqy_iemd-aGuMqt`tJ1e)cvd6 z@pprI01GDi`EM<~mzZd9k@(YulyC_!<&>4&55=^)LUjSJ@J3#@Mz~(jmyE4t(lnri z?JciZYPfIrbHdz6vRqG_J4Pg$%5;>7;k~3L95`Mjs-;hyq~zT|R+0VXpoS3876~|B zj~kgv<5?H?V0ZmTv#!qGR)h|m1j&YrJ?XarOGE0B9OC1HLLeJ5YOIze5pN%l^byfn zt%rVkaN*L=L01%IZ`-2`} z<)l4Q=)=(Po6!|3DiX4+TAY5cSLd8KnT1D9ex2;5b)C9+kl%?TT^{F3Ps(5eAc&)_ zM^~7E#m`bDOWNx39YmCpo)KdB1Y2HWa2@w5{@n()ckAneew!{AkV$NH@+SnM^jX}P z;!<6<*g4!r28=F2b#XlL`uMFGze@QwXC?Z06j^;Dkva;O4+F}RS4&L_;sL*7)6%R| z3fl=Hfmlnzl%8VZbiT{bO9T->eA+oNq)MX2lBEy^L|>D*Bo}*(mgSwKz0yKTv|fs` zr6bo@bL$_{i9S<Si=fo4>Ng@*D3H)}lc(U__ z@JB-+Vv@u;KI1m77RZdC$0wH@NUjALe}+4aOCE;|w1x6tMQ`xRKOL*JW_*;Qu3U|_JE#u;{^oYDn$SX2HCU!U7XUK z&P#%e8_{Xjw#$fdk25>mmjmIq`^dNZ2-dZZ-P~BvQ_bIr9;y8Fbg$Qkv(0JBh$|!R z=9lL7K;$FbC8|iN@wGHfOBI@C{(>SwIo>0fb(KH(Jt0=QMx!CdRlY{jtl?k=0!wh}09R^EdFipUs(Q5koPXul$fvHzd#`=$lpO6P!X@&+ULS$<-sdriGj}41A#XVpwl!_&y z3eU|gPMF|uT0QXkfMCoq1R>H4Th~e$W{?|QV>A-$1Icr88(Y&CC}vY&2dGJAz++20 z8{}I1YwH!tt+xAE$V`ruSBripLq7IEn=A6>LX<^C+|bJimT9j4z6D?b*p_a?SPIWe z2CYqq64qh|^gyv>x#o(CekRI9gxX8sZ1F74m4-jlB-BgxCP)z$mlme*CN#<5FpI)X z%Db&seDFbSTf`(<#a7H>XJtLwtF>0O5zOg(h#-UEeu%E(qqw6VVSyZp3j?};0=FGBR?Ue~*#OJI>LAMzgYIpxY+SYmpv z?;cKIt<(8&b98%Dgvc)-ZeV&8n;27Sr36gK$jENS@BeC)CRhw4j-tA%(|~0;R#b6^ za-i7GLWzuzClgb;iH?Sp@M*b^x4?gmy4!|*^c3{?=*s)2<984JWCcwPrEeZEkYqeS zOBzVYe%N5?3j3aXkoCo~kPffDhGn}4v?NgUMX0MT_gW1h4yT=}S@ z&eX9eQ>)S01>TasYH3#K;aTmE(03xgIFs`Ot2}xU2svZ%rWN#ahpymCQoNJ1|IS#= z$V)>4Oox$u`_FFCU(tNkaDlL0EFVJpkyQ|?Zx(Iwp)5IBfaL2ZZ$Nb+uR3B4wPP>*Uv&c=24h7ZDn*LBU4(UbwP(j)3USbqC>E=M0DC%Ep6l$5M%mj$v{x{ zfe&HPw@!&o^sE)YCYO*o*{62ig{VxhH%X>P^fM7bn5x1hB^IA-CW{$mVjV19DrO`c zJNGeK@@F<60yb-dW|;;oXKE*i4eGM65+!5$f%S?=x4NV0`O3gBkiX7}P@Z$^q7x7y z_m!}D!;%{N(=ys-*U?Q45ft?(Xb@x$vgOJp!43Ft2AcMOQk^BXb}OWHHE4S?0>>Db zYj)40Kn}Pr?6-uIx6yxxgj$@%uLiCz#Qxm8@O5b<(qfj%aC}HY|71HhjE<%jgCrjg zRnF3-kEBQiE^_MZG7IXg9E@_T2DgU=!7GXNi%SrAFh(A)d#+O=h%rXLj}+Ad5LnY^ zp$(CFl=Z&8un;88P0pv2>h`y%_Q-Z*L}z8JG`B~OjQf~7@*%$^(iq&=fDHH({R~dl z+O~}_k_*(?g|x%Vpby{rgxu*5sx^z9N7->pcB+?HK57vl{-Pk$6IEz=C3e@QL@Em( z6?l$6iCnAl!a8>y9gVHl>@iF?^h;9TvfY`o@C?`|Sg6ZguX1;$c1h?Of$+ct$2rf= z{)CYwGXNW0#&YErZ&9`;%`cB_fgM>X5F&0rsGq{xJ!%GSgeW^hxF~EdAn41b~BAm z#X$#_#bqz^!mPoPHy}+2iW_UnIH%oq=K_C3p(*gAPA5| zAe(d~jv=r93hSE*9V{NHlQ*@4-NJZ|dvK}^cG{E@sj;D|ns)7)82<>4M<*W>IKVOz zkF(w~#av9lD8Gyqx{p*lkZ0sv`k-Em@DW5Leov40gL7%-n^Q0>qqI-5cN1?9BRi;g zMtb^(=m%r=j6vHpXQU8+c%;a0-Cg0P^m87SV&BZWKLBaZe%)Nm#F5+yUmAK2{b+@Y zQ5(H_d`@|H{|x;3yB!b@LAc4!v4LMVn+PUBUCkp38iaVAeS_eE{*%V&^t_kODjC`} zIb975bRDAl^r2vE3~uvQgcK@U)iC9VOWvJ!_sCRweGMGPH(3eY4&kFRk7-;C)vs+G z*@k-YHx}S^zld|u-3TLuwd1SB}T6kx62UWVGZn|dAcoo>O&$e? zYXW_Y?CeY~Fnx05if$(}MGC4KvskbXK2dhEtwWlXrT!lArZ`pza%7ASSSa?3jdp8| z)(?0kMGL}d?F|TnK?(L9fp94Hg5na6e9j45-}!g(arxG5i!$T$ok~X4GElTmbRTjc#IdV3(Oce^RenouXaJ?y?#3ZklvU89lIo8{3YO1y^s75m}W{Oo1@L zHCvTfZ_=oU0@kC(pYA2ld0f+>JMaQe1LsoGIGxU2k-?{XXtc2Sl7&4Z^!gK&L%|-m zCARryIpgh0>~#LTUaRP({4HKFcESnUP^MC?m^obDOk0)XI0c5RnUW2AINu;(K!X@F zBgQSlh`udrrSS*VMh|WNODRLTLA`i6ZgT8Ur+#U(MvJgeoMX!F10xsIF`cWh7 zxn%kdIzls6*bX)&za1KB|e9zHphvd=8bvtPxmSQg6B(nJb##P%9*X zoStoYP_K#2mZlqynzo&0zMU|%6_1*t%%RS;c-aWbob|}wSIOl%(x+JP7*)5Sn`;wy z=b9@_CF%<6x%*mJm=1357_(@ZImqPd^a96V7A2ZwtgS+~k+s2mPI_>D4#Ntybsz_O zgsm3B*eA7cWcK#X;k`{_+gmu2l);1x{8pT5pn6pW&cNSG-Dq)A<#%2bdR;!R6J^IG zG;q-h4u0sEQg7hV^&{H}mgWGEAe?L16vpLfUd)H$~BU56n zOZU={SHqz~#Inyg=saIEWc+A+)`Tyf7<;kzI} z45kgcX1q=0_Sur$0N-~r(EO>Uk>RnL{$LbbP{zu*J`)mbJLXh2ek03@JA>}rIUveG z`zLi^6yjK;KzFRxsvjuXOqFOf=rM=W!U>9=v!UHK7StRW%?BH#`y4h6H^a+KL}@)O z;Y7#cHd$%zSof>^;s`$M!p&^O(WpWOo2ZB*+BYeAwH{Z}zgkT2s(Vl3Yd`(aQy!GaiZWc#Qp6`3j>}*w6 z|4hUSX^qxB?8L||YGF)SiuZa=;a{-W*tl~*xjI$8=e|)|k3ZLTVwR|zhbMW$(2|X!*=dpDnhF~BfNm4bn9}cF%U(D<{h^nOU%<4`;h2_=e8a{8Uo-6I zmEz_w31&GN43^7HJ0t_0fg+uIUZL62Re9~#a{Aw00RGGF ztT;rZj4H8R2~?d+G`2r4XtB9PHGLOIjEEGO&!Ul#u)mp)bHGH;xZZv&3-~gv+7XqI zeU&v0Dh##saana6`aAMU9`w!VKTjksaBpnZ1Gn315NIv6)@yG`K*Jj{Dffamfm!v7 zxw|Qa6rWOqdUDoj5F2!?&bs!kgDtzV%zn&n&fChfctkRa9SvYtOp+D11m3V3Fq7Un zpfuwvHohe3KE0|FSuACiW1n7S_rYT*G(7odM95%aGaCt`i))2PP`jvN#qfY}!UPeK zHvpG2ex`s3D@s?s@*~qp4z!sBpa#NrZZlI}{J6#W@(D%Wfb`i6T^Y3ra9z%8YQ*C_ zs)F<}^~7#3JFY_NrED37Hv@eLLvA>L4C}?YCb>Z}B5J{|rWV7&LmO*BZ70S>41K6*5f73X=edBis;R{x$xCi~u{p`BLVe9>t-x(^n z1rM0&=iPPjSKu72Ay%<>pYa0H_-M#Kcj3QQlP13H@iFfpJX*J3QsuRl2|5Wr^S9qQ zbyaq$A1t{mPjrXSBVDHjV`4lrB2I^oG=1Yu1~nPLS#%)86wh?5gY**`z+g$;JFRGe zZ8Q1AqLlD(X0*8aL@tTqfFa!*iv00_3Hzj%$FtCe@beBm!QGy{VS4ZF7n2uGu4ah(s&CeL_X8VJP9qY@2{K4${+6K*LtUvQaJvpr;}ARR5Irl=An z?>cHrnv=hO3xdZrS6dr}N{XBu)#5deXVY}J@A(lb!WkAd4H33qYLF!Af;laThd!zp zKk3;bgOgpnZc7-+?=`PVWPGK2ZB?VJdG<}(IlH}m&5qj%-`?Q!p!WvG9rqV9<~6+P zd6E(KP_a_E$&%(9ryzESsY}d9i+pB*lx^>Fhd=Q%llsrcNcf}S(gAi@CU4BQ9~#5N zW()KVg}?52DkP~cOFI>gdOD3tuFs&fIf(+a2V^(%C$xBG3CX9=qiH2Il40aA0MI3e z_IGJcCq+xgH6~bsGCg}_DJdgQ>-)*7C_Yx{*8}g~{ zCE<_)tR$p2@j5u`^CQ1xk%vV1-}m_!)r);6^H}C9%464Iq3hw8@-W#fe)%WF!aB&82`E9ef-N0Rm$o%C^2d1%oTBtg!tqC zyy1K(IRNbaEoMO~piC!iFqulV8rBZ8D{)3=I4Fk?^a26;O{;1hw*sVptkj9&aRqo#A~w4o4JfzP6ewCu4c?N zT9t*W;S-F0P5m*j$&K74hxqh~*y?T-987>0p8?kaS^Vd&@1^IZ`-dO0Ko?)=V$EQ! z4n6lUN3)_vv`CE@NurcD4BiX^bh9oHrF!6G78F*qhvlAAf$Pt&A^Dl~e9K~`VPQk-#XUF&`umIPD?3q!T#uB= zVr1UM0^0)(og1`96t`;*S>D#0IAf8yN{6gaxP>+xA_KIH10pfz=)2955H!}yuC%0J znhP~pC=CAgiR(L95G0U^&72iZ&8h7K^#Zs2V3kZHC&Xoh9p$s^nNDYz-1=G#3$8=?1Nr2i&y646a1-aeZaim+_%Y6qq)1Me^_Eqq zl7L35C8;T@+<{qty0kgF+(`Oho6;=ieJtvLR9^uKLKuk&O`6=eN|AZyhormpnTG}` z4{DTlCOeGfa&e{sb8=+(fvFs!i%`7^_gFR;C zxhBY&DK})4btqG9Xe=E*m9eA-tk%+yHkSTD8RSuz!`97^S1q}Ahs%tqcKa;*V13*# zx)55=M`fu1xt#UU95t3{U_k}${cCvAcen{SfhtUR%!rH7IF1W-f7{$uSns>DDiCd4 z&OAL-R~Qw3&>2W&tO}(?hkvUgYYr^{7s)ip5GaaVElC9yevv?s1w#SsuDg<^?M)WD*Y7X!)DyVgM^|p+fj*TyUgF5KS9SkMw z*U->xsA{l$D;eQxt0=Y=h^{9hSXRZ1Di;6chv^A9YG|B7)?!F$%|yUX+5}6Bvp*ND zK#6fO04LV|o3qbCksAZ7$B%l!y0+WR0qhJAS-2r!@4l}uKFRxNc53}7M&x_h9Le)H z_3?T}xT+QY^?)HtXQrr-aK~0mpyMc6@|7*JxnIPF25 z-Q{IZQQx6j!A$*xlI;4G*QfcoR#_{)GM6q_s?1<}ckXNQ`Vr*`%NQeM{|i7kf@dGJ zt8TO^?16#7p;#|?WH3xKNoOa(1ETd}-F>1#50oT=qrVyx)M@ZC^-w|7T8SZc z!8ipMw`cv9Dp}^#TQlw}WL|sCQkQ1O0R@$%^AHso?4*JL*|PwwGQb)_uDXl5Wx39! zS^EvjMGCGnHrK4QJ=b7dkA1cgu#_Dx5a=W-OZ(+~GUX>v*4cOIC2uaZ?G^p_H7dB! z%1G0XmN{doscJ1*b+a;vm%f>Ej9?#qnrl^J-=Bju-~Rp<%7jhId!c}2 zT+M?GS!;*jpUIt-A!p4dvv3)EL9!&B&%0JC+o_%Tk=AJ~I}#kz+8IHWB84){QYV`A z_R+YrWrp!|IyGzUY;D-s^!w4f>~aI+Pl5Taadp^A10uLQJi43O$83TtRns#ex#=_{ zNlIm0%2!khk(hx4d8#l2;im{#C6O|odGO))rYf#e`ygyBk>rALxEQVXRQa%6GkA3%`nO|DFR{*)vLJGqt-_F0}GrSH+Pr7 zjA5WuIr^m5R)-T-tL*Hnj?XDwuy)_OVs$#VmK%&=K+h*3kRY1F>sDaQHQp?G!Ih;Eltnky2%9$9q-YkC(u6P55mV)O4I2lZ=0&+ zW!x-ixN4e;Y#VNy5E}NIMH12q=bL%*sj#od^9)^sxUf(NX)<<_8f$Bi!l)-yL#DbV zUQP?4D7=rs!&(Mh_L|L3ND^(LpHoR|IttlGb#-$;sF)?;h&>_Gr=U#`g`o zB{nQWkJinYv4I;{07XzA_XBB(bPbL0$i>|TQupRv6s=!o#oyFRep-y->L-xs4%3`N z5ZtA*;?+B|1yv%eo7yK;f|d&tr}0I496=(lLz6}H-lhYSbp=wS`i#%x( z8^h-^y2wPruL0fA1>Nih93bm09H~lG6_glq6x2qHtK61wylbj}f9tyeWWLPAa_tbuoM%MqqvkJTkm!=SOZ%XRHnhf(ZqHMCKAJpzv=;P;Bs-eNdPLXkvpX zrtA%%Qh04C3XDQAgKk!e6E%{W;`A-+v zdA+-$qm)cu?xr9-I4V;cY!CMWlCZ5-7!+{d_)M3}YCMF2$#(o#*4030K)q)-ZkPU! z&+oPJL`e3O-{IdwrgEY>dl>9VF4E}4d#f0OOgf}(vDhF(0T-LZWj{cYL2*^b4B4rnF|lewc} z|A_&ZHT8gF=|UQFwv zB{gRJ0h|YK<`g-RDBbEw)DMF>JeCJ2F6g91I>b%B3;X=5~t{Z_m_7(B|W8&(&HIPIrwm?o*dE zlg2gb(2%d*sG)!+qLMO=`+a8D@{N_%lnTY97+F?(7(DwXrk z6ZcNqcIps3{C*!3xQT&beg2HxjAuaOifcy_3}^{C&Snzuf|Oo=7Nnt!Jx(>bIVJ(hFm&GUZs_K%(GmLldS0fJAx+x}yb^h>ghuah-}?i= zQm64C_5-v&`B;yGJ0nAxvkZXH77E)-$oqP%JPux*((_y%w_Npk!SRdWkHz`dOh^8F zZHKmXr4Qv%-sr@eOsj#4BCaVdZVZi&7w>nQnBg8Lc0{9<-^Bq*N1D&2x_z7i)Ng6~ zdc95cUz2tpEe`YB*&63_G!pAPAARt4p_xnCQ(OC z(Y2xa6zN)TKA=nwc0{MBZPTE^Hokl-3_~xcqvbbJ%S~BnG z3lCPEre{G!PFm}5=0kc5)u#1j8TtsDv9#48R>Y{p`kbj+cqJq>YeQ6R8TxMSF;1Q$ zY`C!bhA%RSiS7#Fl4f0SaN9&xEjQFr=&(P zr&z~;!iNuFgZ&yPPELS-rYGKu@ojVan6>Sd{mS3GPqp}?*h_L>i%$v@bqFNfY=;ITvd zs`aNvv89G&z^K`!&K^AeMmgyn_+K7{p5^%yzXmCOgIPlI@TC0MSkJUk+`F5prk0{s zwrDKi!2N!EY`M&VsoIr9dI-p zP`);KU^DYrV;k_xEkfQ2CP-M%AfkDlXg>UwrPvK`r9h`#QQSdBb8MD2Jxy0l?T$$= zep}uo|i+r!=PV^(C$L+3M6BdCsTN@<@p07-K1bi+IQ(#WK-4dSRu_ zfjElgewq5yTGnbQw>( z%xnqPBCc9(VtJvwSr=Yoc&7nWF^1AzAiLh5pFcMzNz$5;!rwU{$4wmvYz!>NCvLX{ zftXPihD+S4%fLC@L36(Q`WPBytQcF`84ayIb-!_H|L!oOR)HjF->b&NwMx}wz`bJC zXEee4lPoUbQD;k(+*zlAMa6S%5)7*Ut7S^zW2gmBttm=rVfa{$Yk1g68%F{18k@N# zkz?Ss3em)EWi?xL^Tj&X(> zbz4upockWm8R+uZPjMV)Q5$sDznuwdWKfso{N7QzGIJE8C~paS%=*ZZboeJp=hl*;HQPd{m$;1n|*#z4LLrPxG<;6 zvvI*&{_KT;AIp(ngqP&aTA( zdnzsK(_4HJ_jr{W!LA=T4*#Ui($%0T)pbviMqvH{okc@ELyMOWTjFUDRbECn4^z)$ zZ_S)yqmdO{lK_xGCZ^VKsPt$GHvjfKIKfA|HbJJY@w<}xIU`0hmyf>JF|vLvx-E_< zP(1DA$C!#YDZBbEHX*@D8@kV>UliDhX>Yo1t`!UMMK|+CduY*TR=;Jpc7QJFtgD%31t@8IdQ;O}!0T$yY~=_7NDpf%06JY$F5Z5WK--B1I;jpPO{ zIW@6OK=STM(j6k9Weq^~$q)KI{>2}R6mzO`jRmkqhK;F>%~Ki?>T@*@;-heZ*c=LDve0xpVd0|dOwtXX zAFh>$jNLlV@VSe86+EDegI#8rh+4xetEkZvUl>XDQPsVpC>>x#OZ-n{5D)tF){#RF z+iLd>*)df)eP>Q@;US{-ac5MztP@>n*v-o26@wW&y^u0pivIg0r3oj0*1~BD4Nffc z_8UWO`|Fb!AOEfJnA(Tyk&Lo-PE=v)qJ@s7lFWu{$RYeDW7=KntO4K+83i-Z$&AVG z);v^lY0RYqC26^cq$g(tc1)(~y|B0vx-9K`JXJ%>5*l=9S52$?NJio+f^|siU|gLo zr!43}sw(}W8Nnmjr;m>dziHUGV;s-Wg#!?`sKo#BgWgQR zUR-VbPwt7k_oKTZ6z$)4=StP2-+QHZS#-!DxQO^56 zzQ!~o2x<+@^UI6CpSPLpwhZZLj%|;F_}{1Tzwa6T+ylxFm~WKA{_n%s0cJcQnw;k4 z<>l^$U-hhoH0h(;=?VFZs*Ykh6!P%}7#* zkQ>xnRYmigbb7sanfvZEw2yB*?Q&GU)%Wn3W?uQmczzeEDsU|M;!ts1|ku)$_$ zR981nEIV0T*&#j*QS|}atSfCfol*s@6&-{-Nt;VAL;Mc)iZ48u@Lg(u49cX0vK)X{ zbSb&tukN5(hWwDrg#m;MCLM|2BcJk*A^tCfS6te!7ee24Q|}6TvEG9_n!tsJM@`zp z#1yZNOxzVj7I=jE{C9md`hs`KnXMW!0#jOK-waPtcznCi|N>-&6((tp2zV(B6 z)yk)X)U92*H$2U-e$ie&_!2PIVl;1M%iF$})vZOhK6xlL3z# zR_*Rf#_THo8JQY(elQrv)2dsf#-Phzq5p+dOm)%0IpWtl9F42_mX0dD`zY*~OcIhj zm_afl-w-(-DIE~aTlx46!-fizl7$VDT*2jx|LimT-uHKvA^d$CUaQ;bhB0INt47B| z{+92?Q$C@%wEG01pwo^CmC$2ylfE>#uZ@X81$NAVl53yJuT?Q7+=cngNjkYyn`Usp z%aSsU;Kb3R)odP`{NY`gs99AK$7Fs8f}#dPdCwpsbvUUU>jUn1O&C^_3#UBQM+p_V zpLNpwVGD<{{`tSGj&yV@njdi1PsV5arxY?)of*rCaAC<1UQ(4w947JTxZIg)Oz2U? z^Eh8b2Wf<^aWms)$4Z^+Kt#s*gafM7iFfV|71t$bqt2KwMqBT%m20Veni* zdkiT6?uL5&DcZRJiB;e(+01|T1~IIEO^>f)vpa=~2Wo&7yc4FjsPQe$HsdO-QL+Rv zO-K_}d={n1ju@YVrWBS9UtGGDO^hlGj`L?Fpw?hn^)u;MhS3gWhtdp=jf{qL=X)8v z0?c2h1wH$67Z<^NVu-c1EYkocWid?y{}ldLDs9@6J70l{X7WxhBMK;nXDrb7LWzC+ z)hJMT>EnsFrV^C6qB)CGWm|NhyZ;haOuxWr$7HmeDKg3puZ6E-L9msPnJ15g>p2lY z9B?K&nG?CO!tF?nMJfUI4DzuZd-0kcqpk&310{3GW^m>c_`sQ;J=u8zQ|g*vWkL{5 zwQ0kVns&Mgh&nn>1&?c4D5oIXoWjJJ17|gM#iE(a={q(f8%W|18QVs{vx_g*c|u3U z2N8K(j&C1ryBI%qgUBy8E>t*kt*#)4fJRpQQJ5fHK=UO4_7qck^Uh9dDBxSv=#OWx zk89HP_CD`x`lf_vN|Thrf^ix*t1$){GTQX4qhl3mScNM=L}LqP#6Lug=l}^L@j?e1 zQ4iyd{{$y)$LR_lDm3`VMA;iAnv-H5?2N>*(MXozz^Z1%q+AGutV4BYr}lU`QP+IF?!+)KR>?Iy)n=>m z=9_|fDFvNCnSFhYP|lZ`Yp$Y9K45jlN7dJ-SSm6kblr&zqL+3KO_9CY+5@G7DWKT0 z5!)cPwQ}mq%-7qPP5q={rBwk3ocUu4@1%lQb4FB=|Cgo}`vshvsj_g%bX!djM8xG^ z+XEX}7+=`xTvan2i+KuP&eXGV+KQ#*LABcwAC04(5ykZ}>xp|-tXZT6#xtK`dU5Lq zcVm(1^azrNA1uje$oFF3y6$vg#FiMdDza8!sQJJ=w-$EyEXtG;Jzu^Ky^3X?T)FNL zJ(`P`w%cyMT5K=iRW{+00WWpk@Dda*N(@XK37uCUo5F>;1lCz+1D(Id%d_LM)$_Rw zX^ana?u2ba9v4wQ7s4k^IR8IlN5V+)-=Bha>^9-F`*u*rB#(ygl-eA{XhgbV_6D4! z<@xtYR|uH^OMQ86+KJl|FU#QaF|K@x45@^(JE9Vs{^pBf$};g**JTn{K|&evN^P07 zCHxFugGLxGSKUb#durP{>aa=@0UKQ3l3Nuu?=OOzDj9sE65k#-T!SOHQEzAsEDl{c z9}x8`CTh^Ib0vILjFcvevCm{r`N$zTNs*(Zdrq6goIO}-Eu#L=v!o7gfYyV$zOlb< z9TWn{GD9%Q$_ghcCp(_n*N%hj<{FdKCFD^Om#z)p0+k{g+|Yomm-WG{U$Os&3DT|| z_=$*Hx9Y8n;rskS|1+`ScPjG{wZ@d{T3ythTVs)8qKKS3lpqlQJ`L^$7D~1%f0CtA zB%D%udbCwE#d<8TiH(;zKf>m_@u1ujLG&cn!1=~(e+xmjE5>rXTqOMZ#fB=U1Bh%4 z^57g1o0~7&cdRp{8*)1h@W$f{T59-_Sif4uHFj1FTmfd!e^^TtS1KXJ!o^cvQ*XS= z?hPmO)68nePsQzRdVw7a*1SKLH}r;q^^f&;>KJNt&smd!d-)llhT`Iu$(}*{gnSmv z6ep6|B-*&^58YRk=in{hgNsoL8~Vbm(*8@lpv1@8XzFbFWmBF%sm=ET)c{r2Id9~m zuH4_ACf|mw-mP!YCC{J!f#^u3MwUoW1$Hf?*&4er7z5bja+|eom#II8m8BdaDOc`~ z8M?5tf6&k|#zZ@}9ZQfH7a^j{{t|pZoU7;v_wMN8Aa2$ut2%Hi_PUBg1 zTG6>d!&BvxH>2G&0x}15jnqa03lUr)->ADkLG}F^nc-%W0f7(^DHSbHK<0^+6#yJtiuk91VM!Cz1)a_Az zL5He%DcB(E(_NfM`_#r`*;Bs<0tj;0xAgWR*^FTC45nu~^N5J$*81QKS|Vh&YwgaKnq4sPES$yAK95u7=_Y4}fbSp|Fy0YpVsa}`5jw*p z*&3u-H>)HC%l^liz9tY=KkYV67cK-5=<3#UX4sg@j2W5J@-dFRT1Lz+#aI{M;={en zgYmS~IpOX|knnUoG(mrEds`AaC?@9t;FaGTLJC+;Yl zxt7mHEA$r4r>osfQpD^e;ofqRW#z=Wqm&Yv(-17}gGZxe{1SEPldB`JB>SUhIKXDOOe0X_ zC?1!3YdGE?>fSO6{Jzq<=`v@`YRPN4HNgCnGa2i=#iG`kEuA_!UR;Gjz} zfy5Ly6I4s@&q#N@biYeS_n>TV&d8-{N0p}e2+J}V8%IZZn!Z?7bp3$C-sVwJWVj7g zE*IU9ZLY^?N8PeB<8u8gQ9z>pLbk*lp)m5ZCbM^tQ-l$VT&FZHvD4XG7%)2uVpX`l z3$15nylBRapb}gE>u}UOG$S%Rc?4_J{6z^G%j{C}WpXw6&y?Q^IZYRKyHFkk7KvX?A{GV0}4tq(sdmt-(4?#*pH3TPeKl&to? zh#I}T-){MJ2jV@)23ZHikJ$wAe~T&Zah#zHDp5LOB8SeO>ih?Z; zOiiqs6|A|qQI(hg_RA#74r9+TV0&Ck{yh{}n2Dkej4_VhPJp;4e2y9Bg_Upf?je5A z?-)havL&Xh7*){_xLwr_GwW1SO=Niz*IhBjJgK)PoMHXONHqgnN@FY5q0;6KLHw?g zfO1L0RftLL{+Nw-ArfOuFN_1+@go_o&7L@TYH7b^hm@(w_mnLNgS}LExv*9C^rrQw z_0adr3dVIt98_>7^}JYxQp$ALqoL*4?$hV_D&lr#-d^lhUVA8L^=IN=oAo`BzLz;nNtm-iOPisFu;U))k}Yo%7~l|Qp+G}VJ;Rn zXo(wqDRz2H$K?XRkjeMC(6#o%6hC`?kn1rzaTmir3}BoV_SqP(`r+=h=SN3M5rzAi zXQ*n`c?3|78y>mHL6UPe2$CN)tDbkGe|#PSMh|CCfbNEhRKG8E{z@H>9TSRHzTZRe zqU{|i70n$L>$Ys;oMfZHUOc|P=eK&zYIW?6e{x`MkHw2j)EyR1{~fp^m>KI>q~1{? z8Dhj$QZ_>%NCdlZxxJNCbt_)~YXZ+n6-FU)rIjY2nDaCRp0b6WlcVN{(u~wzDRO}7 zGxBNUKw9T*3r)Vs z)_;Xqd7%ms=%$7#b#G{N9iBy`p3=Wn16VnyIr`#qSV6pA$k_V=KKb!g*htKC*d zrtO)Cm=CMfta~vMk3X_~`8AnvRfhsr3X3vuqZ?>)zgxEMt++K7^@FJykWGRi(&%?_ z|IEl#HdGq(0}L?6MVd^K>Fxs_cICRRz(gO+)26;O_oV}QJFsgV-Eo$~Ox)ce(cn(t zWc?JlwY5`FlCaC46_fyzzUo>DD-)QcQ?VqpVa1`l-ns@NoOg!gkwc35Ei$|C(8{X9HBuy=~6E>Z6_1hrfUSU|{l!s6B z#CWw#d!W772}9UgliX#}?X5NE>vWTP%+_jlYq)!7WgLNW)#xb6iNYUR@W$^p7u`K| z4$_)&3_>Q~#(+j{_TpE5l~Hmu9*K2=XYUt7T*G@iuxM>otKjHVv*+TtH_KBg11z6g zofe>h*8rq3H%uaE(o$=%+bu(^5K<;Otd=#2<^984I48=yTMQBt1F%jHr6CvHh#j1$ z2``=##2Fow;SB0sN20KMHn<}#C-zZdwsySzt@b)zJ4S)kDZxJhMCgQERLtDsKTwGn zr|Y8*YMr$JlYy3Qqn!2OlW=xKN4sp>o2@~#0f|5`9xyejU2dz(vg@n5M#ai|V-=eV zLIiUx)?2Px+a_Brm_o&_T{rHZy=4K@?8hf!QkNFqmpn^3iXqBdGD0oNEcv#5gL3b0 z+&t#UN9HUhM-~k~v_-|6LX3lOY=L^G?e5Vu%$RXLzVoQkay>S68LF6f4V5eFb3hr! z*VQkzshdJdKQ<{^S=p{VJ05(4-Z3dBF8pr0sJDH3qk&!yUF6WeST@BUoBM^|So$zW zIo=HVZTh@d7a#v%7xMJN+)Y-7^-R=1JI#*%K!w$<>OzthPN2aoRdyqtbxpR^(hEV@|YKI#PVal=XQQMMr+D|X_*w|Od?s*7MDfvKGbJvH1Co$9C z<%9&gfGcBX{E~blu#9h)A9~@r{ke?Hf3NzfP-r1X@HCj^$RoNz;j3EK?HQ))uU4K1 zk&o*%$tx%E^nbAoGUbJ7@YwmAic0-2Ffi5rMGBTIn+(B1e3^6rKaD58f=U{0> zm|dYVg`{xxQ4#!b`ce{1Y4xaFR2$3LNZD1qZ}J-w&w-|>Us$gOTl$2oSgM3-+lxCh zuT728O?L3jCFgVmoFa6VT#=GRAOA{<>+Y7}Rrcp{so89ICf8T-A&t5wMDC->NoH*aWu%alk6xdA^z^pk! zRt}iM4E=hHV6ii3{shI61K~vd#QC0k8OO8?k(OB|X5+{z0p=NQ+MdNQKq}%MvY1J8^`F!j6Q5AEG{x+Gu^%1$3;r z74#VbXXUPVB>}IBVb99kRBx|G47PasY8lmR8W2q8_99qu14m{9eKA-&eOk%7ouUEVGmd-7n{wbDcTU&I z2NAwP%S>?=Y23l1WpA@@lTzJy&rHdL+B?^*%%h7W@kBJrkg%*EwP*?+7EwBDF}-9< zVyB-&sa_11#@TDb^LaFBcoOdG$wmTc9Nq8dmGb;=MJZtyxJD&#lFxGP4x`KC;iQ4u zU?z{c(E^)-sHPJ4dr{M5%*<7rQ!&B=?mmv2?R8l0t-QI39uYT2%rWK%;)}rUCp2*q z5#iC7TzMsAEZHyefoT?I*LIS9p-3Fa$nFe{8SER*q!5B$!u?t?MIn_b!@C9r>M5w= z;Fqt8DGEQneY?@m;BrsOm_o<~7$>FN&oUW_NCPA~nPXhs$WC@_+q$h#65eEH6phLN z+hhEeS5)UtL^}LwiXFDTxNY{(w4tNwidsa<(0xRO{N0k;9bFOPM62u7i)YYpCf#ylm7kb zjw7n{uR6@BogM^QKxKUJ{jPB#%?1+nR6tuMpf)h-4sntLGj5n`A@6 zYr-g501OaE2)qvI>XQNc@*SVh`@cnt97V8_Zp8DLevvH_Ftj6ym+x~g-xDMM|E{)w z1c+Y4zs=N!$VJcp%a;Hm1`LxM{?pz2ANhil7LaV`qzzj9zwE+7_dEN4tcw3w9O0sW ztJa8LcYj~}Q?(WxiG#?hq}}X3cv|(hrrn~&*G)2>(uGi6n4w;2-{-v_}zsYlzAlL>>~WRKjI)Z0s2O@ zGAeRBrXc&Js@BMGfe>_VOI5$5Rur%JEIx!Rm>$F@!VT!AUJ=x`Lhy%`U zv_{@f-b^~$WbI?oKdMrXAS>Q$X_FM!!o;3x=Xu>P${yI}HMHJnN9Vk^PsU!`meevV z-zrmDR>E{Jyvp~(Y{-~!zm)2esz?-2-zL@m=ScqdNcJC|TjgI}Pj~V5U(4d#e-^pi zuOO}6tvOx*IUdj=Cz;RAg<#o`l{|-TEyRz|7YJCsze>h37ORHgzBNGGaG0$;5$s*Z zj2F=1;Ve5jz8XSp%oG!j|D{s!Zrx&CjqK2Z_pO89j*T2w093>Tk zk#AYr9Q1jT1{mU4n6k>-@RNz%NOs+`07)+NHzRJA+#1p$N!!@_vaQN zypP)dP7f{Z-Y(sFxWOduzldhFIqBm-e~O3`8H;u0grjD}JFg0RDyp;s1s3$#(GzU=(9W>Ud*Lw^@=(%; zrc4ZkeXt}v76lQMZ0i!8*;Z2Oz-+%-vZ#Q~`A^4|^*Ve>72;&r`wpFXau|9@gV+mN zGq5)NUjoq)&{d!%6KY)UlBA3fwy>`JpvE-T4A$8*Jn2QALHNYktvYt$5NgDp-51P{ z+F-KHT4FaP!kodn9R+0@HnwJ)A@Uv;<H*(Nn ze)z$oG`Jl|kFK;i5p=uw^QQ;4@F0C6?8MR>UC}{F%>uYBGE`iI6o8aqqzVyp>TD`{ z8{rzvmsR^HA%fStD6ZhQ4(|~SpGPO*VHblR`~yIaVZBT+n88i!foT+4$&F}JsjjIT z!{CI}kNUo)$x(2P75=#Cr3D)C91BwLium*R5;o!C#ve9%ck~ z_*ASm0b3pj_1|v8{XV(!_?x~yX$}@}Pi0Hjs?^sCzn-qB8=lOjpjml7o~kJ;>@61iO7*tH zym{eoSWMwW%GLG7lufZi&Wqs>m!)H5YJ0%#RDUxL(le6w@2F2+@YN}p0;bel)yNFj zSKriHrBvySBgfkOW|Vv>tzi}bMO8#V1S7190jf7lhpq2QQXyxq`5EWa+@v4`ZL24&CRRx-_~-G$-x(k`Pt-Y@ngHuU zUF8qP$5Wraf`iYGc}4cu4P0J}8|rnpcI?&u_&;Am@J8M1eg3{RLYZT=_$EkOrwSER zWPn-?yk6u)je$I?!Swuy^jkcH8bA5#Ln1;`Q636rzJ{F!|xtUu}(IrRe z5FYEv1^2h8gI}oNRFy;wp<*AXnL&hA9_dq$;M>uC$BPI+b>y zOqNZAm#CN7ao2`nM7aISF&k;0a?fArQp3iEJ=C;%OpoU2Mz$s{mZrGdq#hNWFpvKHY5@lHVwaqI^t5oaf19~ZCP}%AHrYwY zb)E~sei!povp#$ky@-ch>v>uzIm>p;w-wv4E~%#F@>c683ZmO8ZQi|{@3T5<=v2pN zm=lw9Z8cMeuY-TR&dWQ9v|dRFjnEUja^lngr>ka+(4R@?fOMN?^~%E!EjEQ^eML`~>IASE&yB z4v}tW-59cJTG`Ek(C!z;jqFu7e1?5*;P$@k2dZeK6Ej}BkZ9!fzqA(K*y?fhxavmuzj-`R)ZeYX}eQG;)UN6qA>rg(AJ;-;XFZ-1)&2+AH zu;m!4Rz*=-n)Y7d`wL(9tGYoagFu(q#OlFqIs^G*V1}HVUoA##g(nH(Kd7QdNgJ@`Nn$el;~D*h_DAkK2KhyJSlFE5qX{E`+vd0?kkXD87 zYo1&lJ?sCm_f}z5Ze8EFA}WG}2qGQQsiZW5NP~2DNq3ish%~|iq`PC$4NH*j?hsgX zFLIIJ&3+!YYrjYT>-vA!_nzz%52lMb#~gFyZ;o-#URS1=efkRTE95RN*TyZCe55Ip z2P_J5wv|?9dBL*0L$6xn%Q^`IHD(%)VLDXRl(>w_!Sb3<)r0^c$no)Lh+pNahsP=}_KYL_#N+!O`HyV}%) zw&K#tnL7n7Q02U!?PCR&pGV;M5hKFf1unJFMC}B>oNodhq3+9MCK?r#oG&tBH;Ugd zkiE~^9lqk!Q!UVvszjIL4|zDNT-jcOIj5oBx1GPs<97ow|6-Ydt$McW;ht5Tkv(ye z!8zYpZ$|p?`JR|U#xx$dO1l=lV{EwLBo8Z~i>*mVxpo;(1l|IfI*VA>qEYm|%$D-NHoL%7rrTo>$j_s z)lL(?j(t8Zzx#0t>RBZ;N_~+(nu%Vzcf0}(V{~&(qV?v4RF@>b*3j$Tgn0si7 zgGjG7mvK(}J@mwF;5TAZPrd7Rt2^kMNLcsJQ>FI%)|L3Gnc>W4uuTx_>j|$YuL<%X zic<=9He#J9UNY0lNN)Z1IeuuVS*^_*>r6Wta;1FEfttIoiwkCzcj(%+6E9*=mMhb> zNlzOdv%6T!?4oNB=FH8#khNNl-kmzJmYyK7f2~Bbw#YGo(fLhe8F;;-gPSUN0pymT z*=Oo2C|n-;s7xdo6-jBc%QmZ4QF0YTrzlPG-A-D!YP~H)mp*Q*krd;b^%{oV8SW;? z#1vzy+$7rjBG$0|ajk{iR*Q77MxB6nCV+^<9A9Cpvbs@U0l7B0du?5brVd!eDBFN?RV^!ZC)B=mZNLyN@5 z^^8LcD89uR)HNEdx-VoEy0m#F*yR)IXDgCWOw1~mxM^pdy>`m#C6|$`wpF#NLtjTn z-=COVSR#t+=H8Q*;k>tpUTA?CkPb4N>bu_{1&ed>j`!FhE--e8=gbgGR6IE&5eOD> z9daa$U@87am>06F7(P>mbDAr;9C{@5@YO=yy{_DZ4y$RQO?X1ysB-GfgF-94V&mkW zuD`UE`!MfoPAuj!Hz4E`0`zK4}c-c)m6WxOt2)xG+-Kzi13wKB9;22HRn9=nq(c!PlMjI8kmc z(VG-&+}vSO`u(Rl&|J*iB7&TlrP;%CY;K!_xT8(a$O=4xEbZD?^HbXXwNfV})DY2R z^DId4O7dRKOm;8llE{EU6!(VKh(v$yXex!`)WI(ce`QFwePuVf-D*yby-n1O7fnY7 z&>Wm%6M5ZDJD11nzGipY6UAwLZZVw=m5|?=6QSGHA^%V##HR)!EOwGeCbfS!D}%FZuZ`CSX08*GS!d#rIA1k3%Iq<0AsX{r`mfX_U$IT39mrpsH zhLhn}ebdS2{sp*5C7T%1FqziGSABL&wP&%BEcgbq!4&tj>e+*Ac!2%N-4 zP{S22pEE+|vX6Nt-ybj5AtV3OG*9J$Rzf_TD0MP@^46VJkW=PKSf&w!!Vz#q_Ad?%7WwugdeCdDHLXtfVz zs_%lOz02qMk2PC+29aH@c`3o)o4H-+XXYo}*Od&HRqw-Z!qaD8rn=PNF-Tje3^YX> z!;(g1;qPiDpmB^|hLD{mx**u@b{lDQ62rxZ1CR^S2<-7mLf1+z6PrLbn{71r{LSqdeR+k#yzn;2|RF@xw02@=L}u?)=0lQBeQI zcf1riq3bhu_1erqeax5_!-E=MvS?f}7~Hr#*DSS#h8Dgi=XxN zYG945+79EsXA2K?qdFEj7L=!>zY5LtTAj*}7Bb?W7MyatJoY%OTztP|-4i(zY7dpd z8NDy-1j=+Qa8^FFN`>oBrwPn5cEp9pTnQuZs@#XdltvmwoXcj-%6yrkQpNj)OxJP) zNby63kj%Nyp}a)=qdyhWywxJtw0x$F!WYuQlTYP;Y7a->>}_^NFaHD?Z_0x$2MpkY z2Ew*?8zhEISgEIs9MIMB2!~DYsc`WbALnzVbU!FD9kV>uZ-~=YE~<<}U+_>JBcy#e zLfQo?*vWKPNQJ;!YuU556B+mNcU+2-z*9dxQd=k+%?zd4ECRH+k4T*NrgUtuvl+fq zJ(yIk{5c$EY@Zv$6*!T7(Rk36(B`^wZ0sM<(><#@rQhZuIZ?kx$7^rWGo(zf&3WaN z(P)R=&KPCm9|1ibgOU_eWzT#mjaiUR^^4Z6VrY8&J`dh3BgsaXjkREPkC+RoF$Z~) z>>jcFJtuW93)C8PWDSX7uZ5+AuFiA@)O}C?a{+>uBmWaM|LfTk!K8FT!&x@3@#EG^R@WAGxjN^Yg5^`Z4Yn`s z<(PGyTMfb#v{e(dv^jR8xy-^%R{B{^6`&S!=-o_bn^E`9p%c${<|X$MK8`6Icl?^Q z$i1-$wQPEoob@go2|dazOQR8%`%(I7!^4)$1P#Keo|4%;Sz&SzD`^cCV9n5Kq(1 z%7^Y+D=hS{tr__>%jOHfZ0s+c^t;?2q0WwkFvWi~EqHmNtexG|S}xPgb2$6c4sO}S zCs+Mo>!*HSaemdSADnv5d_#Lu&ZlC#r4uQPm=!L$^`QrP17`=H3T@i&-XC3MC&rL? zW!rHJ#lW0r%Ak{oy9&>`-h*}NaPquh_y_-QE}A&yrEq!=F@cIOmQN z>k)o%zIQ^@9a8NIwtl;4`8EZ-PG;wSnv6SqnI#pALCiy`OF*`;N6s*B`xE`%gU8Qb z+v;_Mse0hQ)ffX>5hDnmO0NPBAvbwlJcufX^My{!S#@anc!@)FzC}a~dX}~;z}ck) zGEhDJHWmvp?%7LiHbTJDY7=ZPNZ-Fsva|CIJ}^Qzl@%C>kPE0_$-*#o@*=Lx*%TXV z53TeKwkPSYbN&H}JQpM0Z2Z+^k*jSr)c&Dwbb5Cp^2bj2W#pajtnbV4UEgi)#~0r1 z$sWZMpjWN=l%bknEomA3ao+w)WTca~)&ix*@B4`|&Hh3f(sv7kyUtjRjw$G`^`vHH z(9eBFPS}a5cLX+0r8Xx%@J5-=d}t!Eiaj-}fpQg<#@S2PElBSU;%y|<`!M)1(^%r) zFlEFMa3GZfn1QNQc8E~$NkdHZ!4gK`CU}2WuaH#lvZ55j`R7YiivxTw3;W7L;5FH~ zdY1_Sb4+V?MlZRcQ;e0-cne5*VP?~y6Jd#RLcNBpZ4#=@V`18b4!-Zxj&a$02dnq* zPm*P&zps+$B9tiBtDCDj?CdJ>92L{n(b1y|uMo-(3POd4 ziow5Zp;Xo|;Sid?ePW)_hq2bLtUgg(=dp2ASnLn!o=TIhfw5$XGG`8+XDr-*Vm@&R zE8vfo8hRtQ$>FgP>*HNnaBY?b!Ki#iUd4a(z`y3*$CDl*_>y>Uoyc|VuHmCHY(;|t zWTXnd&TRh#re7Ea)2dcKEy_wcM_@v{UnMr~YGT_@NY5B@N|)Wu<#LngYo5@belbFj zKsS>S6c^{>q zM|Y7m^wt_SWX6`^olH&$!83>zki1e*#nI>nPtA*=T;!F+NwC0W?FBcSAxD?Om5EB-#Q$8l-XsG|Toj>EI zw>CdHx=7c2O_9POQ~bK5oQIR+ir&IWWqVdLfroFbVke8Y`lj);)we%rn%bFy+*OjQ%+rJ`rq6iyuRNO!T_Cm1plHAmsAg=FdFv3le6~0#(G3=^v!!G z{p#ZEB(>DCf54OoCUeo&vS2z#G`wehQB1ncur`Fb@V(+P)hI>RaoC}Fhwo}JQ=(@~ z_zP|_5i^8ilZ`vvB$W7Kt0jsqPiJkRe`HI=Ym$YggW>go`bNI)>uS?buj>ha4!S&5 zMUbQG_N2N$jYY%EF7u!|-^sVULo^F&9cA3zVXYfpxjpU5YsY@jh0F{5h2FbgesV<} zY&sqkie;}R7sjn~JZ=~MEe>~lKVYHI6i0YVO{NAsgNZy#@j8j~yojRgX2Y#s@UFiu z#~pGjrJ#l%EHpV5%M+h{-vViao652{9rUD-aTWAuufV&xZh4NqefR=v*>-f`adB8( z0-H)({thf@>3oNQQRy6BnASR09DjW2cPg&6m@rkQYKMu7ixIr#61~!P+(XCM&;j0g z7gzcd^u~^wpOf`7@G!~yR~F-u9`<{uTIi^1k{=gQK!Tia@SoA`3OPlF{nm$yOrh5U zs^S-vmV-V;6dW8JG&H99oLwOe?9VEVxHR&Mzb9NstY^wz5oudkMEiz^%hUzcbr?}o zhq;*_EagBgOxaAWoy|qL8BUs5-(edOlv{e3)wJ?VYa-Swki7jVjc;O-Y+&aE7W^!9Ujf&ybQHj+PmNKWxPHaC#7^m%7=l ze(%uLjfNBmH=0T%CE^BmbY?mqN4IGVur$KKnbv^W^q#Hwr=i+9h##$=rB&IkvYI7>aL*i=7Th<{Cdpt?f_x0<5J z-I@&gk>!VQnFkPwaIO{$8D5P7_lXo|hDVr~jd1g6J|>$ju*JmX>aYFu^c$XkJix~azfOaJi8t(8*wnxxm*Xq9dw^( z(~hV>S5}JDUK!WyRX$#zPs|A?5s%kYI|4pohG*3ocg&*Rb7CH9H(tC@X)+$r7=*Di z^RjxO(5LI=^mmYedD|tOT~93dbaeC5b)s0F1=@JJHBAM{b4|;&oo=R*a>h4>H*su& zm)r$;eA+-yx?xoC2C6MxQ0?B%h37&pWDP72T7AJ|x>2XeN+NI1NpyaB*m62d z6WVB)MIub3#^t;jY36?+AXow3ZzM9U)DY1c-iM1qQZ9!5f>=L@&R;g*iYk8bKVVFJZGf8Pp?Fl>=H2eelCvsQD23(&T26iW{Zqu>#phw2!fvE zZALZxe1|S2ArY;p!7b%TGFLB^3{s1T8gBI16j4-ptms{CUjGE|vy!?C9lKWq&K5`8 zvFx`|Z~J%iHt!+xuymJl(({fi>6`G4D{N4PSU4x;QG6jSDJe(4M*waU*Q+d#y~Wy* z2Xx(mBgLHU5=C@=4LXd=Z)|p- zp9!{7eHDdxv!=Yv)eS-&fj&7~-5QB)KW@4a)FFr-eD87e!AY0}z6kdd(`H47Y4v^z z1VVkeE;pwwGGm!i0@ByPu|=fpke8-4LfNu$5MlWkBKD;RbO+72n!4_^98|_fjE2F0B&PPE*qg?WsTNa$~A0!_{JDo+0Y{{DGf=*|t<(W-SFOZEcakFO_p>QF_4e&vQr`8N+8=GD&^BRRUEUL0U^)5FT17)CCQRc(;O`ck7>LTuFKYd2^0E1rUq9Lkkc zKDqt(K{&q(kJ0RV(RPjprC37ub#W#x(qaW%*Qw|-9ItvqBoqaSDqnPL9r0s*_j$Ex zakFUaeRfH?GIYZOCp|5=&vVMG-qhY0kF)jkf?N3s2e#T?f0`00`j-xVTXbA(-__>~ z1#H-sW9O9nNWZo21GjHMBWC`J#prX-rZ(~|R)kLmrK5EIwvp*R(RN}-XtG1U{+?qU!;%+R{2(EQK=D% z?Um1^&}TAhrF!ofos^Boo+EdlPPxh4-JFqHt{e1$jT-ew_Wc3|-CD+dFTj=psZ~u? z25WWE=Oe>8r^TApiZV(DPn)PFWM);L%(fM>%xmU1Bah5eE-ZfC@(Qmw_~5T$?9a!u z$gFrBu8bk;W$2N|IN4;m`DUeAe-lreDzl%s!ea9f=2{(cQfF+R2E3L8`8rg9^2pG= zYfg)FFM2jBA@;J}=(yTQT6y93;8B%3LW`X8XWU_ z^;?MM=;%XIVrrkcl$ajwqE`%9#crl;12$a|;X`9)s9;J+sHvjN%0hg3UW@v!Wdm` zzU@Y@&5jH7%7--k6sG&>*mawrjR$WC!!_2e(PO)wD($Z2pyLJii9_*PX6=^9~r|E@NDwTOo*SbvpB2LD8yIfDesN(*wOR4>8N@`T)Ao-9XF6@Q)cuHnY)426MM+hahU(y( zq||#wF%P`g-j}W#@#k?il+?wlZ-tLHdqRSH>13hRsiq)&H{nvnQ(7q^-je&-yw+eOj-SVQ5+Hja>r+_|fJ zcL`M&Q-}QbAKucmj#TB*w3MA-jjG)h&w&lip`VSql&$rMx%qDz^D7_u`XJnA)^#~l zRT-J#g&yWzH)~#d;uUvMs3jf#Z*u@FRF8tCI%%A_LGxR(fpxw;B`f}#$VBxwu_$DE zcmNsoA?#Ogf1eAG!r22iQ{T$7j1cy)O(CSEANwsk<#(b8gvf7Yfo<7WfkJ+V%VZxv zY9oJT*k&C*disYH-_A!^s&D&aA`w!tlHfab{@U;2y(#`|AB+3$Qc&lV0xJT=&QI1o zIfxq_H-^(h0wg6R`;++@R%Q{=rq2wj%iVPdR8_F~?k@-R#IpDt4T%9D2%Ed7WCA@s zy*Df>#c_SGS={u&AKLCBr)!e+qM>0cSx%LSV$v5wuFuyhB}|Z|u#LYNeYJk=9`I*# zcaXjaeYa~fP4P0(%8RH^qYR3WFgdaZe)w`z?|)7TY1LZwC z|7z?*g#ErP75WZ5M7|u#rJ}5i&%D=on*0hpApQV?ql3WQk3towpB);7NrsxPPG%ZK zNrtHRaJtB*8Wg|(t&u>M?E3*1MX-2%0<24hqyARw2}*z?A}w-ci5IC*;m>@$b~jh2 zHuDYe^`VSUWv&MXe(ye^s#Cfdv0}%)`6Eepd}*Hn*&h~0ApX&yuy>Adc6>a6gtW9Y zs0W9213z6Yv=~wTKUNE!+4*0hCVzhB_W=ET*9Bq$q77sT z*Qxu6@(2v#P)MH*+5N|4;M>V`Fk-1a`Q7~gF#RL({T8?YEiccIDZ-~p})8PbP0=vfYgLr!bO9hb?Ghz z(axH$Vid2eDbmLJ8~-OJ$ldRF5crPr)>r7Y-Tfh9oX33H2;T6Auy5G27*TlylF0Pr z0MFzR#@qPrkM{kL1jZOR=n&cFfeJV}huIAdgmqMaF||Aib%gmQ6+hOrBrnT?5xtNC zFeWL}jd1Wkk^%KTA#!mK{loW<(ZHCBxxzDq6;5!-ic8jNMl29)Na5|6LR8s*(Z*Y{ zOL71|$KR2pgXnUzfU)R!5TZ@uzYn-g?%g8-1bJWp#tKI>84*_4rUSh2fE|GF{uizV zh*Y~B%c3?$kb?gwtV?ys^-tn+vmeQp2@MTB51OlUx|lz0Qq7VPyj;YLii#3pinv6e zlWcJQzHq(H5a7tWyComdyPHKhUiYv-E>mvcDVo-`SvCtzdypG%H#avAytFICzr@NH z`e5e|VovkFY`nR?5DOOAVjqH4+DX?YxGvYTie@FyB}G>#t$%k2@wW|b*IKpU(&#)0>>8rc%;L>f_(b3Tnj?b12lx1eKyMK6P`|F4I)p?h4 zFy!WHFMCS-AI9SYHVPA9Jap?E^IZ1kF$K=_QU`@6hDi6uZ{9`WdHutaW+Rbz;M#Wy z3!}l~)kg+}KZV*Yw?~6OpqQ9$kyxi}WKv5w>4h}*3u#^0JwaXJ|hw2{rW6g6Xi1v;K`E-Ef5sq<3}+-(+!)6{{7wm244ob8^(?*`;&N2=RNi~*xAo; z?SXLh`Y1;*G%)Z$YK;ohYkPWnj?hc|Lyu^m0uk_a9N8o&{_gbjbZhI|IGW;@%K!6+!26H2kLa11nW?CZp`G7= z+ZU%ipL95P`_E+*%`wtMlW6{CtfjPd7uu4AO|V#4xJzxVSj2 z!u_a4L@|(O1b&{T3yMf7ilB=KPNo@D82-nmx{+JlnuIY4k+i>&<$f)b`Mir@(QQS2 zBfzTbRFeB-Kx-wU$H3nd;h$aic3+Raf=gAw!$UuSYc8g;8s|MYzhJk0qSa6G~^^j{v7d-FhCNViJZ<6VUkKgX{td}W3V1*EnHrN)c zCi1#i17>9b-tUs1pPxx*@3nLZKU`T^ahbIf3&0?z6usHvH`EWNLq#}aq*=hGOPZrgR$&pLJD3dNjTu7+?8On~kzm|W51O+NwMSY{n zGpbP9{pNTtuJNFq@FrU(vU5+hVvEVI9YHFw8iYD07@+Mm@R*#lv$H+#(|RCBh>C}T z`3%VEU~Drp)i!IV44)=w5RKmuIo9+sYU98H@OfQ(2M0p~gWC{)C06%jZ{g-*;ik#E zQTS#B^$KjnX_9^~SO>xCfBcyDmrF#Y8Zhq>nQ@tOihYX%Q&^??9I+SExLdb%c7>|= z;U7V}f$vKo<)%UwHFy+fq~k-^{G|c3z63-mb5&Ler>Ad4OUbkmbotJ08ZXlKmG%h7 z?TCVoPQtrZm;%)s((>K_F$vDatH}pK=r9tqeFs+=V{wG|gDmKNVK_tj^Wc91UwJ~E z3v3n|sDTLfg-p-%)?S4Rcp)HkKL~E^6%pNi1X<<(FQMqH5musU$td%#2|iB z9k5OP8FCAV_UaulMj$4MK;e)6TYE*~b^do11OV%D+}f+x3aAJxI6nhy)8YDv3xZY@ zJ^{wszbPOnX-PR?o07y*nh+#IFBBN#2m>LcD<6|??G@ko5i!EXEC4XM&@K}PVTEk~ zrqf>=%GpC;l-dhmEc8Vdf++mUx_?>suhjjku>ab+KeqE7hj4UhkqG7BC}Md=H=6&s%m%?mR|rvGAczNyGfXG_7Uux~)`0tNNp zB7h`3Vc@&`l%)*hu4q19;|TW_cYL4W{1V80)|yTK$IBw8D*=F+Z<~PIMNcU)ggcfLTCUSRYAt7jIah) z7O;jqPl+SKZ*+I6GC$_nVelfyxk`#jFu&q+xa3RJ<9larm5 z-~Or-n&m;lRwClI&jc_JjDQRW)2jV({QKsAu^vStwXBSlo0~gRxids)9|-_}a`W=I zfr6bMzV}|>Y1on~?Y|K2KOrYmrKik|2uw})di=cTzlY(Ag4QYVEs;QCn?6V4TS3t8 z)$A`64GavnMrDeA^Ib?2H{vNJw@i@M)>j4!Q>m_^7i0rUwpr;;>v_Ln)ajCsiDaTj zLyAj5oP1~O2*{wXGf)LJB>N!91OW{l`~vV3D32RNL?ovJfy3p+r;SIziG>J(jQPV) zKamlH)BQk$bZM@3`^zawB)oniI(joAnBS=D!h=L^n1qBE4D-toOvW0nif;TcNG`EH zxAmxD>%uEEWw4OfZe2(ww*Jd&Esrz*k@{*M4I07HDXge?4)&?e3D{27yZ=h%UlLUE zuxe3!G5fM|216;imaf)kbv<$WTON~bm5Phe)=UCMw$Tb-L`-2Pixh(-)SLa4WxR9c zQe5awZSiJ0tKQKK{7qTc?x31OVJ0-VwPMs)+p|vD!P2J-Z%hOrF__DfRrIpWUv9j3 zUN=K|B+vS*s+L{4>ss?><|sLa>LK%Zbabpwk;(Sx*3x_LL5`&tBKQAuDwA?Qpl`dq zQS^^$tanBP<5i&=r40=YH6LbLoLHVQ4ul=4n-~wZTzYbUYdH0-XsrxKPf(pVT`8_) zdNTOgca%h>_PsjA8-M8z4A2!>t<4B^N2_zBgB^U0tIat`9`rqX!+VP25~igbYH!uu zC^%q$rtE0Z&W|_XR68ynx^pr4)Qj-C%IUa6x57Cgl=Ic^3N3jm03z?VRe8od=oeIx zcc|MmVMpdOUwCPdtx3eC1r2Lh7~8Z zQKcvY3UUJl859ZQhhcP*!YP^OFqmIXbkp3iGD{-!Y)Mu{w!D0?jASI`MBbS{TL7o! zbRJa?raM<%)IFt%_f?#bePM%%^L%qUT52<)lN!=>{k6CkK2ufYPz#Razzw$77SPkE zYnZjS@oDdYdh(C##nxRxOYU#*f#r*H9U42;6PZO`c$UVK?$Vtbx9z1i3&m@ADPz~n zdzG^Qs}6%=2q*RRxaP%$)~6vPT9>=KHwoFVrb4t z+m5`=Dz@A~YBV0M+P+U1Ol1_2xBFwf%iY7Yc)CUsHKCzWT3bQA;lNCr(hJ60-!och zWx%7TFrRw_=pB1Nx>9WNATRJqQ*=T}3((y-L3r1GCeVE5BE`T}IHT_H%Is`?=w}r3 zsJQ)M%B8Mlu7zb`5f9g|k6e?J~G?Ow|WNQzjSQn$M-VQUn+75eQr0vFY<8Rjh8Cx@c>bB zrl+>1vLS)EYpm(`b0;>#zwNj%?k{)IT!T$Ic$mFYk*P!3EJH__r-hA*h~G)B$lUby z#j3=Y){w-I_Dh)rlPgQb|>+15E^s<#%x34Fj-J9WL^32XPhmWsen+jZpu(PbS)%tBZ_jdPO zOof{ZOeXLTP#y%f1d!^eWl+xZDkJ4ruR&J^;%3>5CkzAI1mj;zX$;aBI>Np+a1T3+ zc3FPlQ%vMCv2UUb@0Td*j-4hc*0>&WFiS?`HE$aKKAMo4sh(HuyfMZhPsbFkndW}1 zN)Q%3+UfLpQE+`hO5T0W!ChPagX_uZ zMLUDn({SR;pLk;mLKBc{WhUlYRZkgZ<($^c=UH|674#NAs-yGsNAN)LJCKDFQGGuw6XnZAN;?$xJm5QF1UrQa$DA9=J6IH`>R_{vt*WU((Oa z5ae4V$lkOA#r?0_thfjGdD{mP7kV|}FJ6s%7+=9RzLZ&+zfTLf9(h?Jh?|lBe7W&G`?TxiYZ+lKaA6=wr38A)&4ArV<#c^!{eRUWS zTfsXCO`Q@xrl#$Uqkv%0J2)S&j(l)!o&(IVd?5%dnWC6j^{Ysgcz!3s!(uenD?Tr` z(6!P%D3R^7jphq|G~J9hXAbqv&WosQ74wzRQM#~@?OOi5M!Kmd3-3)Mb6X#K9rv}e zgY7+3#BF3Y)l8;l>v)_G=0t0#*;@&J8pRsv8Ipi6sj0`o zL>nuX2LsFKISOsH@;(Kd<4==967fc5*^@4@`v_mCT4{KFi04fDjWvH*2BN9O#}Tv4 zEOMMU(G%#6bUL~a`>jto3-d|mlf8H&N64y!Lu@-bqI3#jo&0bNt7e(NO?4jwjrg*> z*Gz^Hb?)XWUlf!wnZ~YP6PcSE+~BAKzh;se@E8H58}RGBWZmT25f?IWCzzBL4SiUJ zD@C^gFK>quX3>R~=GipDw-dS1s#p4^V{)=hrM(=yH{X}P4VZmcX}#EDaJ=++Vl=n; z!+^IqGDt0h$-IzO$wCokdey5qgYU+ZHYTSd7Ymx`q4Ty}uI(O4GAs_yyQ!;#z7;v8 zJig90ugxlg@L+^Gy?aj%W{y$jI*N6bH{)CL8i|~Syw85#adsfMubs9#lLUe#`LphO zxqFxU)c4Z*5!>rCYxJ>@& z8T75&>WuMum$Asy4BFv}W?DYM?CA@t?ttq}C5N}XIkMyFkC;}veY8%i>s!7stO#Z# z_2|;Y4#2*Hdf6p9UF}wi8$Dch=+y*#c4up!VsCYK<8*vhy-_MD_!Vx`MC-DPx5BTe zrePkb=Dc6KhWBhDv+qW+dA{+!xr$eiykT~{!p@rrwk(Z-m5R!M6P%B*B~q%xdF}f? z3Kkyj5%HDEIcPMJ1<<;;?KRumz;&2uxDTZivvg;*FkNYwCRmlAN09qHTxKp%?uG(W zud}NrF^KoPXFbM8K@BeRGf=MDqH7iJe*BI~Yz4W5q*qtH|HJ;#u_SJzl-AH~Yy@}W?{tWw@EgTNOgcryu2q|xam-au*c>=+#C zZdpoqD?cQ#_fWCYpw%^rKC^6oc;JvMPg>U*cVVJZ=*>J$Q!DoKFq}i8;h;=qA}+pc zc z##P(zuDWT2wi&Co6BV`u9VwMXX-(`S{P*6avqF5;?HgDvk@8&0{W)uhp z6QFA}h^nWgr&@EL->iEo)r0dZ{M@*@X@tvXs-*eqm)LQY(^lr?;>{(vm~`Rm4zGdj zM$Fa2YD*p<2Dom$ckdoYd$shR#tc;;Y_BQ%CJ~k}Jnmg#-(0wM7;K^SMl>aHr6=bk zJi4K40Jl&n*+&3Et9!vb-AV)>&#iE6J5DP-bxS>Mn2>qH6{nO-<+h^A5G3b8Ar=?S z!ZfKi60rq|R5e++$T1pI>$peSpw4v==MP6)%iYRaDNMZi?y0-ey}}~E@1}EIXbKw} z6;NEzia2KqP&XKe8!(a9QDc|$I@0nf)S?qmh~{VANy3a-gEY!FrEwTLW|x`i>wIga z3tIMt+l=?RQQIWE#nZ5ISX;}^=cw2mb%73CoU^AM4j#a20y&*F60SaH?DleJNllzF zCwpZ|Es$Ooyx8fjwW)J#>|&V8!5Ijiysniya)E5K!gNz?2N=8p1O`uwZ88%lnGX)M z4UUhPy~G3-&EMOfJWcLx*Pv!A_43)(>1Z6y0iJAMyU|%7J#IbaXEE-tcqPE?>ifG; z0h#=-6#I>l#RlVWKJctYrDIXr#6+rhbZ+6ZrwdS3N@#TnA>m2@bvz?VW6am0q4%m2 zw4|LK9d)u+$3dgA65++)Y2lPL2i??ylP|Xy-MmxamGM&gwd3kp7QSRgn62cXvaf(324zjcJ|x6z=g*IAc{KtO9r$rI-eM zXr7YYIU&*ojT&9mX^nr!Z=Ms+K1L}Y=~>>!!OOz@5O3Ad0e!Z&Q31%MJYB zDLGK_!vthALkh#Azq1>F0l5fnVBP0bhWmJIJj}fkgu^NFj z{$=3*5(DFZK)a$|Ful=wVy54XP}7jgai205XwG-b)C9!mfBfGVvIcc-#O@Q-is|Qy zuMp(<3GRo|RAF~uwxP64TSNgC`@x$V0T<``R6{Y`0$LbGBIb2&uw4|EnvnGREnGsH z*!yVoP&g|0o5+#O&dOJxM8%4SKp{eGNCyW8z?UBSjP(AlD#n^alJP^`*xMM%Sz)KM zC*A`Z7vY`JB+gxb*)0D?9)@Z4)U>q4!8DPsuCBnqz$9LmDz^LoDctzLf)pJc4eKA% z(U9VuN&2Xjms?yMQ)BS!hc8ev`NUk%PV}=LT@}ED3ZR* z29-n!0S$x8%gY|eo4AC8gtgvJ|7u*+6chRs^fLhS6iEFSZ5?-epUv}R2>k{X2c40b zwQAlgDmttWrup7`_>Q>(K!vN=mMU5a?$#=hzYzsWydKdP-p%?uxmZK8SD?&!2gr(j zJWg%#f7&B~Rjhp^rKK&C>i)(eu|5f_mw}7Z5J;v#?oIwKmKCJ3}D=>cZ?`z>5$U7qSg(`J-ZS}<&i=L1L z*RO$ra?;L^`y_+I#T^KPxK{CEs8^-E$hbd2h)BO>Kh$1_yHLx{9*-;(Kaa&eqBr^* zU{41e5k>wNw$6T~-Tr|AmXm?mwW&{);i423w3^Yey7Ox~fee=gA@LIvjyvg|4$S6t zB4WQK-8TYofx^_mpKuKf4M%k-R~dDalP2FQCf;l|$gD`h!^*yND7>(hAYY{xrGwB3 zAW`X#sjPk(DBmNL*x{6zUDzJ@6g zof!a``)Hgvust2)80dt2DJzO9FUKnct|gkGM@L-`2|#5_p>n3-(i7R+Ie0j#oZ`hr zXvzJ_d>2VB&(_hX2j+8&3uR5Kn7`+llE9BX%^4>z1T8ckBw1Sds+xF3kDp(Jvsf_P zG^Fic$3eW{U8EDcK4P-xd!E-T3;l_MS(gJ2Hy3Q8W}ZIhXoM#&NhdJEU~k?c-IZO{ z1-OzKW74q=1o*J)XD-+?{+r1kb9)VS=j+FUUiP1~f@)3nb7?x}y!&e9a&GLe$vX^L zAO|xvVoq}}Z(@BI`ESAx*XS|xRxqy*4CA<-DpibsRb=@u8{WSExP|+q)oM!=Z;eS! zIVyJ)WId~rq0{)C4v&EV1P<=$30~_PCP+ym@>*N$LNi_*ja{Q}vstt1PPwcwoR}Wc zjeQo4$AA_b9pb?jRE3KR;EOabj*GqpTF@+T$yt|3_C8MI4ngV8oq`naCtanC#Vv9anM^$gT9g9|LSjDmcMOkaS|aA4lsuu!a}w=>d|_# zvyUAcciB$m3f}Q75%z-`xdlsKHTkrsCL6zccnEc-vl`ybP{?Lx>68f;jAVtIy0wLJ zpraf6BVtO^+f#d^w7Yu}u%7Mw9OWBcFs(M&L6?7>LPmbiDiRV|+!QH=HIaC;r~Y(p zs|&^hC!nG>-nB{5y4-_!9Qz9v;GbW*t`>qQt#DUJ-J2|+#b9nln*6P;k)eSm`Q;#J z+Pz2gv(%Tr9nEYRgiO9RRw9boSs{A)cW8ZmbF%x8xtreS=*-vmEgT=M=CeC>ES@kQsEaoFX3MyWmqBFxja*|_B zXc81eW%33i4TT=5g^aED$CUuv$Jgiy-76p}1 zL`q6j8e|ZpySo`0q`Mo00hAJuZY89po0$QI6p+>dL0SYv8i%goJ@~x(%yYdzzF*(` zJlC1C_qq4l`>eh0d#$}lFyb>TKuDtyd`wDH!ed;iG+ih($);nL{KGP$gWY{j15Y>aNR3#d+vml~_31FjY0lnUd?r!< zDQy#vJy3nKBc$2Ose$LkU@U+2=6ud9GjYwj3ZsShK^-BY{@AtwDJNr>MT25yKGpQ7 z|KvL}=Cenm=rn@%Q7{z@H0`n&X+`l_ILO9&w)|zpXf%S+QL$f3)AWiH87PP^DD%Nq zR)veNBYGHF26U&I^cii0$X2tN1?+?mRM3r1lU$!AOX|)^*;%I0xC)eBRhsY=IH7Rm zeM*DoC62cZ8Dvua;2(_)zlu`6nNp;ZIBr|Gk__Bgzeh|;Jw4h=*D`YPCOH)$dYZdm zYbUQn0D3%GJI8+>=9d(Bj*4PJ>DWdRSY*S4LeGSJ9j8wAx6vn&Ykc$Y3l9ArrqoJb z^XAm{C(jS^`b56))lz;z8`KW{)unpU1MW`j*qh3ULWY7@3eNy4_J<5X*r`r%qsNu2s|Z_zKEGLI)XK+$s&zfd zP(DaGo$rIqKWYx?>M|_j`PM4qv=R{qyU5}>R5JgP`feNIFm)UHEHUWM$NNPr{hfES z=f3lg3Zr6T_FLVL?IH&o8Wd?+JlH{QoNpc2$pjXAk5fpGkB_Aou+9G(_#J-T_I#+q zo^EFJ(0CZO`Mw9SK@e)49etUg~blbc$%e;b?i!$&9HK z7G)JTTFAd8SM#TPfWNTw2+%Qe*b_wk#Trl<#{t{XBik+w$A25aZjlER%F&b|c`%s= zPo8|TZ)jp*G?irl%-)h(X-`=$kU@sMYbn~HyDrB=!wz*7u zG%lUxrea9K*~78UZ$^zQd`Rn2=nS;s~;{Tf^XtgI54Ng>96EF?{f&%i0-j_1yb^J*7M1 z?H+)u_J7-jb($EwdH#25-hA*4sIh@JGMR{(+JXj1?F9~Y6%ow70F@8IRvh~hoMOn_HAD=aL*RR3oK z6-q9L2V*DkjRSbK{|g_sAIO1Y5MEwBh_57*@W<@~1_dT~^?FE$6gjF2_K20WUn*l;OuGXnzy zuulgzP!bZYo=lDn25f9>gtVeF%|0&M1nl>2EU#V2BK@n{&Fa{VilfJHh`_!sbQ-l~ zpa(8D{IsO8*OqR%f8iL$xg6dq;`nSl;A8~Dpm{vj4;pxnyLs;sXyQ~^G?0Hx%j4cA zDFDyWsXI8NX5-2El@1o|Gn_^_sAbm}v=oOX7pT0`iWe2Ao!*QL$(QP!_OAUd@M1^Y zI4lL2810u6LzM18S5853r%jaqGYKG{1qe6}Pj8Y60Sjdw1#>8Hn5Al1ezD}sD6zo( zJ_?6?!&(MnwVU$m0oq>5SKFAXsMKs1aLtN^mn%97=$MFkiNxE*aDe}e?1SrFSXf#n zRPoKI!KbO_h>)9SZHbVBcX|3O#{=K9t{SY%KUJ{svpHt|Lo&kX0h7B+9tn~9);yKe zs-dn}LK4$`arK`e77V|#_)bt(NHf|4J6+4|c7(2rd>Rh1X-iWW^Z%w!r!k8Mbu&mN zU_>CXumjbDVc&1q@n7Z@{xwqIP9wKSb^3opcROq^nqCr`^aZ$16(}SXiJdRB)|<&eXW!Q}d3e`1`BKm9*PO)s*S^FkZ7bW(q#`Dz zAb8Iannb#}&+a3zM?z4{92uw8;_pHHPtSkMWi_u|3wg}z@N2by0#8iL>foEooXhpN zQ5O&6E}T3DsT=22 zyK27{L~eD*wZZl)DW1BhAlLkVb-NcZLt)%QrRE}sCDs+^@0X6!1qVz?-G6n$*5CgU zo6j(@&G0*fB%R1Lv8kbcuO=|;7fnuOK4qc)KU7}3C}oQ7mf)V6=#Z^yeF|#x>dteF z6tcv>Z@cwarvwT zwG$YJw0$SkFY|E_ikPkYIXOvDi^L*3VG|Um)5JN%_q>H`nC<3o;Av!`rS)qyNo8ng z!Yd}o`AB+l8y!9Au|ZA!hH_Q}q!$pS)l@QUHFe5)pE zNqYk>^z5ZnL(>hYQOzut&hK?wnAMbJR;To?tWD-<$eo0fv9hYU)VpRZ2ApjEUV9?= z?mCr5u4-$IDI*(#g^y#3OG~}0jEtL1S5bxfNlH*eOWr%x5)}D%X0vmUd3W}$3Qq8kLB894ayk`f|Dlf3G65;1*VP)dSqt2`MDLGk1KabDs z6`$v9iTHtIXJ!_sOp0QYcNV+02^E({zgi|`DI^%jG!#!|t(29e=BdQ3P&y>D4npcI zKYS1%_BV_TXhqFJrZ@BBdk826)(;-WQk{A8)CcTr`JYCWEkBkrlKe5)CnEJFGH%{~ z{td4zDQHgXd`2ryn$)p>((!S9&hF^C+`N!qJePA;ThaG}z^2Uelv*KnPIiwULkqP! zy7j+iE6P~8zw9m53yiwNYAT|)(BBtl9s2PseS8mBXy3dU+p>x6J{qxdh{{VO2wy`Q z322aK_?@;B=PXy0sw)=fc$_YAUi(fD%|CvtbKa$ATJ2PwCvA8>edKPZ?6&kiSbLYod^vQ?&-|=#68S(tIaV+&T+|%yW4V_Gc2u*`p&^s?~oDve^eQFgO zR;GQ*-}8V|yv}~kL?7MRS`pGr@#C4#V&C`Z5F+EPJL?#8r&ga->3fz2zs zQQIakv&6kX=?G7}&o~MM)rXe)&y@-Fp*(yp9Bj{y_sMs!&oQra{a`Ir;)C}~+0mWK zdif(72%wC6xA1PBwd8#u0?WS)x_`RWb2KN?N!N@Wp=d-zGqqa9H_xyTpS(6|5n(l! zy7%JD`lDVXH+{{Lk-C0kx#{V4L~NR*B9^&iL)<{#C{xa7(Y-N_lUt{>B3es#-QRau zJ2^WC@&+k2z!wgsT-=t_Cu{@PKW(){CC(F2@I4WHRyvtloVj%%GhZ>E`MEK(1zBGo zxOgg4x`v2rgI6&YoOU+fDEW?vKHQkG8AtFs#gBSH$tQ-hxI{VMJ1$Rk4vGa&pAmhBt`LKu5~6?;ZtFW7jSeIh=mx%&$}PT3VPyur6<8?BAo@}^9tgKwd@)gU zi{J3N1LVbI-faqos3$)its1AS9Cqmo?ff@D=Wz$~b-wAB-tEM=HF$N`#}5w4uDWFK2o|=&oDgU~>Hl`!D!~sP2^*9kA5j#0sx+6k2CXK@ zoKQ^0-Hv-gGYH>Z#@W0rphMTx7%)k%p)l8=(H!vjK$USd<(1cftr&k)X7w1^oB4Rah5VfP zG1Wa{jv5J_lXSJ%Kq~6@Nq$7`qa18pw)Vb<*clHVN_4;a8p;xBcUM;)c9*!6qoPdI zyp&?{{0-48vE}*nnhCkpi{H!*5+!5egQPI`JONPEV9!k#S{KjsCFde##TcnMZyeU!vkFj%K4 zXGBgc_TGUo!wKc}CGzvHNJ-9I-T5K8Ryz~c!*aR(`vK7vHqY#G_C7-rC1Wcu-pR^< z{N3%~DJU_1{Vsy{s+aIs#S6HbkmFM&VZqRgh( zVtLL%py?;~eW_P6-;+c+oy)F3B60XJ>!7eHcL~<82~ZBYt;y6Ld@M#Ah^Q`FM#?B< zpB9Kuu35d(fd=j}N%Mv?77)H-JMHb8$v0q%w9m+ND$`9^r3kTDmo&Hdeo|oh!8zVO zJA-Gx)lf_N+pP*(Moy=LXw$CEdBcrE4M(+#8(Ev_gl*Hoc?Z&zHGud)z_ zmlRUz&;)qu+bgNRTgt7Yic(sk_Y{Gxtq8nrwr)c>`v!#8k|@8;+z*);Z4yx&P%8nS za>t-Xo*C4$^Z1vZi6>^q#2Ac(pV_iumWjzLwg3bUOWP@q-sg9 zjogj=w*}jh-ox{74b6jlM3Zcf*1{mu^hu$wm@{ZVLWLs|JbZ12^>QUY6e7?00xufq z65U87w@})3yhi;st9z_+g-J_&G(lokgcv9eVBdpiZiD*ujBON>iM?=HVmaa^r6}1@)*?)ho2t>E(q$?}r&k&dneWs# zzHo^N*|){X<5*^?`?ZJlaR-euSQ5JSB8A={3Lyel2(%Qt%-iwWU zJ%xo1q_3Ky-*s;FTyBoPfnG%J)}{{Y2_D84lRwT#%BI$+ad5Ie5F{1$+u%9T+}!0i zVsy=6<`qk?=6V_xLP3FXWKXs1BYvV}+mw$(n zah(n0dt@es{`B1+uLBha#wOCnbC9ji2>auLPq7O)^7OaNLJYT9Xn+NA>tO5~C#t*6 zQSsw~?~~+mU}yQvAr8;dJXE3iVw8+s($=^hm$>3BS7U!ob6=oQ)u?QREmjv?sT$Iz zg+-sPfyFv5>RZANfBi90-K@KV!*V2}XFQI6Gtq!zt^9|maZ?4gKa>t3%^v%566_JP z{Ca}`-x^k$5D_!n+2K!@zoM12X4S^fxT*cFT^-|=<29(F0eXnU< zQcB3}&ad03HcLPdX5;gUR@v~9_wxISZ}`5w`QW<_o!-r!2Tl$=9yO!ec0jx);d;!e zQH9688sgmFg2<`WAzvof(OoyZTCu~-K4tNH=ir(ze)hpjU@Zf#R99y$;2v||TkXKb zM(OC2*ILVxS2WpRliUFl0VRo1Lr@p(_^=U3?h)8wMOH~-uSChC_Q$aP8y<_g4P4dM zrz_A!%jHQZxybpqymAVvdP`j5R8Y>;{#e9RQyd5IQJM=TnZI^*ek}7n@5BI2i*jUY zBwj4r43drE5&x29^P2r#IgGYdxYf362-W`!E_atyht4#TyK+iJ2RXEbS&WY~tP~NG{e85Hz#ADyyYc zSE$e|k*a#HchMoD_t|ce!^wC$Rk@0a*hSThSq-9xLetW`Z?-Qadlpg1PU}Z6qJz_<^XK9{CEdc2wwiG~+jK3&}> zA|k4gkP7H2Gi)4Fpbgs?JLoH=);4TBD{r7Cs+(}kaGH--p8NVJ<0GFBdx;fPRMcX! zhaEZakOFE+Zz8T#={aPHok*ta%rzZ0b;hzgp%n)YJ zPoeklOD`WR^QJbiIa9oPje>i>^H$Pszmn$*Ea;x{Y5_ioU!RfYvB8_&O6HKXf)L4Y zu|A9Zr3H;dX~p%AL#tjRk#xv))YstGo4u`6WN#lNwG}$Z^hhhDKTLsaG!bNMa0+m0 ztEK_ROPSiOx3@=5H1YtyU{Z^5tG>V z5VRU3mhOfQlkLtRZ#!;<1Qj0>Do3V7a45t{+otE6Fxm}P?{#LyV%xbT7_-`Yh|P&u zH_iN9f0-qUAEL=+0~WqGNZa+8OPFc$RM6u+zfKCKr?{T8dF`{^!;a$t93T6>B}rDh z!8~ z(Z$jTG4GkP$EsYIEW3*Dpa-EUfo}Jd;P1$0a4+_wMBd@QUlsYmrWK>v%376g@ zc{79VA!o3#zar?o`xi2;?X126gbFC0zeO|uw584>&Vmvu9HUr?6!q|{O;YYe$5r_p z#%+N-U^I~oHn>kzGdZ;lhd*BP$U&Ru4=SdaDnudHrB_iL7d*`uA8W!LmRZzL!psgH zNvb%Cl*d{3;%?t#py5f3G}XakFH57z2h^mv+|p`Bf>&%|ZH@4A^gx zkrqtcIR7@(>?x;L&J~kumDlYj03FGlL%y4cK)dR2L8U-UYCd=(HtI8eeoakX_9a63 zl0sR!_;oU^O^H*+8}8OgA}UASNF$hVVN9w<0(r?s*ogTh@BOI*`}Tcde$SukIhEkR zz(D)bgTa&m!_JM-D;(Q1(K#|kZf>-(CyBxSi+FNFy?gRoBqU%9yZH26M3^vx!u>a% zc^!MRcBi`mQ?$D~O|Kz|?c&!>HU*!prT=hCz;EOz7CzLGAVLQ(P#-6YDR$n2vsKSA(8*Px@D2c?C-s zXUR@JB}MmCKemz#^=LJYR%{D?L(q7sDb4*A?7L>N{J%QOPej;2yc-Dn0+mMtDkmiI zHkjey4k5#re$#W4pyahEY)P-@r!EHZ^S|1^FJHw%DV?{pydojIWSHL3;r`q|nM3$1 z89hAwsiLAH3g-DuH);Boy!wlqGoS&xpr&%X5O({YXzZKdnKUMP zdea4OtR4w?Rx&|QzzdTTg2-OwQwZ6r{Y{69Vii7cYryAdwb!13k%Veylz1;uJX9)Xi1i_~MM6-u;yv!=dj%ijell5rI7vo= zbRU4pT!Q1J`uB<^O0Oem~=~zS8g`$N#iZ23)I*O2DW$i-nyDP(hh8Uoj}NOS5~$W<*LS z^3TZr(+}fmCTIl4{>=ZA2b4ly{_7?_x|&8 zm(Alteyxe6yDg98V|FoeET>F0m*c=bvx^e}JT|rs`{Qm!jjqThPSvM5!^=wraOqY9Y9wj=r9DF!?^m;*4!BTWYl9SjUx jE(=v(l4Aeu;sx!6HSrb7Pae;&06zs8Rq0Ae)0h7P_%hT+ literal 0 HcmV?d00001 diff --git a/doc/workflow/web_editor/show_file.png b/doc/workflow/web_editor/show_file.png new file mode 100644 index 0000000000000000000000000000000000000000..9cafcb551091a8959671dbedc113062ddbd7da7e GIT binary patch literal 111479 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 +iz*ER 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

    !PF#8fSS4{$Gb0Ic1Hg>g{4_su~ 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

    J_U|-M?5Sl6Bu3kh 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

    .*?
    |.*?}m) { |match| extract_piece(match) } @@ -53,8 +122,6 @@ module Gitlab # Extract images with probably parsable src text.gsub!(%r{}m) { |match| extract_piece(match) } - # TODO: add popups with additional information - text = parse(text, project) # Insert pre block extractions @@ -62,12 +129,11 @@ module Gitlab insert_piece($1) end - allowed_attributes = ActionView::Base.sanitized_allowed_attributes - allowed_tags = ActionView::Base.sanitized_allowed_tags + if options[:parse_tasks] + text = parse_tasks(text) + end - sanitize text.html_safe, - attributes: allowed_attributes + %w(id class), - tags: allowed_tags + %w(table tr td th) + text.html_safe end private @@ -84,78 +150,82 @@ module Gitlab @extractions[id] end - # Private: Parses text for references and emoji + # Private: Parses text for references # # text - Text to parse # # Returns parsed text def parse(text, project = @project) parse_references(text, project) if project - parse_emoji(text) text end + NAME_STR = Gitlab::Regex::NAMESPACE_REGEX_STR + PROJ_STR = "(?#{NAME_STR}/#{NAME_STR})" + REFERENCE_PATTERN = %r{ (?\W)? # Prefix ( # Reference - @(?[a-zA-Z][a-zA-Z0-9_\-\.]*) # User name + @(?#{NAME_STR}) # User name + |~(?
  • ' + unchecked_box = '' + checked_box = unchecked_box.sub(/\/>$/, 'checked="checked" />') + + # Regexp captures don't seem to work when +text+ is an + # ActiveSupport::SafeBuffer, hence the `String.new` + String.new(text).gsub(Taskable::TASK_PATTERN_HTML) do + checked = $LAST_MATCH_INFO[:checked].downcase == 'x' + p_tag = $LAST_MATCH_INFO[:p_tag] + + if checked + "#{li_tag}#{p_tag}#{checked_box}" + else + "#{li_tag}#{p_tag}#{unchecked_box}" + end + end end end end diff --git a/lib/gitlab/markdown_helper.rb b/lib/gitlab/markdown_helper.rb index abed12fe57..5e3cfc0585 100644 --- a/lib/gitlab/markdown_helper.rb +++ b/lib/gitlab/markdown_helper.rb @@ -21,5 +21,9 @@ module Gitlab def gitlab_markdown?(filename) filename.downcase.end_with?(*%w(.mdown .md .markdown)) end + + def previewable?(filename) + gitlab_markdown?(filename) || markup?(filename) + end end end diff --git a/lib/gitlab/middleware/static.rb b/lib/gitlab/middleware/static.rb new file mode 100644 index 0000000000..85ffa8aca6 --- /dev/null +++ b/lib/gitlab/middleware/static.rb @@ -0,0 +1,13 @@ +module Gitlab + module Middleware + class Static < ActionDispatch::Static + UPLOADS_REGEX = /\A\/uploads(\/|\z)/.freeze + + def call(env) + return @app.call(env) if env['PATH_INFO'] =~ UPLOADS_REGEX + + super + end + end + end +end diff --git a/lib/gitlab/note_data_builder.rb b/lib/gitlab/note_data_builder.rb new file mode 100644 index 0000000000..644dec45dc --- /dev/null +++ b/lib/gitlab/note_data_builder.rb @@ -0,0 +1,77 @@ +module Gitlab + class NoteDataBuilder + class << self + # Produce a hash of post-receive data + # + # For all notes: + # + # data = { + # object_kind: "note", + # user: { + # name: String, + # username: String, + # avatar_url: String + # } + # project_id: Integer, + # repository: { + # name: String, + # url: String, + # description: String, + # homepage: String, + # } + # object_attributes: { + # + # } + # : { + # } + # note-specific data is a hash with one of the following keys and contains + # the hook data for that type. + # - commit + # - issue + # - merge_request + # - snippet + # + def build(note, user) + project = note.project + data = build_base_data(project, user, note) + + if note.for_commit? + data[:commit] = build_data_for_commit(project, user, note) + elsif note.for_issue? + data[:issue] = note.noteable.hook_attrs + elsif note.for_merge_request? + data[:merge_request] = note.noteable.hook_attrs + elsif note.for_project_snippet? + data[:snippet] = note.noteable.hook_attrs + end + + data + end + + def build_base_data(project, user, note) + base_data = { + object_kind: "note", + user: user.hook_attrs, + project_id: project.id, + repository: { + name: project.name, + url: project.url_to_repo, + description: project.description, + homepage: project.web_url, + }, + object_attributes: note.hook_attrs + } + + base_data[:object_attributes][:url] = + Gitlab::UrlBuilder.new(:note).build(note.id) + base_data + end + + def build_data_for_commit(project, user, note) + # commit_id is the SHA hash + commit = project.repository.commit(note.commit_id) + commit.hook_attrs(project) + end + end + end +end diff --git a/lib/gitlab/o_auth/auth_hash.rb b/lib/gitlab/o_auth/auth_hash.rb new file mode 100644 index 0000000000..ce52beec78 --- /dev/null +++ b/lib/gitlab/o_auth/auth_hash.rb @@ -0,0 +1,54 @@ +# Class to parse and transform the info provided by omniauth +# +module Gitlab + module OAuth + class AuthHash + attr_reader :auth_hash + def initialize(auth_hash) + @auth_hash = auth_hash + end + + def uid + auth_hash.uid.to_s + end + + def provider + auth_hash.provider + end + + def info + auth_hash.info + end + + def name + (info.try(:name) || full_name).to_s.force_encoding('utf-8') + end + + def full_name + "#{info.first_name} #{info.last_name}" + end + + def username + (info.try(:nickname) || generate_username).to_s.force_encoding('utf-8') + end + + def email + (info.try(:email) || generate_temporarily_email).downcase + end + + def password + @password ||= Devise.friendly_token[0, 8].downcase + end + + # Get the first part of the email address (before @) + # In addtion in removes illegal characters + def generate_username + email.match(/^[^@]*/)[0].parameterize + end + + def generate_temporarily_email + "temp-email-for-oauth-#{username}@gitlab.localhost" + end + end + end +end diff --git a/lib/gitlab/o_auth/user.rb b/lib/gitlab/o_auth/user.rb new file mode 100644 index 0000000000..2f5c217d76 --- /dev/null +++ b/lib/gitlab/o_auth/user.rb @@ -0,0 +1,106 @@ +# OAuth extension for User model +# +# * Find GitLab user based on omniauth uid and provider +# * Create new user from omniauth data +# +module Gitlab + module OAuth + class ForbiddenAction < StandardError; end + + class User + attr_accessor :auth_hash, :gl_user + + def initialize(auth_hash) + self.auth_hash = auth_hash + end + + def persisted? + gl_user.try(:persisted?) + end + + def new? + !persisted? + end + + def valid? + gl_user.try(:valid?) + end + + def save + unauthorized_to_create unless gl_user + + if needs_blocking? + gl_user.save! + gl_user.block + else + gl_user.save! + end + + log.info "(OAuth) saving user #{auth_hash.email} from login with extern_uid => #{auth_hash.uid}" + gl_user + rescue ActiveRecord::RecordInvalid => e + log.info "(OAuth) Error saving user: #{gl_user.errors.full_messages}" + return self, e.record.errors + end + + def gl_user + @user ||= find_by_uid_and_provider + + if signup_enabled? + @user ||= build_new_user + end + + @user + end + + protected + + def needs_blocking? + new? && block_after_signup? + end + + def signup_enabled? + Gitlab.config.omniauth.allow_single_sign_on + end + + def block_after_signup? + Gitlab.config.omniauth.block_auto_created_users + end + + def auth_hash=(auth_hash) + @auth_hash = AuthHash.new(auth_hash) + end + + def find_by_uid_and_provider + identity = Identity.find_by(provider: auth_hash.provider, extern_uid: auth_hash.uid) + identity && identity.user + end + + def build_new_user + user = ::User.new(user_attributes) + user.skip_confirmation! + user.identities.new(extern_uid: auth_hash.uid, provider: auth_hash.provider) + user + end + + def user_attributes + { + name: auth_hash.name, + username: ::Namespace.clean_path(auth_hash.username), + email: auth_hash.email, + password: auth_hash.password, + password_confirmation: auth_hash.password, + password_automatically_set: true + } + end + + def log + Gitlab::AppLogger + end + + def unauthorized_to_create + raise ForbiddenAction.new("Unauthorized to create user, signup disabled for #{auth_hash.provider}") + end + end + end +end diff --git a/lib/gitlab/oauth/user.rb b/lib/gitlab/oauth/user.rb deleted file mode 100644 index 0056eb3a28..0000000000 --- a/lib/gitlab/oauth/user.rb +++ /dev/null @@ -1,113 +0,0 @@ -# OAuth extension for User model -# -# * Find GitLab user based on omniauth uid and provider -# * Create new user from omniauth data -# -module Gitlab - module OAuth - class User - class << self - attr_reader :auth - - def find(auth) - @auth = auth - find_by_uid_and_provider - end - - def create(auth) - @auth = auth - password = Devise.friendly_token[0, 8].downcase - opts = { - extern_uid: uid, - provider: provider, - name: name, - username: username, - email: email, - password: password, - password_confirmation: password, - } - - user = model.build_user(opts) - user.skip_confirmation! - - # Services like twitter and github does not return email via oauth - # In this case we generate temporary email and force user to fill it later - if user.email.blank? - user.generate_tmp_oauth_email - elsif provider != "ldap" - # Google oauth returns email but dont return nickname - # So we use part of email as username for new user - # For LDAP, username is already set to the user's - # uid/userid/sAMAccountName. - email_username = email.match(/^[^@]*/)[0] - # Strip apostrophes since they are disallowed as part of username - user.username = email_username.gsub("'", "") - end - - begin - user.save! - rescue ActiveRecord::RecordInvalid => e - log.info "(OAuth) Email #{e.record.errors[:email]}. Username #{e.record.errors[:username]}" - return nil, e.record.errors - end - - log.info "(OAuth) Creating user #{email} from login with extern_uid => #{uid}" - - if Gitlab.config.omniauth['block_auto_created_users'] && !ldap? - user.block - end - - user - end - - private - - def find_by_uid_and_provider - model.where(provider: provider, extern_uid: uid).last - end - - def uid - uid = auth.info.uid || auth.uid - uid = uid.to_s unless uid.nil? - uid - end - - def email - auth.info.email.downcase unless auth.info.email.nil? - end - - def name - if auth.info.name.nil? - "#{auth.info.first_name} #{auth.info.last_name}".force_encoding('utf-8') - else - auth.info.name.to_s.force_encoding('utf-8') - end - end - - def username - auth.info.nickname.to_s.force_encoding("utf-8") - end - - def provider - auth.provider - end - - def log - Gitlab::AppLogger - end - - def model - ::User - end - - def raise_error(message) - raise OmniAuth::Error, "(OAuth) " + message - end - - def ldap? - provider == 'ldap' - end - end - end - end -end diff --git a/lib/gitlab/popen.rb b/lib/gitlab/popen.rb index e2fbafb389..43e07e0916 100644 --- a/lib/gitlab/popen.rb +++ b/lib/gitlab/popen.rb @@ -21,12 +21,15 @@ module Gitlab @cmd_output = "" @cmd_status = 0 Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| + # We are not using stdin so we should close it, in case the command we + # are running waits for input. + stdin.close @cmd_output << stdout.read @cmd_output << stderr.read @cmd_status = wait_thr.value.exitstatus end - return @cmd_output, @cmd_status + [@cmd_output, @cmd_status] end end end diff --git a/lib/gitlab/production_logger.rb b/lib/gitlab/production_logger.rb new file mode 100644 index 0000000000..89ce7144b1 --- /dev/null +++ b/lib/gitlab/production_logger.rb @@ -0,0 +1,7 @@ +module Gitlab + class ProductionLogger < Gitlab::Logger + def self.file_name_noext + 'production' + end + end +end diff --git a/lib/gitlab/project_search_results.rb b/lib/gitlab/project_search_results.rb new file mode 100644 index 0000000000..0dab7bcfa4 --- /dev/null +++ b/lib/gitlab/project_search_results.rb @@ -0,0 +1,77 @@ +module Gitlab + class ProjectSearchResults < SearchResults + attr_reader :project, :repository_ref + + def initialize(project_id, query, repository_ref = nil) + @project = Project.find(project_id) + @repository_ref = if repository_ref.present? + repository_ref + else + nil + end + @query = Shellwords.shellescape(query) if query.present? + end + + def objects(scope, page = nil) + case scope + when 'notes' + notes.page(page).per(per_page) + when 'blobs' + Kaminari.paginate_array(blobs).page(page).per(per_page) + when 'wiki_blobs' + Kaminari.paginate_array(wiki_blobs).page(page).per(per_page) + else + super + end + end + + def total_count + @total_count ||= issues_count + merge_requests_count + blobs_count + + notes_count + wiki_blobs_count + end + + def blobs_count + @blobs_count ||= blobs.count + end + + def notes_count + @notes_count ||= notes.count + end + + def wiki_blobs_count + @wiki_blobs_count ||= wiki_blobs.count + end + + private + + def blobs + if project.empty_repo? || query.blank? + [] + else + project.repository.search_files(query, repository_ref) + end + end + + def wiki_blobs + if project.wiki_enabled? && query.present? + project_wiki = ProjectWiki.new(project) + + unless project_wiki.empty? + project_wiki.search_files(query) + else + [] + end + else + [] + end + end + + def notes + Note.where(project_id: limit_project_ids).user.search(query).order('updated_at DESC') + end + + def limit_project_ids + [project.id] + end + end +end diff --git a/lib/gitlab/push_data_builder.rb b/lib/gitlab/push_data_builder.rb new file mode 100644 index 0000000000..f8da452e4c --- /dev/null +++ b/lib/gitlab/push_data_builder.rb @@ -0,0 +1,90 @@ +module Gitlab + class PushDataBuilder + class << self + # Produce a hash of post-receive data + # + # data = { + # before: String, + # after: String, + # ref: String, + # user_id: String, + # user_name: String, + # user_email: String + # project_id: String, + # repository: { + # name: String, + # url: String, + # description: String, + # homepage: String, + # }, + # commits: Array, + # total_commits_count: Fixnum + # } + # + def build(project, user, oldrev, newrev, ref, commits = [], message = nil) + # Total commits count + commits_count = commits.size + + # Get latest 20 commits ASC + commits_limited = commits.last(20) + + # For performance purposes maximum 20 latest commits + # will be passed as post receive hook data. + commit_attrs = commits_limited.map do |commit| + commit.hook_attrs(project) + end + + type = Gitlab::Git.tag_ref?(ref) ? "tag_push" : "push" + # Hash to be passed as post_receive_data + data = { + object_kind: type, + before: oldrev, + after: newrev, + ref: ref, + checkout_sha: checkout_sha(project.repository, newrev, ref), + message: message, + user_id: user.id, + user_name: user.name, + user_email: user.email, + project_id: project.id, + repository: { + name: project.name, + url: project.url_to_repo, + description: project.description, + homepage: project.web_url, + git_http_url: project.http_url_to_repo, + git_ssh_url: project.ssh_url_to_repo, + visibility_level: project.visibility_level + }, + commits: commit_attrs, + total_commits_count: commits_count + } + + data + end + + # This method provide a sample data generated with + # existing project and commits to test web hooks + def build_sample(project, user) + commits = project.repository.commits(project.default_branch, nil, 3) + ref = "#{Gitlab::Git::BRANCH_REF_PREFIX}#{project.default_branch}" + build(project, user, commits.last.id, commits.first.id, ref, commits) + end + + def checkout_sha(repository, newrev, ref) + # Find sha for tag, except when it was deleted. + if Gitlab::Git.tag_ref?(ref) && !Gitlab::Git.blank_ref?(newrev) + tag_name = Gitlab::Git.ref_name(ref) + tag = repository.find_tag(tag_name) + + if tag + commit = repository.commit(tag.target) + commit.try(:sha) + end + else + newrev + end + end + end + end +end diff --git a/lib/gitlab/reference_extractor.rb b/lib/gitlab/reference_extractor.rb index 1eda614807..a502a8fe9c 100644 --- a/lib/gitlab/reference_extractor.rb +++ b/lib/gitlab/reference_extractor.rb @@ -1,59 +1,94 @@ module Gitlab # Extract possible GFM references from an arbitrary String for further processing. class ReferenceExtractor - attr_accessor :users, :issues, :merge_requests, :snippets, :commits + attr_accessor :project, :current_user, :references - include Markdown + include ::Gitlab::Markdown - def initialize - @users, @issues, @merge_requests, @snippets, @commits = [], [], [], [], [] + def initialize(project, current_user = nil) + @project = project + @current_user = current_user end - def analyze string - parse_references(string.dup) + def can?(user, action, subject) + Ability.abilities.allowed?(user, action, subject) + end + + def analyze(text) + text = text.dup + + # Remove preformatted/code blocks so that references are not included + text.gsub!(%r{
    .*?
    |.*?}m) { |match| '' } + text.gsub!(%r{^```.*?^```}m) { |match| '' } + + @references = Hash.new { |hash, type| hash[type] = [] } + parse_references(text) end # Given a valid project, resolve the extracted identifiers of the requested type to # model objects. - def users_for project - users.map do |identifier| - project.users.where(username: identifier).first - end.reject(&:nil?) + def users + references[:user].uniq.map do |project, identifier| + if identifier == "all" + project.team.members.flatten + elsif namespace = Namespace.find_by(path: identifier) + if namespace.is_a?(Group) + namespace.users + else + namespace.owner + end + end + end.flatten.compact.uniq end - def issues_for project - issues.map do |identifier| - project.issues.where(iid: identifier).first - end.reject(&:nil?) + def labels + references[:label].uniq.map do |project, identifier| + project.labels.where(id: identifier).first + end.compact.uniq end - def merge_requests_for project - merge_requests.map do |identifier| + def issues + references[:issue].uniq.map do |project, identifier| + if project.default_issues_tracker? + project.issues.where(iid: identifier).first + end + end.compact.uniq + end + + def merge_requests + references[:merge_request].uniq.map do |project, identifier| project.merge_requests.where(iid: identifier).first - end.reject(&:nil?) + end.compact.uniq end - def snippets_for project - snippets.map do |identifier| + def snippets + references[:snippet].uniq.map do |project, identifier| project.snippets.where(id: identifier).first - end.reject(&:nil?) + end.compact.uniq end - def commits_for project - repo = project.repository - return [] if repo.nil? + def commits + references[:commit].uniq.map do |project, identifier| + repo = project.repository + repo.commit(identifier) if repo + end.compact.uniq + end - commits.map do |identifier| - repo.commit(identifier) - end.reject(&:nil?) + def commit_ranges + references[:commit_range].uniq.map do |project, identifier| + repo = project.repository + if repo + from_id, to_id = identifier.split(/\.{2,3}/, 2) + [repo.commit(from_id), repo.commit(to_id)] + end + end.compact.uniq end private - def reference_link(type, identifier, project) - # Append identifier to the appropriate collection. - send("#{type}s") << identifier + def reference_link(type, identifier, project, _) + references[type] << [project, identifier] end end end diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index 4b8038843b..9aeed5e693 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -2,49 +2,66 @@ module Gitlab module Regex extend self - def username_regex - default_regex + NAMESPACE_REGEX_STR = '(?:[a-zA-Z0-9_\.][a-zA-Z0-9_\-\.]*)'.freeze + + def namespace_regex + @namespace_regex ||= /\A#{NAMESPACE_REGEX_STR}\z/.freeze end - def username_regex_message - default_regex_message + def namespace_regex_message + "can contain only letters, digits, '_', '-' and '.'. " \ + "Cannot start with '-'." \ end + + def namespace_name_regex + @namespace_name_regex ||= /\A[a-zA-Z0-9_\-\. ]*\z/.freeze + end + + def namespace_name_regex_message + "can contain only letters, digits, '_', '-', '.' and space." + end + + def project_name_regex - /\A[a-zA-Z0-9_][a-zA-Z0-9_\-\. ]*\z/ + @project_name_regex ||= /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\. ]*\z/.freeze end - def project_regex_message - "can contain only letters, digits, '_', '-' and '.' and space. " \ + def project_name_regex_message + "can contain only letters, digits, '_', '-', '.' and space. " \ "It must start with letter, digit or '_'." end - def name_regex - /\A[a-zA-Z0-9_\-\. ]*\z/ + + def project_path_regex + @project_path_regex ||= /\A[a-zA-Z0-9_.][a-zA-Z0-9_\-\.]*(? ex + log_and_raise(CheckoutFailed, ex.message) + end # update the file in the satellite's working dir file_path_in_satellite = File.join(repo.working_dir, file_path) @@ -31,19 +35,33 @@ module Gitlab # commit the changes # will raise CommandFailed when commit fails - repo.git.commit(raise: true, timeout: true, a: true, m: commit_message) + begin + repo.git.commit(raise: true, timeout: true, a: true, m: commit_message) + rescue Grit::Git::CommandFailed => ex + log_and_raise(CommitFailed, ex.message) + end + target_branch = new_branch.present? ? "#{ref}:#{new_branch}" : ref + # push commit back to bare repo # will raise CommandFailed when push fails - repo.git.push({raise: true, timeout: true}, :origin, ref) + begin + repo.git.push({ raise: true, timeout: true }, :origin, target_branch) + rescue Grit::Git::CommandFailed => ex + log_and_raise(PushFailed, ex.message) + end # everything worked true end - rescue Grit::Git::CommandFailed => ex - Gitlab::GitLogger.error(ex.message) - false + end + + private + + def log_and_raise(errorClass, message) + Gitlab::GitLogger.error(message) + raise(errorClass, message) end end end diff --git a/lib/gitlab/satellite/files/new_file_action.rb b/lib/gitlab/satellite/files/new_file_action.rb index 15e9b7a6f7..724dfa0d04 100644 --- a/lib/gitlab/satellite/files/new_file_action.rb +++ b/lib/gitlab/satellite/files/new_file_action.rb @@ -9,12 +9,19 @@ module Gitlab # Returns false if committing the change fails # Returns false if pushing from the satellite to bare repo failed or was rejected # Returns true otherwise - def commit!(content, commit_message, encoding) + def commit!(content, commit_message, encoding, new_branch = nil) in_locked_and_timed_satellite do |repo| prepare_satellite!(repo) # create target branch in satellite at the corresponding commit from bare repo - repo.git.checkout({raise: true, timeout: true, b: true}, ref, "origin/#{ref}") + current_ref = + if @project.empty_repo? + # skip this step if we want to add first file to empty repo + Satellite::PARKING_BRANCH + else + repo.git.checkout({ raise: true, timeout: true, b: true }, ref, "origin/#{ref}") + ref + end file_path_in_satellite = File.join(repo.working_dir, file_path) dir_name_in_satellite = File.dirname(file_path_in_satellite) @@ -38,10 +45,15 @@ module Gitlab # will raise CommandFailed when commit fails repo.git.commit(raise: true, timeout: true, a: true, m: commit_message) + target_branch = if new_branch.present? && !@project.empty_repo? + "#{ref}:#{new_branch}" + else + "#{current_ref}:#{ref}" + end # push commit back to bare repo # will raise CommandFailed when push fails - repo.git.push({raise: true, timeout: true}, :origin, ref) + repo.git.push({ raise: true, timeout: true }, :origin, target_branch) # everything worked true diff --git a/lib/gitlab/satellite/merge_action.rb b/lib/gitlab/satellite/merge_action.rb index 7c9b229464..1f2e5f82dd 100644 --- a/lib/gitlab/satellite/merge_action.rb +++ b/lib/gitlab/satellite/merge_action.rb @@ -65,15 +65,16 @@ module Gitlab prepare_satellite!(merge_repo) update_satellite_source_and_target!(merge_repo) if merge_request.for_fork? - # Only show what is new in the source branch compared to the target branch, not the other way around. - # The line below with merge_base is equivalent to diff with three dots (git diff branch1...branch2) - # From the git documentation: "git diff A...B" is equivalent to "git diff $(git-merge-base A B) B" - common_commit = merge_repo.git.native(:merge_base, default_options, ["origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}"]).strip - diffs = merge_repo.diff(common_commit, "source/#{merge_request.source_branch}") + repository = Gitlab::Git::Repository.new(merge_repo.path) + diffs = Gitlab::Git::Diff.between( + repository, + "source/#{merge_request.source_branch}", + "origin/#{merge_request.target_branch}" + ) else raise "Attempt to determine diffs between for a non forked merge request in satellite MergeRequest.id:[#{merge_request.id}]" end - diffs = diffs.map { |diff| Gitlab::Git::Diff.new(diff) } + return diffs end rescue Grit::Git::CommandFailed => ex @@ -85,7 +86,7 @@ module Gitlab in_locked_and_timed_satellite do |merge_repo| prepare_satellite!(merge_repo) update_satellite_source_and_target!(merge_repo) - patch = merge_repo.git.format_patch(default_options({stdout: true}), "origin/#{merge_request.target_branch}..source/#{merge_request.source_branch}") + patch = merge_repo.git.format_patch(default_options({ stdout: true }), "origin/#{merge_request.target_branch}..source/#{merge_request.source_branch}") end rescue Grit::Git::CommandFailed => ex handle_exception(ex) @@ -96,12 +97,17 @@ module Gitlab in_locked_and_timed_satellite do |merge_repo| prepare_satellite!(merge_repo) update_satellite_source_and_target!(merge_repo) - if (merge_request.for_fork?) - commits = merge_repo.commits_between("origin/#{merge_request.target_branch}", "source/#{merge_request.source_branch}") + if merge_request.for_fork? + repository = Gitlab::Git::Repository.new(merge_repo.path) + commits = Gitlab::Git::Commit.between( + repository, + "origin/#{merge_request.target_branch}", + "source/#{merge_request.source_branch}" + ) else raise "Attempt to determine commits between for a non forked merge request in satellite MergeRequest.id:[#{merge_request.id}]" end - commits = commits.map { |commit| Gitlab::Git::Commit.new(commit, nil) } + return commits end rescue Grit::Git::CommandFailed => ex @@ -122,7 +128,7 @@ module Gitlab # merge the source branch into the satellite # will raise CommandFailed when merge fails - repo.git.merge(default_options({no_ff: true}), "-m#{message}", "source/#{merge_request.source_branch}") + repo.git.merge(default_options({ no_ff: true }), "-m#{message}", "source/#{merge_request.source_branch}") rescue Grit::Git::CommandFailed => ex handle_exception(ex) end @@ -131,7 +137,7 @@ module Gitlab def update_satellite_source_and_target!(repo) repo.remote_add('source', merge_request.source_project.repository.path_to_repo) repo.remote_fetch('source') - repo.git.checkout(default_options({b: true}), merge_request.target_branch, "origin/#{merge_request.target_branch}") + repo.git.checkout(default_options({ b: true }), merge_request.target_branch, "origin/#{merge_request.target_branch}") rescue Grit::Git::CommandFailed => ex handle_exception(ex) end diff --git a/lib/gitlab/satellite/satellite.rb b/lib/gitlab/satellite/satellite.rb index 7c058b58c4..398643d68d 100644 --- a/lib/gitlab/satellite/satellite.rb +++ b/lib/gitlab/satellite/satellite.rb @@ -1,5 +1,14 @@ module Gitlab module Satellite + autoload :DeleteFileAction, 'gitlab/satellite/files/delete_file_action' + autoload :EditFileAction, 'gitlab/satellite/files/edit_file_action' + autoload :FileAction, 'gitlab/satellite/files/file_action' + autoload :NewFileAction, 'gitlab/satellite/files/new_file_action' + + class CheckoutFailed < StandardError; end + class CommitFailed < StandardError; end + class PushFailed < StandardError; end + class Satellite include Gitlab::Popen @@ -11,7 +20,7 @@ module Gitlab @project = project end - def log message + def log(message) Gitlab::Satellite::Logger.error(message) end @@ -95,16 +104,12 @@ module Gitlab heads = repo.heads.map(&:name) # update or create the parking branch - if heads.include? PARKING_BRANCH - repo.git.checkout({}, PARKING_BRANCH) - else - repo.git.checkout(default_options({b: true}), PARKING_BRANCH) - end + repo.git.checkout(default_options({ B: true }), PARKING_BRANCH) # remove the parking branch from the list of heads ... heads.delete(PARKING_BRANCH) # ... and delete all others - heads.each { |head| repo.git.branch(default_options({D: true}), head) } + heads.each { |head| repo.git.branch(default_options({ D: true }), head) } end # Deletes all remotes except origin @@ -121,11 +126,12 @@ module Gitlab # # Note: this will only update remote branches (i.e. origin/*) def update_from_source! + repo.git.remote(default_options, 'set-url', :origin, project.repository.path_to_repo) repo.git.fetch(default_options, :origin) end def default_options(options = {}) - {raise: true, timeout: true}.merge(options) + { raise: true, timeout: true }.merge(options) end # Create directory for storing diff --git a/lib/gitlab/search_results.rb b/lib/gitlab/search_results.rb new file mode 100644 index 0000000000..75a3dfe37c --- /dev/null +++ b/lib/gitlab/search_results.rb @@ -0,0 +1,69 @@ +module Gitlab + class SearchResults + attr_reader :query + + # Limit search results by passed project ids + # It allows us to search only for projects user has access to + attr_reader :limit_project_ids + + def initialize(limit_project_ids, query) + @limit_project_ids = limit_project_ids || Project.all + @query = Shellwords.shellescape(query) if query.present? + end + + def objects(scope, page = nil) + case scope + when 'projects' + projects.page(page).per(per_page) + when 'issues' + issues.page(page).per(per_page) + when 'merge_requests' + merge_requests.page(page).per(per_page) + else + Kaminari.paginate_array([]).page(page).per(per_page) + end + end + + def total_count + @total_count ||= projects_count + issues_count + merge_requests_count + end + + def projects_count + @projects_count ||= projects.count + end + + def issues_count + @issues_count ||= issues.count + end + + def merge_requests_count + @merge_requests_count ||= merge_requests.count + end + + def empty? + total_count.zero? + end + + private + + def projects + Project.where(id: limit_project_ids).search(query) + end + + def issues + Issue.where(project_id: limit_project_ids).full_search(query).order('updated_at DESC') + end + + def merge_requests + MergeRequest.in_projects(limit_project_ids).full_search(query).order('updated_at DESC') + end + + def default_scope + 'projects' + end + + def per_page + 20 + end + end +end diff --git a/lib/gitlab/sidekiq_logger.rb b/lib/gitlab/sidekiq_logger.rb new file mode 100644 index 0000000000..c1dab87a43 --- /dev/null +++ b/lib/gitlab/sidekiq_logger.rb @@ -0,0 +1,7 @@ +module Gitlab + class SidekiqLogger < Gitlab::Logger + def self.file_name_noext + 'sidekiq' + end + end +end diff --git a/lib/gitlab/sidekiq_middleware/memory_killer.rb b/lib/gitlab/sidekiq_middleware/memory_killer.rb new file mode 100644 index 0000000000..0f2db50e98 --- /dev/null +++ b/lib/gitlab/sidekiq_middleware/memory_killer.rb @@ -0,0 +1,53 @@ +module Gitlab + module SidekiqMiddleware + class MemoryKiller + # Default the RSS limit to 0, meaning the MemoryKiller is disabled + MAX_RSS = (ENV['SIDEKIQ_MEMORY_KILLER_MAX_RSS'] || 0).to_s.to_i + # Give Sidekiq 15 minutes of grace time after exceeding the RSS limit + GRACE_TIME = (ENV['SIDEKIQ_MEMORY_KILLER_GRACE_TIME'] || 15 * 60).to_s.to_i + # Wait 30 seconds for running jobs to finish during graceful shutdown + SHUTDOWN_WAIT = (ENV['SIDEKIQ_MEMORY_KILLER_SHUTDOWN_WAIT'] || 30).to_s.to_i + + # Create a mutex used to ensure there will be only one thread waiting to + # shut Sidekiq down + MUTEX = Mutex.new + + def call(worker, job, queue) + yield + current_rss = get_rss + + return unless MAX_RSS > 0 && current_rss > MAX_RSS + + Thread.new do + # Return if another thread is already waiting to shut Sidekiq down + return unless MUTEX.try_lock + + Sidekiq.logger.warn "current RSS #{current_rss} exceeds maximum RSS "\ + "#{MAX_RSS}" + Sidekiq.logger.warn "spawned thread that will shut down PID "\ + "#{Process.pid} in #{GRACE_TIME} seconds" + sleep(GRACE_TIME) + + Sidekiq.logger.warn "sending SIGUSR1 to PID #{Process.pid}" + Process.kill('SIGUSR1', Process.pid) + + Sidekiq.logger.warn "waiting #{SHUTDOWN_WAIT} seconds before sending "\ + "SIGTERM to PID #{Process.pid}" + sleep(SHUTDOWN_WAIT) + + Sidekiq.logger.warn "sending SIGTERM to PID #{Process.pid}" + Process.kill('SIGTERM', Process.pid) + end + end + + private + + def get_rss + output, status = Gitlab::Popen.popen(%W(ps -o rss= -p #{Process.pid})) + return 0 unless status.zero? + + output.to_i + end + end + end +end diff --git a/lib/gitlab/snippet_search_results.rb b/lib/gitlab/snippet_search_results.rb new file mode 100644 index 0000000000..938219efdb --- /dev/null +++ b/lib/gitlab/snippet_search_results.rb @@ -0,0 +1,131 @@ +module Gitlab + class SnippetSearchResults < SearchResults + attr_reader :limit_snippet_ids + + def initialize(limit_snippet_ids, query) + @limit_snippet_ids = limit_snippet_ids + @query = query + end + + def objects(scope, page = nil) + case scope + when 'snippet_titles' + Kaminari.paginate_array(snippet_titles).page(page).per(per_page) + when 'snippet_blobs' + Kaminari.paginate_array(snippet_blobs).page(page).per(per_page) + else + super + end + end + + def total_count + @total_count ||= snippet_titles_count + snippet_blobs_count + end + + def snippet_titles_count + @snippet_titles_count ||= snippet_titles.count + end + + def snippet_blobs_count + @snippet_blobs_count ||= snippet_blobs.count + end + + private + + def snippet_titles + Snippet.where(id: limit_snippet_ids).search(query).order('updated_at DESC') + end + + def snippet_blobs + search = Snippet.where(id: limit_snippet_ids).search_code(query) + search = search.order('updated_at DESC').to_a + snippets = [] + search.each { |e| snippets << chunk_snippet(e) } + snippets + end + + def default_scope + 'snippet_blobs' + end + + # Get an array of line numbers surrounding a matching + # line, bounded by min/max. + # + # @returns Array of line numbers + def bounded_line_numbers(line, min, max) + lower = line - surrounding_lines > min ? line - surrounding_lines : min + upper = line + surrounding_lines < max ? line + surrounding_lines : max + (lower..upper).to_a + end + + # Returns a sorted set of lines to be included in a snippet preview. + # This ensures matching adjacent lines do not display duplicated + # surrounding code. + # + # @returns Array, unique and sorted. + def matching_lines(lined_content) + used_lines = [] + lined_content.each_with_index do |line, line_number| + used_lines.concat bounded_line_numbers( + line_number, + 0, + lined_content.size + ) if line.include?(query) + end + + used_lines.uniq.sort + end + + # 'Chunkify' entire snippet. Splits the snippet data into matching lines + + # surrounding_lines() worth of unmatching lines. + # + # @returns a hash with {snippet_object, snippet_chunks:{data,start_line}} + def chunk_snippet(snippet) + lined_content = snippet.content.split("\n") + used_lines = matching_lines(lined_content) + + snippet_chunk = [] + snippet_chunks = [] + snippet_start_line = 0 + last_line = -1 + + # Go through each used line, and add consecutive lines as a single chunk + # to the snippet chunk array. + used_lines.each do |line_number| + if last_line < 0 + # Start a new chunk. + snippet_start_line = line_number + snippet_chunk << lined_content[line_number] + elsif last_line == line_number - 1 + # Consecutive line, continue chunk. + snippet_chunk << lined_content[line_number] + else + # Non-consecutive line, add chunk to chunk array. + snippet_chunks << { + data: snippet_chunk.join("\n"), + start_line: snippet_start_line + 1 + } + + # Start a new chunk. + snippet_chunk = [lined_content[line_number]] + snippet_start_line = line_number + end + last_line = line_number + end + # Add final chunk to chunk array + snippet_chunks << { + data: snippet_chunk.join("\n"), + start_line: snippet_start_line + 1 + } + + # Return snippet with chunk array + { snippet_object: snippet, snippet_chunks: snippet_chunks } + end + + # Defines how many unmatching lines should be + # included around the matching lines in a snippet + def surrounding_lines + 3 + end + end +end diff --git a/lib/gitlab/theme.rb b/lib/gitlab/theme.rb index b7c50cb734..43093c7d27 100644 --- a/lib/gitlab/theme.rb +++ b/lib/gitlab/theme.rb @@ -5,6 +5,7 @@ module Gitlab MODERN = 3 unless const_defined?(:MODERN) GRAY = 4 unless const_defined?(:GRAY) COLOR = 5 unless const_defined?(:COLOR) + BLUE = 6 unless const_defined?(:BLUE) def self.css_class_by_id(id) themes = { @@ -12,12 +13,27 @@ module Gitlab MARS => "ui_mars", MODERN => "ui_modern", GRAY => "ui_gray", - COLOR => "ui_color" + COLOR => "ui_color", + BLUE => "ui_blue" } id ||= Gitlab.config.gitlab.default_theme - return themes[id] + themes[id] + end + + def self.type_css_class_by_id(id) + types = { + BASIC => 'light_theme', + MARS => 'dark_theme', + MODERN => 'dark_theme', + GRAY => 'dark_theme', + COLOR => 'dark_theme' + } + + id ||= Gitlab.config.gitlab.default_theme + + types[id] end end end diff --git a/lib/gitlab/upgrader.rb b/lib/gitlab/upgrader.rb index 0846359f9b..0570c2fbeb 100644 --- a/lib/gitlab/upgrader.rb +++ b/lib/gitlab/upgrader.rb @@ -43,7 +43,7 @@ module Gitlab end def latest_version_raw - remote_tags, _ = Gitlab::Popen.popen(%W(git ls-remote --tags origin)) + remote_tags, _ = Gitlab::Popen.popen(%W(git ls-remote --tags https://gitlab.com/gitlab-org/gitlab-ce.git)) git_tags = remote_tags.split("\n").grep(/tags\/v#{current_version.major}/) git_tags = git_tags.select { |version| version =~ /v\d\.\d\.\d\Z/ } last_tag = git_tags.last.match(/v\d\.\d\.\d/).to_s @@ -62,7 +62,7 @@ module Gitlab end def env - {'RAILS_ENV' => 'production'} + { 'RAILS_ENV' => 'production' } end def upgrade diff --git a/lib/gitlab/url_builder.rb b/lib/gitlab/url_builder.rb index de7e040408..11b0d44f34 100644 --- a/lib/gitlab/url_builder.rb +++ b/lib/gitlab/url_builder.rb @@ -1,6 +1,7 @@ module Gitlab class UrlBuilder include Rails.application.routes.url_helpers + include GitlabRoutingHelper def initialize(type) @type = type @@ -9,17 +10,51 @@ module Gitlab def build(id) case @type when :issue - issue_url(id) + build_issue_url(id) + when :merge_request + build_merge_request_url(id) + when :note + build_note_url(id) + end end private - def issue_url(id) + def build_issue_url(id) issue = Issue.find(id) - project_issue_url(id: issue.iid, - project_id: issue.project, - host: Settings.gitlab['url']) + issue_url(issue, host: Gitlab.config.gitlab['url']) + end + + def build_merge_request_url(id) + merge_request = MergeRequest.find(id) + merge_request_url(merge_request, host: Gitlab.config.gitlab['url']) + end + + def build_note_url(id) + note = Note.find(id) + if note.for_commit? + namespace_project_commit_url(namespace_id: note.project.namespace, + id: note.commit_id, + project_id: note.project, + host: Gitlab.config.gitlab['url'], + anchor: "note_#{note.id}") + elsif note.for_issue? + issue = Issue.find(note.noteable_id) + issue_url(issue, + host: Gitlab.config.gitlab['url'], + anchor: "note_#{note.id}") + elsif note.for_merge_request? + merge_request = MergeRequest.find(note.noteable_id) + merge_request_url(merge_request, + host: Gitlab.config.gitlab['url'], + anchor: "note_#{note.id}") + elsif note.for_project_snippet? + snippet = Snippet.find(note.noteable_id) + project_snippet_url(snippet, + host: Gitlab.config.gitlab['url'], + anchor: "note_#{note.id}") + end end end end diff --git a/lib/gitlab/utils.rb b/lib/gitlab/utils.rb new file mode 100644 index 0000000000..bd184c2718 --- /dev/null +++ b/lib/gitlab/utils.rb @@ -0,0 +1,13 @@ +module Gitlab + module Utils + extend self + + # Run system command without outputting to stdout. + # + # @param cmd [Array] + # @return [Boolean] + def system_silent(cmd) + Popen::popen(cmd).last.zero? + end + end +end diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb index ea1319268f..582fc759ef 100644 --- a/lib/gitlab/visibility_level.rb +++ b/lib/gitlab/visibility_level.rb @@ -5,6 +5,8 @@ # module Gitlab module VisibilityLevel + extend CurrentSettings + PRIVATE = 0 unless const_defined?(:PRIVATE) INTERNAL = 10 unless const_defined?(:INTERNAL) PUBLIC = 20 unless const_defined?(:PUBLIC) @@ -23,7 +25,27 @@ module Gitlab end def allowed_for?(user, level) - user.is_admin? || !Gitlab.config.gitlab.restricted_visibility_levels.include?(level) + user.is_admin? || allowed_level?(level.to_i) + end + + # Return true if the specified level is allowed for the current user. + # Level should be a numeric value, e.g. `20`. + def allowed_level?(level) + valid_level?(level) && non_restricted_level?(level) + end + + def non_restricted_level?(level) + restricted_levels = current_application_settings.restricted_visibility_levels + + if restricted_levels.nil? + true + else + !restricted_levels.include?(level) + end + end + + def valid_level?(level) + options.has_value?(level) end end diff --git a/lib/redcarpet/render/gitlab_html.rb b/lib/redcarpet/render/gitlab_html.rb index bb225f1acd..10efff2ae9 100644 --- a/lib/redcarpet/render/gitlab_html.rb +++ b/lib/redcarpet/render/gitlab_html.rb @@ -3,30 +3,47 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML attr_reader :template alias_method :h, :template - def initialize(template, options = {}) + def initialize(template, color_scheme, options = {}) @template = template + @color_scheme = color_scheme @project = @template.instance_variable_get("@project") @options = options.dup super options end + def preprocess(full_document) + # Redcarpet doesn't allow SMB links when `safe_links_only` is enabled. + # FTP links are allowed, so we trick Redcarpet. + full_document.gsub("smb://", "ftp://smb:") + end + + # If project has issue number 39, apostrophe will be linked in + # regular text to the issue as Redcarpet will convert apostrophe to + # #39; + # We replace apostrophe with right single quote before Redcarpet + # does the processing and put the apostrophe back in postprocessing. + # This only influences regular text, code blocks are untouched. + def normal_text(text) + return text unless text.present? + text.gsub("'", "’") + end + + # Stolen from Rugments::Plugins::Redcarpet as this module is not required + # from Rugments's gem root. def block_code(code, language) - # New lines are placed to fix an rendering issue - # with code wrapped inside

    tag for next case: - # - # # Title kinda h1 - # - # ruby code here - # - <<-HTML + lexer = Rugments::Lexer.find_fancy(language, code) || Rugments::Lexers::PlainText -
    -
    -
    #{h.send(:html_escape, code)}
    -
    -
    + # XXX HACK: Redcarpet strips hard tabs out of code blocks, + # so we assume you're not using leading spaces that aren't tabs, + # and just replace them here. + if lexer.tag == 'make' + code.gsub! /^ /, "\t" + end - HTML + formatter = Rugments::Formatters::HTML.new( + cssclass: "code highlight #{@color_scheme} #{lexer.tag}" + ) + formatter.format(lexer.lex(code)) end def link(link, title, content) @@ -44,9 +61,12 @@ class Redcarpet::Render::GitlabHTML < Redcarpet::Render::HTML end def postprocess(full_document) + full_document.gsub!("ftp://smb:", "smb://") + + full_document.gsub!("’", "'") unless @template.instance_variable_get("@project_wiki") || @project.nil? full_document = h.create_relative_links(full_document) end - h.gfm(full_document) + h.gfm_with_options(full_document, @options) end end diff --git a/lib/repository_cache.rb b/lib/repository_cache.rb new file mode 100644 index 0000000000..fa016a170c --- /dev/null +++ b/lib/repository_cache.rb @@ -0,0 +1,21 @@ +# Interface to the Redis-backed cache store used by the Repository model +class RepositoryCache + attr_reader :namespace, :backend + + def initialize(namespace, backend = Rails.cache) + @namespace = namespace + @backend = backend + end + + def cache_key(type) + "#{type}:#{namespace}" + end + + def expire(key) + backend.delete(cache_key(key)) + end + + def fetch(key, &block) + backend.fetch(cache_key(key), &block) + end +end diff --git a/lib/support/deploy/deploy.sh b/lib/support/deploy/deploy.sh index 4684957233..adea4c7a74 100755 --- a/lib/support/deploy/deploy.sh +++ b/lib/support/deploy/deploy.sh @@ -4,7 +4,7 @@ # If any command return non-zero status - stop deploy set -e -echo 'Deploy: Stoping sidekiq..' +echo 'Deploy: Stopping sidekiq..' cd /home/git/gitlab/ && sudo -u git -H bundle exec rake sidekiq:stop RAILS_ENV=production echo 'Deploy: Show deploy index page' diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab index 49306fb63d..62a4276536 100644 --- a/lib/support/nginx/gitlab +++ b/lib/support/nginx/gitlab @@ -1,69 +1,110 @@ -# GITLAB -# Maintainer: @randx - -# CHUNKED TRANSFER -# It is a known issue that Git-over-HTTP requires chunked transfer encoding [0] which is not -# supported by Nginx < 1.3.9 [1]. As a result, pushing a large object with Git (i.e. a single large file) -# can lead to a 411 error. In theory you can get around this by tweaking this configuration file and either -# - installing an old version of Nginx with the chunkin module [2] compiled in, or -# - using a newer version of Nginx. -# -# At the time of writing we do not know if either of these theoretical solutions works. As a workaround -# users can use Git over SSH to push large files. -# -# [0] https://git.kernel.org/cgit/git/git.git/tree/Documentation/technical/http-protocol.txt#n99 -# [1] https://github.com/agentzh/chunkin-nginx-module#status -# [2] https://github.com/agentzh/chunkin-nginx-module +## GitLab +## Contributors: randx, yin8086, sashkab, orkoden, axilleas, bbodenmiller, DouweM +## +## Lines starting with two hashes (##) are comments with information. +## Lines starting with one hash (#) are configuration parameters that can be uncommented. +## +################################## +## CHUNKED TRANSFER ## +################################## +## +## It is a known issue that Git-over-HTTP requires chunked transfer encoding [0] +## which is not supported by Nginx < 1.3.9 [1]. As a result, pushing a large object +## with Git (i.e. a single large file) can lead to a 411 error. In theory you can get +## around this by tweaking this configuration file and either: +## - installing an old version of Nginx with the chunkin module [2] compiled in, or +## - using a newer version of Nginx. +## +## At the time of writing we do not know if either of these theoretical solutions works. +## As a workaround users can use Git over SSH to push large files. +## +## [0] https://git.kernel.org/cgit/git/git.git/tree/Documentation/technical/http-protocol.txt#n99 +## [1] https://github.com/agentzh/chunkin-nginx-module#status +## [2] https://github.com/agentzh/chunkin-nginx-module +## +################################### +## configuration ## +################################### +## +## See installation.md#using-https for additional HTTPS configuration details. upstream gitlab { - server unix:/home/git/gitlab/tmp/sockets/gitlab.socket; + server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0; } +## Normal HTTP host server { - listen *:80 default_server; + listen 0.0.0.0:80 default_server; + listen [::]:80 default_server; server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com server_tokens off; ## Don't show the nginx version number, a security best practice root /home/git/gitlab/public; - - # Increase this if you want to upload large attachments - # Or if you want to accept large git objects over http + + ## Increase this if you want to upload large attachments + ## Or if you want to accept large git objects over http client_max_body_size 20m; - # individual nginx logs for this gitlab vhost + ## See app/controllers/application_controller.rb for headers set + + ## Individual nginx logs for this GitLab vhost access_log /var/log/nginx/gitlab_access.log; error_log /var/log/nginx/gitlab_error.log; location / { - # serve static files from defined root folder;. - # @gitlab is a named location for the upstream fallback, see below + ## Serve static files from defined root folder. + ## @gitlab is a named location for the upstream fallback, see below. try_files $uri $uri/index.html $uri.html @gitlab; } - # if a file, which is not found in the root folder is requested, - # then the proxy pass the request to the upsteam (gitlab unicorn) - location @gitlab { - # If you use https make sure you disable gzip compression - # to be safe against BREACH attack + ## We route uploads through GitLab to prevent XSS and enforce access control. + location /uploads/ { + ## If you use HTTPS make sure you disable gzip compression + ## to be safe against BREACH attack. # gzip off; - proxy_read_timeout 300; # Some requests take more than 30 seconds. - proxy_connect_timeout 300; # Some requests take more than 30 seconds. - proxy_redirect off; + ## https://github.com/gitlabhq/gitlabhq/issues/694 + ## Some requests take more than 30 seconds. + proxy_read_timeout 300; + proxy_connect_timeout 300; + proxy_redirect off; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header Host $http_host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Frame-Options SAMEORIGIN; + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Frame-Options SAMEORIGIN; proxy_pass http://gitlab; } - # Enable gzip compression as per rails guide: http://guides.rubyonrails.org/asset_pipeline.html#gzip-compression - # WARNING: If you are using relative urls do remove the block below - # See config/application.rb under "Relative url support" for the list of - # other files that need to be changed for relative url support - location ~ ^/(assets)/ { + ## If a file, which is not found in the root folder is requested, + ## then the proxy passes the request to the upsteam (gitlab unicorn). + location @gitlab { + ## If you use HTTPS make sure you disable gzip compression + ## to be safe against BREACH attack. + # gzip off; + + ## https://github.com/gitlabhq/gitlabhq/issues/694 + ## Some requests take more than 30 seconds. + proxy_read_timeout 300; + proxy_connect_timeout 300; + proxy_redirect off; + + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Frame-Options SAMEORIGIN; + + proxy_pass http://gitlab; + } + + ## Enable gzip compression as per rails guide: + ## http://guides.rubyonrails.org/asset_pipeline.html#gzip-compression + ## WARNING: If you are using relative urls remove the block below + ## See config/application.rb under "Relative url support" for the list of + ## other files that need to be changed for relative url support + location ~ ^/(assets)/ { root /home/git/gitlab/public; gzip_static on; # to serve pre-gzipped version expires max; diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl index 54a4a080a9..2aefc94469 100644 --- a/lib/support/nginx/gitlab-ssl +++ b/lib/support/nginx/gitlab-ssl @@ -1,13 +1,12 @@ ## GitLab -## Contributors: randx, yin8086, sashkab, orkoden, axilleas +## Contributors: randx, yin8086, sashkab, orkoden, axilleas, bbodenmiller, DouweM ## ## Modified from nginx http version ## Modified from http://blog.phusion.nl/2012/04/21/tutorial-setting-up-gitlab-on-debian-6/ ## Modified from https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html ## ## Lines starting with two hashes (##) are comments with information. -## Lines starting with one hash (#) are configuration parameters. -## The last category can be commented/uncommented to your liking. +## Lines starting with one hash (#) are configuration parameters that can be uncommented. ## ################################## ## CHUNKED TRANSFER ## @@ -20,48 +19,41 @@ ## - installing an old version of Nginx with the chunkin module [2] compiled in, or ## - using a newer version of Nginx. ## -## At the time of writing we do not know if either of these theoretical solutions works. As a workaround -## users can use Git over SSH to push large files. +## At the time of writing we do not know if either of these theoretical solutions works. +## As a workaround users can use Git over SSH to push large files. ## ## [0] https://git.kernel.org/cgit/git/git.git/tree/Documentation/technical/http-protocol.txt#n99 ## [1] https://github.com/agentzh/chunkin-nginx-module#status ## [2] https://github.com/agentzh/chunkin-nginx-module ## ################################### -## SSL file editing ## -################################### -## -## Edit `gitlab-shell/config.yml`: -## 1) Set "gitlab_url" param in `gitlab-shell/config.yml` to `https://git.example.com` -## 2) Set "ca_file" to `/etc/nginx/ssl/gitlab.crt` -## 3) Set "self_signed_cert" to `true` -## Edit `gitlab/config/gitlab.yml`: -## 1) Define port for http "port: 443" -## 2) Enable https "https: true" -## 3) Update ssl for gravatar "ssl_url: https://secure.gravatar.com/avatar/%{hash}?s=%{size}&d=mm" -## -################################### -## SSL configuration ## +## configuration ## ################################### ## +## See installation.md#using-https for additional HTTPS configuration details. upstream gitlab { - server unix:/home/git/gitlab/tmp/sockets/gitlab.socket; + server unix:/home/git/gitlab/tmp/sockets/gitlab.socket fail_timeout=0; } -## This is a normal HTTP host which redirects all traffic to the HTTPS host. +## Redirects all HTTP traffic to the HTTPS host server { - listen *:80 default_server; + listen 0.0.0.0:80; + listen [::]:80 ipv6only=on default_server; server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com server_tokens off; ## Don't show the nginx version number, a security best practice - root /nowhere; ## root doesn't have to be a valid path since we are redirecting - rewrite ^ https://$server_name$request_uri permanent; + return 301 https://$server_name$request_uri; + access_log /var/log/nginx/gitlab_access.log; + error_log /var/log/nginx/gitlab_error.log; } + +## HTTPS host server { - listen 443 ssl; + listen 0.0.0.0:443 ssl; + listen [::]:443 ipv6only=on ssl default_server; server_name YOUR_SERVER_FQDN; ## Replace this with something like gitlab.example.com - server_tokens off; + server_tokens off; ## Don't show the nginx version number, a security best practice root /home/git/gitlab/public; ## Increase this if you want to upload large attachments @@ -69,21 +61,35 @@ server { client_max_body_size 20m; ## Strong SSL Security - ## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html + ## https://raymii.org/s/tutorials/Strong_SSL_Security_On_nginx.html & https://cipherli.st/ ssl on; ssl_certificate /etc/nginx/ssl/gitlab.crt; ssl_certificate_key /etc/nginx/ssl/gitlab.key; - ssl_ciphers 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!CAMELLIA:!DES:!MD5:!PSK:!RC4'; + # GitLab needs backwards compatible ciphers to retain compatibility with Java IDEs + ssl_ciphers "ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA:ECDHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES128-SHA256:DHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES256-GCM-SHA384:AES128-GCM-SHA256:AES256-SHA256:AES128-SHA256:AES256-SHA:AES128-SHA:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4"; + ssl_protocols TLSv1 TLSv1.1 TLSv1.2; + ssl_prefer_server_ciphers on; + ssl_session_cache shared:SSL:10m; + ssl_session_timeout 5m; - ssl_protocols TLSv1 TLSv1.1 TLSv1.2; - ssl_session_cache builtin:1000 shared:SSL:10m; + ## See app/controllers/application_controller.rb for headers set - ssl_prefer_server_ciphers on; + ## [Optional] If your certficate has OCSP, enable OCSP stapling to reduce the overhead and latency of running SSL. + ## Replace with your ssl_trusted_certificate. For more info see: + ## - https://medium.com/devops-programming/4445f4862461 + ## - https://www.ruby-forum.com/topic/4419319 + ## - https://www.digitalocean.com/community/tutorials/how-to-configure-ocsp-stapling-on-apache-and-nginx + # ssl_stapling on; + # ssl_stapling_verify on; + # ssl_trusted_certificate /etc/nginx/ssl/stapling.trusted.crt; + # resolver 208.67.222.222 208.67.222.220 valid=300s; # Can change to your DNS resolver if desired + # resolver_timeout 5s; - add_header Strict-Transport-Security max-age=63072000; - add_header X-Frame-Options DENY; - add_header X-Content-Type-Options nosniff; + ## [Optional] Generate a stronger DHE parameter: + ## sudo openssl dhparam -out /etc/ssl/certs/dhparam.pem 4096 + ## + # ssl_dhparam /etc/ssl/certs/dhparam.pem; ## Individual nginx logs for this GitLab vhost access_log /var/log/nginx/gitlab_access.log; @@ -95,11 +101,32 @@ server { try_files $uri $uri/index.html $uri.html @gitlab; } - ## If a file, which is not found in the root folder is requested, - ## then the proxy pass the request to the upsteam (gitlab unicorn). - location @gitlab { + ## We route uploads through GitLab to prevent XSS and enforce access control. + location /uploads/ { + ## If you use HTTPS make sure you disable gzip compression + ## to be safe against BREACH attack. + gzip off; - ## If you use https make sure you disable gzip compression + ## https://github.com/gitlabhq/gitlabhq/issues/694 + ## Some requests take more than 30 seconds. + proxy_read_timeout 300; + proxy_connect_timeout 300; + proxy_redirect off; + + proxy_set_header Host $http_host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-Ssl on; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Frame-Options SAMEORIGIN; + + proxy_pass http://gitlab; + } + + ## If a file, which is not found in the root folder is requested, + ## then the proxy passes the request to the upsteam (gitlab unicorn). + location @gitlab { + ## If you use HTTPS make sure you disable gzip compression ## to be safe against BREACH attack. gzip off; @@ -121,7 +148,7 @@ server { ## Enable gzip compression as per rails guide: ## http://guides.rubyonrails.org/asset_pipeline.html#gzip-compression - ## WARNING: If you are using relative urls do remove the block below + ## WARNING: If you are using relative urls remove the block below ## See config/application.rb under "Relative url support" for the list of ## other files that need to be changed for relative url support location ~ ^/(assets)/ { diff --git a/lib/tasks/brakeman.rake b/lib/tasks/brakeman.rake new file mode 100644 index 0000000000..3a225801ff --- /dev/null +++ b/lib/tasks/brakeman.rake @@ -0,0 +1,9 @@ +desc 'Security check via brakeman' +task :brakeman do + if system("brakeman --skip-files lib/backup/repository.rb -w3 -z") + puts 'Security check succeed' + else + puts 'Security check failed' + exit 1 + end +end diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake index 2eff1260b6..84445b3bf2 100644 --- a/lib/tasks/gitlab/backup.rake +++ b/lib/tasks/gitlab/backup.rake @@ -6,6 +6,7 @@ namespace :gitlab do desc "GITLAB | Create a backup of the GitLab system" task create: :environment do warn_user_is_not_gitlab + configure_cron_mode Rake::Task["gitlab:backup:db:create"].invoke Rake::Task["gitlab:backup:repo:create"].invoke @@ -21,13 +22,14 @@ namespace :gitlab do desc "GITLAB | Restore a previously created backup" task restore: :environment do warn_user_is_not_gitlab + configure_cron_mode backup = Backup::Manager.new backup.unpack - Rake::Task["gitlab:backup:db:restore"].invoke - Rake::Task["gitlab:backup:repo:restore"].invoke - Rake::Task["gitlab:backup:uploads:restore"].invoke + Rake::Task["gitlab:backup:db:restore"].invoke unless backup.skipped?("db") + Rake::Task["gitlab:backup:repo:restore"].invoke unless backup.skipped?("repositories") + Rake::Task["gitlab:backup:uploads:restore"].invoke unless backup.skipped?("uploads") Rake::Task["gitlab:shell:setup"].invoke backup.cleanup @@ -35,43 +37,69 @@ namespace :gitlab do namespace :repo do task create: :environment do - puts "Dumping repositories ...".blue - Backup::Repository.new.dump - puts "done".green + $progress.puts "Dumping repositories ...".blue + + if ENV["SKIP"] && ENV["SKIP"].include?("repositories") + $progress.puts "[SKIPPED]".cyan + else + Backup::Repository.new.dump + $progress.puts "done".green + end end task restore: :environment do - puts "Restoring repositories ...".blue + $progress.puts "Restoring repositories ...".blue Backup::Repository.new.restore - puts "done".green + $progress.puts "done".green end end namespace :db do task create: :environment do - puts "Dumping database ... ".blue - Backup::Database.new.dump - puts "done".green + $progress.puts "Dumping database ... ".blue + + if ENV["SKIP"] && ENV["SKIP"].include?("db") + $progress.puts "[SKIPPED]".cyan + else + Backup::Database.new.dump + $progress.puts "done".green + end end task restore: :environment do - puts "Restoring database ... ".blue + $progress.puts "Restoring database ... ".blue Backup::Database.new.restore - puts "done".green + $progress.puts "done".green end end namespace :uploads do task create: :environment do - puts "Dumping uploads ... ".blue - Backup::Uploads.new.dump - puts "done".green + $progress.puts "Dumping uploads ... ".blue + + if ENV["SKIP"] && ENV["SKIP"].include?("uploads") + $progress.puts "[SKIPPED]".cyan + else + Backup::Uploads.new.dump + $progress.puts "done".green + end end task restore: :environment do - puts "Restoring uploads ... ".blue + $progress.puts "Restoring uploads ... ".blue Backup::Uploads.new.restore - puts "done".green + $progress.puts "done".green + end + end + + def configure_cron_mode + if ENV['CRON'] + # We need an object we can say 'puts' and 'print' to; let's use a + # StringIO. + require 'stringio' + $progress = StringIO.new + else + $progress = $stdout end end end # namespace end: backup diff --git a/lib/tasks/gitlab/bulk_add_permission.rake b/lib/tasks/gitlab/bulk_add_permission.rake index 0e1a3d071e..3d8c171dfa 100644 --- a/lib/tasks/gitlab/bulk_add_permission.rake +++ b/lib/tasks/gitlab/bulk_add_permission.rake @@ -7,10 +7,10 @@ namespace :gitlab do projects_ids = Project.pluck(:id) puts "Importing #{user_ids.size} users into #{projects_ids.size} projects" - UsersProject.add_users_into_projects(projects_ids, user_ids, UsersProject::DEVELOPER) + ProjectMember.add_users_into_projects(projects_ids, user_ids, ProjectMember::DEVELOPER) puts "Importing #{admin_ids.size} admins into #{projects_ids.size} projects" - UsersProject.add_users_into_projects(projects_ids, admin_ids, UsersProject::MASTER) + ProjectMember.add_users_into_projects(projects_ids, admin_ids, ProjectMember::MASTER) end desc "GITLAB | Add a specific user to all projects (as a developer)" @@ -18,7 +18,7 @@ namespace :gitlab do user = User.find_by(email: args.email) project_ids = Project.pluck(:id) puts "Importing #{user.email} users into #{project_ids.size} projects" - UsersProject.add_users_into_projects(project_ids, Array.wrap(user.id), UsersProject::DEVELOPER) + ProjectMember.add_users_into_projects(project_ids, Array.wrap(user.id), ProjectMember::DEVELOPER) end desc "GITLAB | Add all users to all groups (admin users are added as owners)" @@ -30,8 +30,8 @@ namespace :gitlab do puts "Importing #{user_ids.size} users into #{groups.size} groups" puts "Importing #{admin_ids.size} admins into #{groups.size} groups" groups.each do |group| - group.add_users(user_ids, UsersGroup::DEVELOPER) - group.add_users(admin_ids, UsersGroup::OWNER) + group.add_users(user_ids, GroupMember::DEVELOPER) + group.add_users(admin_ids, GroupMember::OWNER) end end @@ -41,7 +41,7 @@ namespace :gitlab do groups = Group.all puts "Importing #{user.email} users into #{groups.size} groups" groups.each do |group| - group.add_users(Array.wrap(user.id), UsersGroup::DEVELOPER) + group.add_users(Array.wrap(user.id), GroupMember::DEVELOPER) end end end diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake index 032ed5ee37..04a2eb12db 100644 --- a/lib/tasks/gitlab/check.rake +++ b/lib/tasks/gitlab/check.rake @@ -17,7 +17,7 @@ namespace :gitlab do check_database_config_exists check_database_is_not_sqlite check_migrations_are_up - check_orphaned_users_groups + check_orphaned_group_members check_gitlab_config_exists check_gitlab_config_not_outdated check_log_writable @@ -29,6 +29,7 @@ namespace :gitlab do check_redis_version check_ruby_version check_git_version + check_active_users finished_checking "GitLab" end @@ -194,13 +195,13 @@ namespace :gitlab do end end - def check_orphaned_users_groups - print "Database contains orphaned UsersGroups? ... " - if UsersGroup.where("user_id not in (select id from users)").count > 0 + def check_orphaned_group_members + print "Database contains orphaned GroupMembers? ... " + if GroupMember.where("user_id not in (select id from users)").count > 0 puts "yes".red try_fixing_it( "You can delete the orphaned records using something along the lines of:", - sudo_gitlab("bundle exec rails runner -e production 'UsersGroup.where(\"user_id NOT IN (SELECT id FROM users)\").delete_all'") + sudo_gitlab("bundle exec rails runner -e production 'GroupMember.where(\"user_id NOT IN (SELECT id FROM users)\").delete_all'") ) else puts "no".green @@ -322,22 +323,26 @@ namespace :gitlab do "core.autocrlf" => "input" } correct_options = options.map do |name, value| - run(%W(git config --global --get #{name})).try(:squish) == value + run(%W(#{Gitlab.config.git.bin_path} config --global --get #{name})).try(:squish) == value end if correct_options.all? puts "yes".green else - puts "no".red - try_fixing_it( - sudo_gitlab("git config --global user.name \"#{options["user.name"]}\""), - sudo_gitlab("git config --global user.email \"#{options["user.email"]}\""), - sudo_gitlab("git config --global core.autocrlf \"#{options["core.autocrlf"]}\"") - ) - for_more_information( - see_installation_guide_section "GitLab" - ) - fix_and_rerun + print "Trying to fix Git error automatically. ..." + if auto_fix_git_config(options) + puts "Success".green + else + puts "Failed".red + try_fixing_it( + sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global user.name \"#{options["user.name"]}\""), + sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global user.email \"#{options["user.email"]}\""), + sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global core.autocrlf \"#{options["core.autocrlf"]}\"") + ) + for_more_information( + see_installation_guide_section "GitLab" + ) + end end end end @@ -356,8 +361,7 @@ namespace :gitlab do check_repo_base_user_and_group check_repo_base_permissions check_satellites_permissions - check_update_hook_is_up_to_date - check_repos_update_hooks_is_link + check_repos_hooks_directory_is_link check_gitlab_shell_self_test finished_checking "GitLab Shell" @@ -367,29 +371,6 @@ namespace :gitlab do # Checks ######################## - - def check_update_hook_is_up_to_date - print "update hook up-to-date? ... " - - hook_file = "update" - gitlab_shell_hooks_path = Gitlab.config.gitlab_shell.hooks_path - gitlab_shell_hook_file = File.join(gitlab_shell_hooks_path, hook_file) - - if File.exists?(gitlab_shell_hook_file) - puts "yes".green - else - puts "no".red - puts "Could not find #{gitlab_shell_hook_file}" - try_fixing_it( - 'Check the hooks_path in config/gitlab.yml', - 'Check your gitlab-shell installation' - ) - for_more_information( - see_installation_guide_section "GitLab Shell" - ) - end - end - def check_repo_base_exists print "Repo base directory exists? ... " @@ -508,18 +489,10 @@ namespace :gitlab do end end - def check_repos_update_hooks_is_link - print "update hooks in repos are links: ... " + def check_repos_hooks_directory_is_link + print "hooks directories in repos are links: ... " - hook_file = "update" gitlab_shell_hooks_path = Gitlab.config.gitlab_shell.hooks_path - gitlab_shell_hook_file = File.join(gitlab_shell_hooks_path, hook_file) - gitlab_shell_ssh_user = Gitlab.config.gitlab_shell.ssh_user - - unless File.exists?(gitlab_shell_hook_file) - puts "can't check because of previous errors".magenta - return - end unless Project.count > 0 puts "can't check, you have no projects".magenta @@ -529,38 +502,25 @@ namespace :gitlab do Project.find_each(batch_size: 100) do |project| print sanitized_message(project) + project_hook_directory = File.join(project.repository.path_to_repo, "hooks") if project.empty_repo? puts "repository is empty".magenta + elsif File.realpath(project_hook_directory) == File.realpath(gitlab_shell_hooks_path) + puts 'ok'.green else - project_hook_file = File.join(project.repository.path_to_repo, "hooks", hook_file) - - unless File.exists?(project_hook_file) - puts "missing".red - try_fixing_it( - "sudo -u #{gitlab_shell_ssh_user} ln -sf #{gitlab_shell_hook_file} #{project_hook_file}" - ) - for_more_information( - "#{gitlab_shell_path}/support/rewrite-hooks.sh" - ) - fix_and_rerun - next - end - - if File.lstat(project_hook_file).symlink? && - File.realpath(project_hook_file) == File.realpath(gitlab_shell_hook_file) - puts "ok".green - else - puts "not a link to GitLab Shell's hook".red - try_fixing_it( - "sudo -u #{gitlab_shell_ssh_user} ln -sf #{gitlab_shell_hook_file} #{project_hook_file}" - ) - for_more_information( - "lib/support/rewrite-hooks.sh" - ) - fix_and_rerun - end + puts "wrong or missing hooks".red + try_fixing_it( + sudo_gitlab("#{gitlab_shell_path}/bin/create-hooks"), + 'Check the hooks_path in config/gitlab.yml', + 'Check your gitlab-shell installation' + ) + for_more_information( + see_installation_guide_section "GitLab Shell" + ) + fix_and_rerun end + end end @@ -619,24 +579,16 @@ namespace :gitlab do Gitlab::Shell.new.version end - def required_gitlab_shell_version - File.read(File.join(Rails.root, "GITLAB_SHELL_VERSION")).strip - end - def gitlab_shell_major_version - required_gitlab_shell_version.split(".")[0].to_i + Gitlab::Shell.version_required.split('.')[0].to_i end def gitlab_shell_minor_version - required_gitlab_shell_version.split(".")[1].to_i + Gitlab::Shell.version_required.split('.')[1].to_i end def gitlab_shell_patch_version - required_gitlab_shell_version.split(".")[2].to_i - end - - def has_gitlab_shell3? - gitlab_shell_version.try(:start_with?, "v3.") + Gitlab::Shell.version_required.split('.')[2].to_i end end @@ -709,7 +661,7 @@ namespace :gitlab do warn_user_is_not_gitlab start_checking "LDAP" - if ldap_config.enabled + if Gitlab::LDAP::Config.enabled? print_users(args.limit) else puts 'LDAP is disabled in config/gitlab.yml' @@ -720,39 +672,36 @@ namespace :gitlab do def print_users(limit) puts "LDAP users with access to your GitLab server (only showing the first #{limit} results)" - ldap.search(attributes: attributes, filter: filter, size: limit, return_result: false) do |entry| - puts "DN: #{entry.dn}\t#{ldap_config.uid}: #{entry[ldap_config.uid]}" + + servers = Gitlab::LDAP::Config.providers + + servers.each do |server| + puts "Server: #{server}" + Gitlab::LDAP::Adapter.open(server) do |adapter| + users = adapter.users(adapter.config.uid, '*', 100) + users.each do |user| + puts "\tDN: #{user.dn}\t #{adapter.config.uid}: #{user.uid}" + end + end end end + end - def attributes - [ldap_config.uid] - end + namespace :repo do + desc "GITLAB | Check the integrity of the repositories managed by GitLab" + task check: :environment do + namespace_dirs = Dir.glob( + File.join(Gitlab.config.gitlab_shell.repos_path, '*') + ) - def filter - uid_filter = Net::LDAP::Filter.present?(ldap_config.uid) - if user_filter - Net::LDAP::Filter.join(uid_filter, user_filter) - else - uid_filter + namespace_dirs.each do |namespace_dir| + repo_dirs = Dir.glob(File.join(namespace_dir, '*')) + repo_dirs.each do |dir| + puts "\nChecking repo at #{dir}" + system(*%w(git fsck), chdir: dir) + end end end - - def user_filter - if ldap_config['user_filter'] && ldap_config.user_filter.present? - Net::LDAP::Filter.construct(ldap_config.user_filter) - else - nil - end - end - - def ldap - @ldap ||= OmniAuth::LDAP::Adaptor.new(ldap_config).connection - end - - def ldap_config - @ldap_config ||= Gitlab.config.ldap - end end # Helper methods @@ -854,19 +803,23 @@ namespace :gitlab do end end + def check_active_users + puts "Active users: #{User.active.count}" + end + def omnibus_gitlab? Dir.pwd == '/opt/gitlab/embedded/service/gitlab-rails' end def sanitized_message(project) - if sanitize + if should_sanitize? "#{project.namespace_id.to_s.yellow}/#{project.id.to_s.yellow} ... " else "#{project.name_with_namespace.yellow} ... " end end - def sanitize + def should_sanitize? if ENV['SANITIZE'] == "true" true else @@ -874,3 +827,4 @@ namespace :gitlab do end end end + diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake index 63dcdc5237..3c9802a0be 100644 --- a/lib/tasks/gitlab/cleanup.rake +++ b/lib/tasks/gitlab/cleanup.rake @@ -90,13 +90,14 @@ namespace :gitlab do warn_user_is_not_gitlab block_flag = ENV['BLOCK'] - User.ldap.each do |ldap_user| - print "#{ldap_user.name} (#{ldap_user.extern_uid}) ..." - if Gitlab::LDAP::Access.open { |access| access.allowed?(ldap_user) } + User.find_each do |user| + next unless user.ldap_user? + print "#{user.name} (#{user.ldap_identity.extern_uid}) ..." + if Gitlab::LDAP::Access.allowed?(user) puts " [OK]".green else if block_flag - ldap_user.block! + user.block! unless user.blocked? puts " [BLOCKED]".red else puts " [NOT IN LDAP]".yellow diff --git a/lib/tasks/gitlab/db/drop_all_postgres_sequences.rake b/lib/tasks/gitlab/db/drop_all_postgres_sequences.rake new file mode 100644 index 0000000000..e9cf0a9b5e --- /dev/null +++ b/lib/tasks/gitlab/db/drop_all_postgres_sequences.rake @@ -0,0 +1,10 @@ +namespace :gitlab do + namespace :db do + task drop_all_postgres_sequences: :environment do + connection = ActiveRecord::Base.connection + connection.execute("SELECT c.relname FROM pg_class c WHERE c.relkind = 'S';").each do |sequence| + connection.execute("DROP SEQUENCE #{sequence['relname']}") + end + end + end +end diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake index cbfa736c84..20abb2fa50 100644 --- a/lib/tasks/gitlab/import.rake +++ b/lib/tasks/gitlab/import.rake @@ -15,23 +15,17 @@ namespace :gitlab do git_base_path = Gitlab.config.gitlab_shell.repos_path repos_to_import = Dir.glob(git_base_path + '/**/*.git') - namespaces = Namespace.pluck(:path) - repos_to_import.each do |repo_path| # strip repo base path repo_path[0..git_base_path.length] = '' path = repo_path.sub(/\.git$/, '') - name = File.basename path - group_name = File.dirname path + group_name, name = File.split(path) group_name = nil if group_name == '.' - # Skip if group or user - next if namespaces.include?(name) - puts "Processing #{repo_path}".yellow - if path =~ /.wiki\Z/ + if path.end_with?('.wiki') puts " * Skipping wiki repo" next end @@ -50,9 +44,9 @@ namespace :gitlab do # find group namespace if group_name - group = Group.find_by(path: group_name) + group = Namespace.find_by(path: group_name) # create group namespace - if !group + unless group group = Group.new(:name => group_name) group.path = group_name group.owner = user @@ -72,6 +66,7 @@ namespace :gitlab do puts " * Created #{project.name} (#{repo_path})".green else puts " * Failed trying to create #{project.name} (#{repo_path})".red + puts " Validation Errors: #{project.errors.messages}".red end end end diff --git a/lib/tasks/gitlab/mail_google_schema_whitelisting.rake b/lib/tasks/gitlab/mail_google_schema_whitelisting.rake new file mode 100644 index 0000000000..102c6ae55d --- /dev/null +++ b/lib/tasks/gitlab/mail_google_schema_whitelisting.rake @@ -0,0 +1,73 @@ +require "#{Rails.root}/app/helpers/emails_helper" +require 'action_view/helpers' +extend ActionView::Helpers + +include ActionView::Context +include EmailsHelper + +namespace :gitlab do + desc "Email google whitelisting email with example email for actions in inbox" + task mail_google_schema_whitelisting: :environment do + subject = "Rails | Implemented feature" + url = "#{Gitlab.config.gitlab.url}/base/rails-project/issues/#{rand(1..100)}#note_#{rand(10..1000)}" + schema = email_action(url) + body = email_template(schema, url) + mail = Notify.test_email("schema.whitelisting+sample@gmail.com", subject, body.html_safe) + if send_now + mail.deliver + else + puts "WOULD SEND:" + end + puts mail + end + + def email_template(schema, url) + " + + + + GitLab + + + + + +
    +
    +

    I like it :+1:

    +
    +
    + +
    + + " + end + + def send_now + if ENV['SEND'] == "true" + true + else + false + end + end +end diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake index ff27e6a306..e835d6cb9b 100644 --- a/lib/tasks/gitlab/shell.rake +++ b/lib/tasks/gitlab/shell.rake @@ -4,27 +4,32 @@ namespace :gitlab do task :install, [:tag, :repo] => :environment do |t, args| warn_user_is_not_gitlab - default_version = File.read(File.join(Rails.root, "GITLAB_SHELL_VERSION")).strip + default_version = Gitlab::Shell.version_required args.with_defaults(tag: 'v' + default_version, repo: "https://gitlab.com/gitlab-org/gitlab-shell.git") - user = Settings.gitlab.user - home_dir = Settings.gitlab.user_home - gitlab_url = Settings.gitlab.url + user = Gitlab.config.gitlab.user + home_dir = Rails.env.test? ? Rails.root.join('tmp/tests') : Gitlab.config.gitlab.user_home + gitlab_url = Gitlab.config.gitlab.url # gitlab-shell requires a / at the end of the url - gitlab_url += "/" unless gitlab_url.match(/\/$/) + gitlab_url += '/' unless gitlab_url.end_with?('/') repos_path = Gitlab.config.gitlab_shell.repos_path target_dir = Gitlab.config.gitlab_shell.path # Clone if needed unless File.directory?(target_dir) - sh "git clone '#{args.repo}' '#{target_dir}'" + system(*%W(git clone -- #{args.repo} #{target_dir})) end # Make sure we're on the right tag Dir.chdir(target_dir) do - sh "git fetch origin && git reset --hard $(git describe #{args.tag} || git describe origin/#{args.tag})" + # First try to checkout without fetching + # to avoid stalling tests if the Internet is down. + reseted = reset_to_commit(args) - redis_url = URI.parse(ENV['REDIS_URL'] || "redis://localhost:6379") + unless reseted + system(*%W(git fetch origin)) + reset_to_commit(args) + end config = { user: user, @@ -34,19 +39,26 @@ namespace :gitlab do auth_file: File.join(home_dir, ".ssh", "authorized_keys"), redis: { bin: %x{which redis-cli}.chomp, - host: redis_url.host, - port: redis_url.port, namespace: "resque:gitlab" }.stringify_keys, log_level: "INFO", audit_usernames: false }.stringify_keys + redis_url = URI.parse(ENV['REDIS_URL'] || "redis://localhost:6379") + + if redis_url.scheme == 'unix' + config['redis']['socket'] = redis_url.path + else + config['redis']['host'] = redis_url.host + config['redis']['port'] = redis_url.port + end + # Generate config.yml based on existing gitlab settings File.open("config.yml", "w+") {|f| f.puts config.to_yaml} # Launch installation process - sh "bin/install" + system(*%W(bin/install)) end # Required for debian packaging with PKGR: Setup .ssh/environment with @@ -68,7 +80,7 @@ namespace :gitlab do desc "GITLAB | Build missing projects" task build_missing_projects: :environment do Project.find_each(batch_size: 1000) do |project| - path_to_repo = File.join(Gitlab.config.gitlab_shell.repos_path, "#{project.path_with_namespace}.git") + path_to_repo = project.repository.path_to_repo if File.exists?(path_to_repo) print '-' else @@ -100,6 +112,7 @@ namespace :gitlab do print '.' end end + puts "" unless $?.success? puts "Failed to add keys...".red @@ -110,5 +123,16 @@ namespace :gitlab do puts "Quitting...".red exit 1 end + + def reset_to_commit(args) + tag, status = Gitlab::Popen.popen(%W(git describe -- #{args.tag})) + + unless status.zero? + tag, status = Gitlab::Popen.popen(%W(git describe -- origin/#{args.tag})) + end + + tag = tag.strip + system(*%W(git reset --hard #{tag})) + end end diff --git a/lib/tasks/gitlab/sidekiq.rake b/lib/tasks/gitlab/sidekiq.rake new file mode 100644 index 0000000000..7e2a6668e5 --- /dev/null +++ b/lib/tasks/gitlab/sidekiq.rake @@ -0,0 +1,47 @@ +namespace :gitlab do + namespace :sidekiq do + QUEUE = 'queue:post_receive' + + desc 'Drop all Sidekiq PostReceive jobs for a given project' + task :drop_post_receive , [:project] => :environment do |t, args| + unless args.project.present? + abort "Please specify the project you want to drop PostReceive jobs for:\n rake gitlab:sidekiq:drop_post_receive[group/project]" + end + project_path = Project.find_with_namespace(args.project).repository.path_to_repo + + Sidekiq.redis do |redis| + unless redis.exists(QUEUE) + abort "Queue #{QUEUE} is empty" + end + + temp_queue = "#{QUEUE}_#{Time.now.to_i}" + redis.rename(QUEUE, temp_queue) + + # At this point, then post_receive queue is empty. It may be receiving + # new jobs already. We will repopulate it with the old jobs, skipping the + # ones we want to drop. + dropped = 0 + while (job = redis.lpop(temp_queue)) do + if repo_path(job) == project_path + dropped += 1 + else + redis.rpush(QUEUE, job) + end + end + # The temp_queue will delete itself after we have popped all elements + # from it + + puts "Dropped #{dropped} jobs containing #{project_path} from #{QUEUE}" + end + end + + def repo_path(job) + job_args = JSON.parse(job)['args'] + if job_args + job_args.first + else + nil + end + end + end +end diff --git a/lib/tasks/gitlab/task_helpers.rake b/lib/tasks/gitlab/task_helpers.rake index da61c6e007..14a130be2c 100644 --- a/lib/tasks/gitlab/task_helpers.rake +++ b/lib/tasks/gitlab/task_helpers.rake @@ -112,4 +112,20 @@ namespace :gitlab do @warned_user_not_gitlab = true end end + + # Tries to configure git itself + # + # Returns true if all subcommands were successfull (according to their exit code) + # Returns false if any or all subcommands failed. + def auto_fix_git_config(options) + if !@warned_user_not_gitlab && options['user.email'] != 'example@example.com' # default email should be overridden? + command_success = options.map do |name, value| + system(%W(#{Gitlab.config.git.bin_path} config --global #{name} #{value})) + end + + command_success.all? + else + false + end + end end diff --git a/lib/tasks/gitlab/test.rake b/lib/tasks/gitlab/test.rake index c01b00bd1c..b4c0ae3ff7 100644 --- a/lib/tasks/gitlab/test.rake +++ b/lib/tasks/gitlab/test.rake @@ -2,6 +2,8 @@ namespace :gitlab do desc "GITLAB | Run all tests" task :test do cmds = [ + %W(rake brakeman), + %W(rake rubocop), %W(rake spinach), %W(rake spec), %W(rake jasmine:ci) diff --git a/lib/tasks/rubocop.rake b/lib/tasks/rubocop.rake new file mode 100644 index 0000000000..ddfaf5d51f --- /dev/null +++ b/lib/tasks/rubocop.rake @@ -0,0 +1,4 @@ +unless Rails.env.production? + require 'rubocop/rake_task' + RuboCop::RakeTask.new +end diff --git a/lib/tasks/spinach.rake b/lib/tasks/spinach.rake index 507b315759..4aefc18ce1 100644 --- a/lib/tasks/spinach.rake +++ b/lib/tasks/spinach.rake @@ -2,9 +2,15 @@ Rake::Task["spinach"].clear if Rake::Task.task_defined?('spinach') desc "GITLAB | Run spinach" task :spinach do + tags = if ENV['SEMAPHORE'] + '~@tricky' + else + '~@semaphore' + end + cmds = [ %W(rake gitlab:setup), - %W(spinach), + %W(spinach --tags #{tags}), ] run_commands(cmds) end diff --git a/lib/tasks/test.rake b/lib/tasks/test.rake index f19da1bb43..a39d964987 100644 --- a/lib/tasks/test.rake +++ b/lib/tasks/test.rake @@ -4,3 +4,10 @@ desc "GITLAB | Run all tests" task :test do Rake::Task["gitlab:test"].invoke end + +unless Rails.env.production? + require 'coveralls/rake/task' + Coveralls::RakeTask.new + desc "GITLAB | Run all tests on CI with simplecov" + task :test_ci => [:rubocop, :brakeman, 'jasmine:ci', :spinach, :spec, 'coveralls:push'] +end diff --git a/safe/public.pem b/safe/public.pem new file mode 100644 index 0000000000..c5ffe20a5c --- /dev/null +++ b/safe/public.pem @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnp2mUaLBoHFX127ysonX +OihiGpI4098eFfH1iAxpKHIof0vs0jFF05IUScNXJZ1U3w8G1U/unY/wGGa3NzAb +ZfDd22eOF6X2Gfiey6U4w9dFf0/UT5x1bphlpX357yh4O9oWWuNaWD062DTbOOsJ +U6UW2U/sZAu/QScys0Nw+gJ58t93hb4jFq+nO5IAQc6g4S8ek5YvIXOshFEpF2in +ZLbSYowx92+9GzfjvdQ7fk0Q2ssg0zfScVa6FY8n019osz0SC3wcSd/qicdfecpu +7oycpd9YDqk4lufE1qVMOsgE8OO4KXMrByz2f+T0p/bH9zdBa5HYylf1T7i60hIL +kQIDAQAB +-----END PUBLIC KEY----- diff --git a/spec/controllers/application_controller_spec.rb b/spec/controllers/application_controller_spec.rb index cc32805f5e..186239d309 100644 --- a/spec/controllers/application_controller_spec.rb +++ b/spec/controllers/application_controller_spec.rb @@ -7,26 +7,26 @@ describe ApplicationController do it 'should redirect if the user is over their password expiry' do user.password_expires_at = Time.new(2002) - user.ldap_user?.should be_false - controller.stub(:current_user).and_return(user) - controller.should_receive(:redirect_to) - controller.should_receive(:new_profile_password_path) + expect(user.ldap_user?).to be_falsey + allow(controller).to receive(:current_user).and_return(user) + expect(controller).to receive(:redirect_to) + expect(controller).to receive(:new_profile_password_path) controller.send(:check_password_expiration) end it 'should not redirect if the user is under their password expiry' do user.password_expires_at = Time.now + 20010101 - user.ldap_user?.should be_false - controller.stub(:current_user).and_return(user) - controller.should_not_receive(:redirect_to) + expect(user.ldap_user?).to be_falsey + allow(controller).to receive(:current_user).and_return(user) + expect(controller).not_to receive(:redirect_to) controller.send(:check_password_expiration) end it 'should not redirect if the user is over their password expiry but they are an ldap user' do user.password_expires_at = Time.new(2002) - user.stub(:ldap_user?).and_return(true) - controller.stub(:current_user).and_return(user) - controller.should_not_receive(:redirect_to) + allow(user).to receive(:ldap_user?).and_return(true) + allow(controller).to receive(:current_user).and_return(user) + expect(controller).not_to receive(:redirect_to) controller.send(:check_password_expiration) end end diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb new file mode 100644 index 0000000000..a0909cec3b --- /dev/null +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe AutocompleteController do + let!(:project) { create(:project) } + let!(:user) { create(:user) } + let!(:user2) { create(:user) } + + context 'project members' do + before do + sign_in(user) + project.team << [user, :master] + + get(:users, project_id: project.id) + end + + let(:body) { JSON.parse(response.body) } + + it { body.should be_kind_of(Array) } + it { body.size.should eq(1) } + it { body.first["username"].should == user.username } + end + + context 'group members' do + let(:group) { create(:group) } + + before do + sign_in(user) + group.add_owner(user) + + get(:users, group_id: group.id) + end + + let(:body) { JSON.parse(response.body) } + + it { body.should be_kind_of(Array) } + it { body.size.should eq(1) } + it { body.first["username"].should == user.username } + end + + context 'all users' do + before do + sign_in(user) + get(:users) + end + + let(:body) { JSON.parse(response.body) } + + it { body.should be_kind_of(Array) } + it { body.size.should eq(User.count) } + end +end diff --git a/spec/controllers/blob_controller_spec.rb b/spec/controllers/blob_controller_spec.rb index 11d748ca77..a1102f2834 100644 --- a/spec/controllers/blob_controller_spec.rb +++ b/spec/controllers/blob_controller_spec.rb @@ -9,29 +9,32 @@ describe Projects::BlobController do project.team << [user, :master] - project.stub(:branches).and_return(['master', 'foo/bar/baz']) - project.stub(:tags).and_return(['v1.0.0', 'v2.0.0']) + allow(project).to receive(:branches).and_return(['master', 'foo/bar/baz']) + allow(project).to receive(:tags).and_return(['v1.0.0', 'v2.0.0']) controller.instance_variable_set(:@project, project) end describe "GET show" do render_views - before { get :show, project_id: project.to_param, id: id } + before do + get(:show, namespace_id: project.namespace.to_param, + project_id: project.to_param, id: id) + end context "valid branch, valid file" do let(:id) { 'master/README.md' } - it { should respond_with(:success) } + it { is_expected.to respond_with(:success) } end context "valid branch, invalid file" do let(:id) { 'master/invalid-path.rb' } - it { should respond_with(:not_found) } + it { is_expected.to respond_with(:not_found) } end context "invalid branch, valid file" do let(:id) { 'invalid-branch/README.md' } - it { should respond_with(:not_found) } + it { is_expected.to respond_with(:not_found) } end end @@ -39,13 +42,17 @@ describe Projects::BlobController do render_views before do - get :show, project_id: project.to_param, id: id + get(:show, namespace_id: project.namespace.to_param, + project_id: project.to_param, id: id) controller.instance_variable_set(:@blob, nil) end context 'redirect to tree' do let(:id) { 'markdown/doc' } - it { should redirect_to("/#{project.path_with_namespace}/tree/markdown/doc") } + it 'redirects' do + expect(subject). + to redirect_to("/#{project.path_with_namespace}/tree/markdown/doc") + end end end end diff --git a/spec/controllers/branches_controller_spec.rb b/spec/controllers/branches_controller_spec.rb new file mode 100644 index 0000000000..51397382cf --- /dev/null +++ b/spec/controllers/branches_controller_spec.rb @@ -0,0 +1,58 @@ +require 'spec_helper' + +describe Projects::BranchesController do + let(:project) { create(:project) } + let(:user) { create(:user) } + + before do + sign_in(user) + + project.team << [user, :master] + + allow(project).to receive(:branches).and_return(['master', 'foo/bar/baz']) + allow(project).to receive(:tags).and_return(['v1.0.0', 'v2.0.0']) + controller.instance_variable_set(:@project, project) + end + + describe "POST create" do + render_views + + before { + post :create, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + branch_name: branch, + ref: ref + } + + context "valid branch name, valid source" do + let(:branch) { "merge_branch" } + let(:ref) { "master" } + it 'redirects' do + expect(subject). + to redirect_to("/#{project.path_with_namespace}/tree/merge_branch") + end + end + + context "invalid branch name, valid ref" do + let(:branch) { "" } + let(:ref) { "master" } + it 'redirects' do + expect(subject). + to redirect_to("/#{project.path_with_namespace}/tree/alert('merge');") + end + end + + context "valid branch name, invalid ref" do + let(:branch) { "merge_branch" } + let(:ref) { "" } + it { is_expected.to render_template('new') } + end + + context "invalid branch name, invalid ref" do + let(:branch) { "" } + let(:ref) { "" } + it { is_expected.to render_template('new') } + end + end +end diff --git a/spec/controllers/commit_controller_spec.rb b/spec/controllers/commit_controller_spec.rb index f5822157ea..3394a1f863 100644 --- a/spec/controllers/commit_controller_spec.rb +++ b/spec/controllers/commit_controller_spec.rb @@ -13,27 +13,32 @@ describe Projects::CommitController do describe "#show" do shared_examples "export as" do |format| it "should generally work" do - get :show, project_id: project.to_param, id: commit.id, format: format + get(:show, namespace_id: project.namespace.to_param, + project_id: project.to_param, id: commit.id, format: format) expect(response).to be_success end it "should generate it" do - Commit.any_instance.should_receive(:"to_#{format}") + expect_any_instance_of(Commit).to receive(:"to_#{format}") - get :show, project_id: project.to_param, id: commit.id, format: format + get(:show, namespace_id: project.namespace.to_param, + project_id: project.to_param, id: commit.id, format: format) end it "should render it" do - get :show, project_id: project.to_param, id: commit.id, format: format + get(:show, namespace_id: project.namespace.to_param, + project_id: project.to_param, id: commit.id, format: format) expect(response.body).to eq(commit.send(:"to_#{format}")) end it "should not escape Html" do - Commit.any_instance.stub(:"to_#{format}").and_return('HTML entities &<>" ') + allow_any_instance_of(Commit).to receive(:"to_#{format}"). + and_return('HTML entities &<>" ') - get :show, project_id: project.to_param, id: commit.id, format: format + get(:show, namespace_id: project.namespace.to_param, + project_id: project.to_param, id: commit.id, format: format) expect(response.body).to_not include('&') expect(response.body).to_not include('>') @@ -47,7 +52,8 @@ describe Projects::CommitController do let(:format) { :diff } it "should really only be a git diff" do - get :show, project_id: project.to_param, id: commit.id, format: format + get(:show, namespace_id: project.namespace.to_param, + project_id: project.to_param, id: commit.id, format: format) expect(response.body).to start_with("diff --git") end @@ -58,16 +64,28 @@ describe Projects::CommitController do let(:format) { :patch } it "should really be a git email patch" do - get :show, project_id: project.to_param, id: commit.id, format: format + get(:show, namespace_id: project.namespace.to_param, + project_id: project.to_param, id: commit.id, format: format) expect(response.body).to start_with("From #{commit.id}") end it "should contain a git diff" do - get :show, project_id: project.to_param, id: commit.id, format: format + get(:show, namespace_id: project.namespace.to_param, + project_id: project.to_param, id: commit.id, format: format) expect(response.body).to match(/^diff --git/) end end end + + describe "#branches" do + it "contains branch and tags information" do + get(:branches, namespace_id: project.namespace.to_param, + project_id: project.to_param, id: commit.id) + + expect(assigns(:branches)).to include("master", "feature_conflict") + expect(assigns(:tags)).to include("v1.1.0") + end + end end diff --git a/spec/controllers/commits_controller_spec.rb b/spec/controllers/commits_controller_spec.rb index 0c19d755eb..2184b35152 100644 --- a/spec/controllers/commits_controller_spec.rb +++ b/spec/controllers/commits_controller_spec.rb @@ -12,9 +12,10 @@ describe Projects::CommitsController do describe "GET show" do context "as atom feed" do it "should render as atom" do - get :show, project_id: project.to_param, id: "master", format: "atom" - response.should be_success - response.content_type.should == 'application/atom+xml' + get(:show, namespace_id: project.namespace.to_param, + project_id: project.to_param, id: "master", format: "atom") + expect(response).to be_success + expect(response.content_type).to eq('application/atom+xml') end end end diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb new file mode 100644 index 0000000000..93535ced7a --- /dev/null +++ b/spec/controllers/help_controller_spec.rb @@ -0,0 +1,61 @@ +require 'spec_helper' + +describe HelpController do + let(:user) { create(:user) } + + before do + sign_in(user) + end + + describe 'GET #show' do + context 'for Markdown formats' do + context 'when requested file exists' do + before do + get :show, category: 'ssh', file: 'README', format: :md + end + + it 'assigns to @markdown' do + expect(assigns[:markdown]).not_to be_empty + end + + it 'renders HTML' do + expect(response).to render_template('show.html.haml') + expect(response.content_type).to eq 'text/html' + end + end + + context 'when requested file is missing' do + it 'renders not found' do + get :show, category: 'foo', file: 'bar', format: :md + expect(response).to be_not_found + end + end + end + + context 'for image formats' do + context 'when requested file exists' do + it 'renders the raw file' do + get :show, category: 'workflow/protected_branches', + file: 'protected_branches1', format: :png + expect(response).to be_success + expect(response.content_type).to eq 'image/png' + expect(response.headers['Content-Disposition']).to match(/^inline;/) + end + end + + context 'when requested file is missing' do + it 'renders not found' do + get :show, category: 'foo', file: 'bar', format: :png + expect(response).to be_not_found + end + end + end + + context 'for other formats' do + it 'always renders not found' do + get :show, category: 'ssh', file: 'README', format: :foo + expect(response).to be_not_found + end + end + end +end diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb new file mode 100644 index 0000000000..c31563e6d7 --- /dev/null +++ b/spec/controllers/import/bitbucket_controller_spec.rb @@ -0,0 +1,163 @@ +require 'spec_helper' + +describe Import::BitbucketController do + let(:user) { create(:user, bitbucket_access_token: 'asd123', bitbucket_access_token_secret: "sekret") } + + before do + sign_in(user) + controller.stub(:bitbucket_import_enabled?).and_return(true) + end + + describe "GET callback" do + before do + session[:oauth_request_token] = {} + end + + it "updates access token" do + token = "asdasd12345" + secret = "sekrettt" + access_token = double(token: token, secret: secret) + Gitlab::BitbucketImport::Client.any_instance.stub(:get_token).and_return(access_token) + Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "bitbucket") + + get :callback + + expect(user.reload.bitbucket_access_token).to eq(token) + expect(user.reload.bitbucket_access_token_secret).to eq(secret) + expect(controller).to redirect_to(status_import_bitbucket_url) + end + end + + describe "GET status" do + before do + @repo = OpenStruct.new(slug: 'vim', owner: 'asd') + end + + it "assigns variables" do + @project = create(:project, import_type: 'bitbucket', creator_id: user.id) + controller.stub_chain(:client, :projects).and_return([@repo]) + + get :status + + expect(assigns(:already_added_projects)).to eq([@project]) + expect(assigns(:repos)).to eq([@repo]) + end + + it "does not show already added project" do + @project = create(:project, import_type: 'bitbucket', creator_id: user.id, import_source: 'asd/vim') + controller.stub_chain(:client, :projects).and_return([@repo]) + + get :status + + expect(assigns(:already_added_projects)).to eq([@project]) + expect(assigns(:repos)).to eq([]) + end + end + + describe "POST create" do + let(:bitbucket_username) { user.username } + + let(:bitbucket_user) { + { + user: { + username: bitbucket_username + } + }.with_indifferent_access + } + + let(:bitbucket_repo) { + { + slug: "vim", + owner: bitbucket_username + }.with_indifferent_access + } + + before do + allow(Gitlab::BitbucketImport::KeyAdder). + to receive(:new).with(bitbucket_repo, user). + and_return(double(execute: true)) + + controller.stub_chain(:client, :user).and_return(bitbucket_user) + controller.stub_chain(:client, :project).and_return(bitbucket_repo) + end + + context "when the repository owner is the Bitbucket user" do + context "when the Bitbucket user and GitLab user's usernames match" do + it "takes the current user's namespace" do + expect(Gitlab::BitbucketImport::ProjectCreator). + to receive(:new).with(bitbucket_repo, user.namespace, user). + and_return(double(execute: true)) + + post :create, format: :js + end + end + + context "when the Bitbucket user and GitLab user's usernames don't match" do + let(:bitbucket_username) { "someone_else" } + + it "takes the current user's namespace" do + expect(Gitlab::BitbucketImport::ProjectCreator). + to receive(:new).with(bitbucket_repo, user.namespace, user). + and_return(double(execute: true)) + + post :create, format: :js + end + end + end + + context "when the repository owner is not the Bitbucket user" do + let(:other_username) { "someone_else" } + + before do + bitbucket_repo["owner"] = other_username + end + + context "when a namespace with the Bitbucket user's username already exists" do + let!(:existing_namespace) { create(:namespace, name: other_username, owner: user) } + + context "when the namespace is owned by the GitLab user" do + it "takes the existing namespace" do + expect(Gitlab::BitbucketImport::ProjectCreator). + to receive(:new).with(bitbucket_repo, existing_namespace, user). + and_return(double(execute: true)) + + post :create, format: :js + end + end + + context "when the namespace is not owned by the GitLab user" do + before do + existing_namespace.owner = create(:user) + existing_namespace.save + end + + it "doesn't create a project" do + expect(Gitlab::BitbucketImport::ProjectCreator). + not_to receive(:new) + + post :create, format: :js + end + end + end + + context "when a namespace with the Bitbucket user's username doesn't exist" do + it "creates the namespace" do + expect(Gitlab::BitbucketImport::ProjectCreator). + to receive(:new).and_return(double(execute: true)) + + post :create, format: :js + + expect(Namespace.where(name: other_username).first).not_to be_nil + end + + it "takes the new namespace" do + expect(Gitlab::BitbucketImport::ProjectCreator). + to receive(:new).with(bitbucket_repo, an_instance_of(Group), user). + and_return(double(execute: true)) + + post :create, format: :js + end + end + end + end +end diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb new file mode 100644 index 0000000000..3d3846b2e3 --- /dev/null +++ b/spec/controllers/import/github_controller_spec.rb @@ -0,0 +1,153 @@ +require 'spec_helper' + +describe Import::GithubController do + let(:user) { create(:user, github_access_token: 'asd123') } + + before do + sign_in(user) + controller.stub(:github_import_enabled?).and_return(true) + end + + describe "GET callback" do + it "updates access token" do + token = "asdasd12345" + allow_any_instance_of(Gitlab::GithubImport::Client). + to receive(:get_token).and_return(token) + Gitlab.config.omniauth.providers << OpenStruct.new(app_id: 'asd123', + app_secret: 'asd123', + name: 'github') + + get :callback + + expect(user.reload.github_access_token).to eq(token) + expect(controller).to redirect_to(status_import_github_url) + end + end + + describe "GET status" do + before do + @repo = OpenStruct.new(login: 'vim', full_name: 'asd/vim') + @org = OpenStruct.new(login: 'company') + @org_repo = OpenStruct.new(login: 'company', full_name: 'company/repo') + end + + it "assigns variables" do + @project = create(:project, import_type: 'github', creator_id: user.id) + controller.stub_chain(:client, :repos).and_return([@repo]) + controller.stub_chain(:client, :orgs).and_return([@org]) + controller.stub_chain(:client, :org_repos).with(@org.login).and_return([@org_repo]) + + get :status + + expect(assigns(:already_added_projects)).to eq([@project]) + expect(assigns(:repos)).to eq([@repo, @org_repo]) + end + + it "does not show already added project" do + @project = create(:project, import_type: 'github', creator_id: user.id, import_source: 'asd/vim') + controller.stub_chain(:client, :repos).and_return([@repo]) + controller.stub_chain(:client, :orgs).and_return([]) + + get :status + + expect(assigns(:already_added_projects)).to eq([@project]) + expect(assigns(:repos)).to eq([]) + end + end + + describe "POST create" do + let(:github_username) { user.username } + + let(:github_user) { + OpenStruct.new(login: github_username) + } + + let(:github_repo) { + OpenStruct.new(name: 'vim', full_name: "#{github_username}/vim", owner: OpenStruct.new(login: github_username)) + } + + before do + controller.stub_chain(:client, :user).and_return(github_user) + controller.stub_chain(:client, :repo).and_return(github_repo) + end + + context "when the repository owner is the GitHub user" do + context "when the GitHub user and GitLab user's usernames match" do + it "takes the current user's namespace" do + expect(Gitlab::GithubImport::ProjectCreator). + to receive(:new).with(github_repo, user.namespace, user). + and_return(double(execute: true)) + + post :create, format: :js + end + end + + context "when the GitHub user and GitLab user's usernames don't match" do + let(:github_username) { "someone_else" } + + it "takes the current user's namespace" do + expect(Gitlab::GithubImport::ProjectCreator). + to receive(:new).with(github_repo, user.namespace, user). + and_return(double(execute: true)) + + post :create, format: :js + end + end + end + + context "when the repository owner is not the GitHub user" do + let(:other_username) { "someone_else" } + + before do + github_repo.owner = OpenStruct.new(login: other_username) + end + + context "when a namespace with the GitHub user's username already exists" do + let!(:existing_namespace) { create(:namespace, name: other_username, owner: user) } + + context "when the namespace is owned by the GitLab user" do + it "takes the existing namespace" do + expect(Gitlab::GithubImport::ProjectCreator). + to receive(:new).with(github_repo, existing_namespace, user). + and_return(double(execute: true)) + + post :create, format: :js + end + end + + context "when the namespace is not owned by the GitLab user" do + before do + existing_namespace.owner = create(:user) + existing_namespace.save + end + + it "doesn't create a project" do + expect(Gitlab::GithubImport::ProjectCreator). + not_to receive(:new) + + post :create, format: :js + end + end + end + + context "when a namespace with the GitHub user's username doesn't exist" do + it "creates the namespace" do + expect(Gitlab::GithubImport::ProjectCreator). + to receive(:new).and_return(double(execute: true)) + + post :create, format: :js + + expect(Namespace.where(name: other_username).first).not_to be_nil + end + + it "takes the new namespace" do + expect(Gitlab::GithubImport::ProjectCreator). + to receive(:new).with(github_repo, an_instance_of(Group), user). + and_return(double(execute: true)) + + post :create, format: :js + end + end + end + end +end diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb new file mode 100644 index 0000000000..112e51d431 --- /dev/null +++ b/spec/controllers/import/gitlab_controller_spec.rb @@ -0,0 +1,152 @@ +require 'spec_helper' + +describe Import::GitlabController do + let(:user) { create(:user, gitlab_access_token: 'asd123') } + + before do + sign_in(user) + controller.stub(:gitlab_import_enabled?).and_return(true) + end + + describe "GET callback" do + it "updates access token" do + token = "asdasd12345" + Gitlab::GitlabImport::Client.any_instance.stub_chain(:client, :auth_code, :get_token, :token).and_return(token) + Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "gitlab") + + get :callback + + expect(user.reload.gitlab_access_token).to eq(token) + expect(controller).to redirect_to(status_import_gitlab_url) + end + end + + describe "GET status" do + before do + @repo = OpenStruct.new(path: 'vim', path_with_namespace: 'asd/vim') + end + + it "assigns variables" do + @project = create(:project, import_type: 'gitlab', creator_id: user.id) + controller.stub_chain(:client, :projects).and_return([@repo]) + + get :status + + expect(assigns(:already_added_projects)).to eq([@project]) + expect(assigns(:repos)).to eq([@repo]) + end + + it "does not show already added project" do + @project = create(:project, import_type: 'gitlab', creator_id: user.id, import_source: 'asd/vim') + controller.stub_chain(:client, :projects).and_return([@repo]) + + get :status + + expect(assigns(:already_added_projects)).to eq([@project]) + expect(assigns(:repos)).to eq([]) + end + end + + describe "POST create" do + let(:gitlab_username) { user.username } + + let(:gitlab_user) { + { + username: gitlab_username + }.with_indifferent_access + } + + let(:gitlab_repo) { + { + path: 'vim', + path_with_namespace: "#{gitlab_username}/vim", + owner: { name: gitlab_username }, + namespace: { path: gitlab_username } + }.with_indifferent_access + } + + before do + controller.stub_chain(:client, :user).and_return(gitlab_user) + controller.stub_chain(:client, :project).and_return(gitlab_repo) + end + + context "when the repository owner is the GitLab.com user" do + context "when the GitLab.com user and GitLab server user's usernames match" do + it "takes the current user's namespace" do + expect(Gitlab::GitlabImport::ProjectCreator). + to receive(:new).with(gitlab_repo, user.namespace, user). + and_return(double(execute: true)) + + post :create, format: :js + end + end + + context "when the GitLab.com user and GitLab server user's usernames don't match" do + let(:gitlab_username) { "someone_else" } + + it "takes the current user's namespace" do + expect(Gitlab::GitlabImport::ProjectCreator). + to receive(:new).with(gitlab_repo, user.namespace, user). + and_return(double(execute: true)) + + post :create, format: :js + end + end + end + + context "when the repository owner is not the GitLab.com user" do + let(:other_username) { "someone_else" } + + before do + gitlab_repo["namespace"]["path"] = other_username + end + + context "when a namespace with the GitLab.com user's username already exists" do + let!(:existing_namespace) { create(:namespace, name: other_username, owner: user) } + + context "when the namespace is owned by the GitLab server user" do + it "takes the existing namespace" do + expect(Gitlab::GitlabImport::ProjectCreator). + to receive(:new).with(gitlab_repo, existing_namespace, user). + and_return(double(execute: true)) + + post :create, format: :js + end + end + + context "when the namespace is not owned by the GitLab server user" do + before do + existing_namespace.owner = create(:user) + existing_namespace.save + end + + it "doesn't create a project" do + expect(Gitlab::GitlabImport::ProjectCreator). + not_to receive(:new) + + post :create, format: :js + end + end + end + + context "when a namespace with the GitLab.com user's username doesn't exist" do + it "creates the namespace" do + expect(Gitlab::GitlabImport::ProjectCreator). + to receive(:new).and_return(double(execute: true)) + + post :create, format: :js + + expect(Namespace.where(name: other_username).first).not_to be_nil + end + + it "takes the new namespace" do + expect(Gitlab::GitlabImport::ProjectCreator). + to receive(:new).with(gitlab_repo, an_instance_of(Group), user). + and_return(double(execute: true)) + + post :create, format: :js + end + end + end + end +end diff --git a/spec/controllers/import/gitorious_controller_spec.rb b/spec/controllers/import/gitorious_controller_spec.rb new file mode 100644 index 0000000000..07c9484bf1 --- /dev/null +++ b/spec/controllers/import/gitorious_controller_spec.rb @@ -0,0 +1,67 @@ +require 'spec_helper' + +describe Import::GitoriousController do + let(:user) { create(:user) } + + before do + sign_in(user) + end + + describe "GET new" do + it "redirects to import endpoint on gitorious.org" do + get :new + + expect(controller).to redirect_to("https://gitorious.org/gitlab-import?callback_url=http://test.host/import/gitorious/callback") + end + end + + describe "GET callback" do + it "stores repo list in session" do + get :callback, repos: 'foo/bar,baz/qux' + + expect(session[:gitorious_repos]).to eq('foo/bar,baz/qux') + end + end + + describe "GET status" do + before do + @repo = OpenStruct.new(full_name: 'asd/vim') + end + + it "assigns variables" do + @project = create(:project, import_type: 'gitorious', creator_id: user.id) + controller.stub_chain(:client, :repos).and_return([@repo]) + + get :status + + expect(assigns(:already_added_projects)).to eq([@project]) + expect(assigns(:repos)).to eq([@repo]) + end + + it "does not show already added project" do + @project = create(:project, import_type: 'gitorious', creator_id: user.id, import_source: 'asd/vim') + controller.stub_chain(:client, :repos).and_return([@repo]) + + get :status + + expect(assigns(:already_added_projects)).to eq([@project]) + expect(assigns(:repos)).to eq([]) + end + end + + describe "POST create" do + before do + @repo = Gitlab::GitoriousImport::Repository.new('asd/vim') + end + + it "takes already existing namespace" do + namespace = create(:namespace, name: "asd", owner: user) + expect(Gitlab::GitoriousImport::ProjectCreator). + to receive(:new).with(@repo, namespace, user). + and_return(double(execute: true)) + controller.stub_chain(:client, :repo).and_return(@repo) + + post :create, format: :js + end + end +end diff --git a/spec/controllers/import/google_code_controller_spec.rb b/spec/controllers/import/google_code_controller_spec.rb new file mode 100644 index 0000000000..037cddb460 --- /dev/null +++ b/spec/controllers/import/google_code_controller_spec.rb @@ -0,0 +1,47 @@ +require 'spec_helper' + +describe Import::GoogleCodeController do + let(:user) { create(:user) } + let(:dump_file) { fixture_file_upload(Rails.root + 'spec/fixtures/GoogleCodeProjectHosting.json', 'application/json') } + + before do + sign_in(user) + end + + describe "POST callback" do + it "stores Google Takeout dump list in session" do + post :callback, dump_file: dump_file + + expect(session[:google_code_dump]).to be_a(Hash) + expect(session[:google_code_dump]["kind"]).to eq("projecthosting#user") + expect(session[:google_code_dump]).to have_key("projects") + end + end + + describe "GET status" do + before do + @repo = OpenStruct.new(name: 'vim') + controller.stub_chain(:client, :valid?).and_return(true) + end + + it "assigns variables" do + @project = create(:project, import_type: 'google_code', creator_id: user.id) + controller.stub_chain(:client, :repos).and_return([@repo]) + + get :status + + expect(assigns(:already_added_projects)).to eq([@project]) + expect(assigns(:repos)).to eq([@repo]) + end + + it "does not show already added project" do + @project = create(:project, import_type: 'google_code', creator_id: user.id, import_source: 'vim') + controller.stub_chain(:client, :repos).and_return([@repo]) + + get :status + + expect(assigns(:already_added_projects)).to eq([@project]) + expect(assigns(:repos)).to eq([]) + end + end +end diff --git a/spec/controllers/merge_requests_controller_spec.rb b/spec/controllers/merge_requests_controller_spec.rb index 300527e4ff..d6f56ed33d 100644 --- a/spec/controllers/merge_requests_controller_spec.rb +++ b/spec/controllers/merge_requests_controller_spec.rb @@ -13,27 +13,32 @@ describe Projects::MergeRequestsController do describe "#show" do shared_examples "export merge as" do |format| it "should generally work" do - get :show, project_id: project.to_param, id: merge_request.iid, format: format + get(:show, namespace_id: project.namespace.to_param, + project_id: project.to_param, id: merge_request.iid, format: format) expect(response).to be_success end it "should generate it" do - MergeRequest.any_instance.should_receive(:"to_#{format}") + expect_any_instance_of(MergeRequest).to receive(:"to_#{format}") - get :show, project_id: project.to_param, id: merge_request.iid, format: format + get(:show, namespace_id: project.namespace.to_param, + project_id: project.to_param, id: merge_request.iid, format: format) end it "should render it" do - get :show, project_id: project.to_param, id: merge_request.iid, format: format + get(:show, namespace_id: project.namespace.to_param, + project_id: project.to_param, id: merge_request.iid, format: format) expect(response.body).to eq((merge_request.send(:"to_#{format}",user)).to_s) end it "should not escape Html" do - MergeRequest.any_instance.stub(:"to_#{format}").and_return('HTML entities &<>" ') + allow_any_instance_of(MergeRequest).to receive(:"to_#{format}"). + and_return('HTML entities &<>" ') - get :show, project_id: project.to_param, id: merge_request.iid, format: format + get(:show, namespace_id: project.namespace.to_param, + project_id: project.to_param, id: merge_request.iid, format: format) expect(response.body).to_not include('&') expect(response.body).to_not include('>') @@ -47,7 +52,8 @@ describe Projects::MergeRequestsController do let(:format) { :diff } it "should really only be a git diff" do - get :show, project_id: project.to_param, id: merge_request.iid, format: format + get(:show, namespace_id: project.namespace.to_param, + project_id: project.to_param, id: merge_request.iid, format: format) expect(response.body).to start_with("diff --git") end @@ -58,13 +64,15 @@ describe Projects::MergeRequestsController do let(:format) { :patch } it "should really be a git email patch with commit" do - get :show, project_id: project.to_param, id: merge_request.iid, format: format + get(:show, namespace_id: project.namespace.to_param, + project_id: project.to_param, id: merge_request.iid, format: format) expect(response.body[0..100]).to start_with("From #{merge_request.commits.last.id}") end it "should contain git diffs" do - get :show, project_id: project.to_param, id: merge_request.iid, format: format + get(:show, namespace_id: project.namespace.to_param, + project_id: project.to_param, id: merge_request.iid, format: format) expect(response.body).to match(/^diff --git/) end diff --git a/spec/controllers/namespaces_controller_spec.rb b/spec/controllers/namespaces_controller_spec.rb new file mode 100644 index 0000000000..9c8619722c --- /dev/null +++ b/spec/controllers/namespaces_controller_spec.rb @@ -0,0 +1,121 @@ +require 'spec_helper' + +describe NamespacesController do + let!(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } + + describe "GET show" do + context "when the namespace belongs to a user" do + let!(:other_user) { create(:user) } + + it "redirects to the user's page" do + get :show, id: other_user.username + + expect(response).to redirect_to(user_path(other_user)) + end + end + + context "when the namespace belongs to a group" do + let!(:group) { create(:group) } + let!(:project) { create(:project, namespace: group) } + + context "when the group has public projects" do + before do + project.update_attribute(:visibility_level, Project::PUBLIC) + end + + context "when not signed in" do + it "redirects to the group's page" do + get :show, id: group.path + + expect(response).to redirect_to(group_path(group)) + end + end + + context "when signed in" do + before do + sign_in(user) + end + + it "redirects to the group's page" do + get :show, id: group.path + + expect(response).to redirect_to(group_path(group)) + end + end + end + + context "when the project doesn't have public projects" do + context "when not signed in" do + it "redirects to the sign in page" do + get :show, id: group.path + + expect(response).to redirect_to(new_user_session_path) + end + end + + context "when signed in" do + before do + sign_in(user) + end + + context "when the user has access to the project" do + before do + project.team << [user, :master] + end + + context "when the user is blocked" do + before do + user.block + project.team << [user, :master] + end + + it "redirects to the sign in page" do + get :show, id: group.path + + expect(response).to redirect_to(new_user_session_path) + end + end + + context "when the user isn't blocked" do + it "redirects to the group's page" do + get :show, id: group.path + + expect(response).to redirect_to(group_path(group)) + end + end + end + + context "when the user doesn't have access to the project" do + it "responds with status 404" do + get :show, id: group.path + + expect(response.status).to eq(404) + end + end + end + end + end + + context "when the namespace doesn't exist" do + context "when signed in" do + before do + sign_in(user) + end + + it "responds with status 404" do + get :show, id: "doesntexist" + + expect(response.status).to eq(404) + end + end + + context "when not signed in" do + it "redirects to the sign in page" do + get :show, id: "doesntexist" + + expect(response).to redirect_to(new_user_session_path) + end + end + end + end +end diff --git a/spec/controllers/projects/protected_branches_controller_spec.rb b/spec/controllers/projects/protected_branches_controller_spec.rb new file mode 100644 index 0000000000..596d8d34b7 --- /dev/null +++ b/spec/controllers/projects/protected_branches_controller_spec.rb @@ -0,0 +1,10 @@ +require('spec_helper') + +describe Projects::ProtectedBranchesController do + describe "GET #index" do + let(:project) { create(:project_empty_repo, :public) } + it "redirect empty repo to projects page" do + get(:index, namespace_id: project.namespace.to_param, project_id: project.to_param) + end + end +end diff --git a/spec/controllers/projects/refs_controller_spec.rb b/spec/controllers/projects/refs_controller_spec.rb new file mode 100644 index 0000000000..c254ab7cb6 --- /dev/null +++ b/spec/controllers/projects/refs_controller_spec.rb @@ -0,0 +1,41 @@ +require 'spec_helper' + +describe Projects::RefsController do + let(:project) { create(:project) } + let(:user) { create(:user) } + + before do + sign_in(user) + project.team << [user, :developer] + end + + describe 'GET #logs_tree' do + def default_get(format = :html) + get :logs_tree, namespace_id: project.namespace.to_param, + project_id: project.to_param, id: 'master', + path: 'foo/bar/baz.html', format: format + end + + def xhr_get(format = :html) + xhr :get, :logs_tree, namespace_id: project.namespace.to_param, + project_id: project.to_param, id: 'master', + path: 'foo/bar/baz.html', format: format + end + + it 'never throws MissingTemplate' do + expect { default_get }.not_to raise_error + expect { xhr_get }.not_to raise_error + end + + it 'renders 404 for non-JS requests' do + xhr_get + + expect(response).to be_not_found + end + + it 'renders JS' do + xhr_get(:js) + expect(response).to be_success + end + end +end diff --git a/spec/controllers/projects/repositories_controller_spec.rb b/spec/controllers/projects/repositories_controller_spec.rb new file mode 100644 index 0000000000..91856ed0cc --- /dev/null +++ b/spec/controllers/projects/repositories_controller_spec.rb @@ -0,0 +1,65 @@ +require "spec_helper" + +describe Projects::RepositoriesController do + let(:project) { create(:project) } + let(:user) { create(:user) } + + describe "GET archive" do + before do + sign_in(user) + project.team << [user, :developer] + + allow(ArchiveRepositoryService).to receive(:new).and_return(service) + end + + let(:service) { ArchiveRepositoryService.new(project, "master", "zip") } + + it "executes ArchiveRepositoryService" do + expect(ArchiveRepositoryService).to receive(:new).with(project, "master", "zip") + expect(service).to receive(:execute) + + get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip" + end + + context "when the service raises an error" do + + before do + allow(service).to receive(:execute).and_raise("Archive failed") + end + + it "renders Not Found" do + get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip" + + expect(response.status).to eq(404) + end + end + + context "when the service doesn't return a path" do + + before do + allow(service).to receive(:execute).and_return(nil) + end + + it "reloads the page" do + get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip" + + expect(response).to redirect_to(archive_namespace_project_repository_path(project.namespace, project, ref: "master", format: "zip")) + end + end + + context "when the service returns a path" do + + let(:path) { Rails.root.join("spec/fixtures/dk.png").to_s } + + before do + allow(service).to receive(:execute).and_return(path) + end + + it "sends the file" do + get :archive, namespace_id: project.namespace.path, project_id: project.path, ref: "master", format: "zip" + + expect(response.body).to eq(File.binread(path)) + end + end + end +end diff --git a/spec/controllers/projects/uploads_controller_spec.rb b/spec/controllers/projects/uploads_controller_spec.rb new file mode 100644 index 0000000000..f51abfedae --- /dev/null +++ b/spec/controllers/projects/uploads_controller_spec.rb @@ -0,0 +1,280 @@ +require('spec_helper') + +describe Projects::UploadsController do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') } + let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') } + + describe "POST #create" do + before do + sign_in(user) + project.team << [user, :developer] + end + + context "without params['file']" do + it "returns an error" do + post :create, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + format: :json + expect(response.status).to eq(422) + end + end + + context 'with valid image' do + before do + post :create, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + file: jpg, + format: :json + end + + it 'returns a content with original filename, new link, and correct type.' do + expect(response.body).to match '\"alt\":\"rails_sample\"' + expect(response.body).to match "\"url\":\"http://localhost/#{project.path_with_namespace}/uploads" + expect(response.body).to match '\"is_image\":true' + end + end + + context 'with valid non-image file' do + before do + post :create, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + file: txt, + format: :json + end + + it 'returns a content with original filename, new link, and correct type.' do + expect(response.body).to match '\"alt\":\"doc_sample.txt\"' + expect(response.body).to match "\"url\":\"http://localhost/#{project.path_with_namespace}/uploads" + expect(response.body).to match '\"is_image\":false' + end + end + end + + describe "GET #show" do + let(:go) do + get :show, + namespace_id: project.namespace.to_param, + project_id: project.to_param, + secret: "123456", + filename: "image.jpg" + end + + context "when the project is public" do + before do + project.update_attribute(:visibility_level, Project::PUBLIC) + end + + context "when not signed in" do + context "when the file exists" do + before do + allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg) + allow(jpg).to receive(:exists?).and_return(true) + end + + it "responds with status 200" do + go + + expect(response.status).to eq(200) + end + end + + context "when the file doesn't exist" do + it "responds with status 404" do + go + + expect(response.status).to eq(404) + end + end + end + + context "when signed in" do + before do + sign_in(user) + end + + context "when the file exists" do + before do + allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg) + allow(jpg).to receive(:exists?).and_return(true) + end + + it "responds with status 200" do + go + + expect(response.status).to eq(200) + end + end + + context "when the file doesn't exist" do + it "responds with status 404" do + go + + expect(response.status).to eq(404) + end + end + end + end + + context "when the project is private" do + before do + project.update_attribute(:visibility_level, Project::PRIVATE) + end + + context "when not signed in" do + context "when the file exists" do + before do + allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg) + allow(jpg).to receive(:exists?).and_return(true) + end + + context "when the file is an image" do + before do + allow_any_instance_of(FileUploader).to receive(:image?).and_return(true) + end + + it "responds with status 200" do + go + + expect(response.status).to eq(200) + end + end + + context "when the file is not an image" do + it "redirects to the sign in page" do + go + + expect(response).to redirect_to(new_user_session_path) + end + end + end + + context "when the file doesn't exist" do + it "redirects to the sign in page" do + go + + expect(response).to redirect_to(new_user_session_path) + end + end + end + + context "when signed in" do + before do + sign_in(user) + end + + context "when the user has access to the project" do + before do + project.team << [user, :master] + end + + context "when the user is blocked" do + before do + user.block + project.team << [user, :master] + end + + context "when the file exists" do + before do + allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg) + allow(jpg).to receive(:exists?).and_return(true) + end + + context "when the file is an image" do + before do + allow_any_instance_of(FileUploader).to receive(:image?).and_return(true) + end + + it "responds with status 200" do + go + + expect(response.status).to eq(200) + end + end + + context "when the file is not an image" do + it "redirects to the sign in page" do + go + + expect(response).to redirect_to(new_user_session_path) + end + end + end + + context "when the file doesn't exist" do + it "redirects to the sign in page" do + go + + expect(response).to redirect_to(new_user_session_path) + end + end + end + + context "when the user isn't blocked" do + context "when the file exists" do + before do + allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg) + allow(jpg).to receive(:exists?).and_return(true) + end + + it "responds with status 200" do + go + + expect(response.status).to eq(200) + end + end + + context "when the file doesn't exist" do + it "responds with status 404" do + go + + expect(response.status).to eq(404) + end + end + end + end + + context "when the user doesn't have access to the project" do + context "when the file exists" do + before do + allow_any_instance_of(FileUploader).to receive(:file).and_return(jpg) + allow(jpg).to receive(:exists?).and_return(true) + end + + context "when the file is an image" do + before do + allow_any_instance_of(FileUploader).to receive(:image?).and_return(true) + end + + it "responds with status 200" do + go + + expect(response.status).to eq(200) + end + end + + context "when the file is not an image" do + it "responds with status 404" do + go + + expect(response.status).to eq(404) + end + end + end + + context "when the file doesn't exist" do + it "responds with status 404" do + go + + expect(response.status).to eq(404) + end + end + end + end + end + end +end diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb index 71bc49787c..a1b82a3215 100644 --- a/spec/controllers/projects_controller_spec.rb +++ b/spec/controllers/projects_controller_spec.rb @@ -7,56 +7,41 @@ describe ProjectsController do let(:jpg) { fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') } let(:txt) { fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') } - describe "POST #upload_image" do - before do - sign_in(user) - project.team << [user, :developer] - end + describe "GET show" do - context "without params['markdown_img']" do - it "returns an error" do - post :upload_image, id: project.to_param, format: :json - expect(response.status).to eq(422) - end - end + context "when requested by `go get`" do + render_views - context "with invalid file" do - before do - post :upload_image, id: project.to_param, markdown_img: txt, format: :json - end + it "renders the go-import meta tag" do + get :show, "go-get" => "1", namespace_id: "bogus_namespace", id: "bogus_project" - it "returns an error" do - expect(response.status).to eq(422) - end - end - - context "with valid file" do - before do - post :upload_image, id: project.to_param, markdown_img: jpg, format: :json - end - - it "returns a content with original filename and new link." do - expect(response.body).to match "\"alt\":\"rails_sample\"" - expect(response.body).to match "\"url\":\"http://test.host/uploads/#{project.path_with_namespace}" + expect(response.body).to include("name='go-import'") + + content = "localhost/bogus_namespace/bogus_project git http://localhost/bogus_namespace/bogus_project.git" + expect(response.body).to include("content='#{content}'") end end end - + describe "POST #toggle_star" do it "toggles star if user is signed in" do sign_in(user) - expect(user.starred?(public_project)).to be_false - post :toggle_star, id: public_project.to_param - expect(user.starred?(public_project)).to be_true - post :toggle_star, id: public_project.to_param - expect(user.starred?(public_project)).to be_false + expect(user.starred?(public_project)).to be_falsey + post(:toggle_star, namespace_id: public_project.namespace.to_param, + id: public_project.to_param) + expect(user.starred?(public_project)).to be_truthy + post(:toggle_star, namespace_id: public_project.namespace.to_param, + id: public_project.to_param) + expect(user.starred?(public_project)).to be_falsey end it "does nothing if user is not signed in" do - post :toggle_star, id: public_project.to_param - expect(user.starred?(public_project)).to be_false - post :toggle_star, id: public_project.to_param - expect(user.starred?(public_project)).to be_false + post(:toggle_star, namespace_id: project.namespace.to_param, + id: public_project.to_param) + expect(user.starred?(public_project)).to be_falsey + post(:toggle_star, namespace_id: project.namespace.to_param, + id: public_project.to_param) + expect(user.starred?(public_project)).to be_falsey end end end diff --git a/spec/controllers/tree_controller_spec.rb b/spec/controllers/tree_controller_spec.rb index 8147fb0e6f..7b219819bb 100644 --- a/spec/controllers/tree_controller_spec.rb +++ b/spec/controllers/tree_controller_spec.rb @@ -9,8 +9,8 @@ describe Projects::TreeController do project.team << [user, :master] - project.stub(:branches).and_return(['master', 'foo/bar/baz']) - project.stub(:tags).and_return(['v1.0.0', 'v2.0.0']) + allow(project).to receive(:branches).and_return(['master', 'foo/bar/baz']) + allow(project).to receive(:tags).and_return(['v1.0.0', 'v2.0.0']) controller.instance_variable_set(:@project, project) end @@ -18,26 +18,29 @@ describe Projects::TreeController do # Make sure any errors accessing the tree in our views bubble up to this spec render_views - before { get :show, project_id: project.to_param, id: id } + before do + get(:show, namespace_id: project.namespace.to_param, + project_id: project.to_param, id: id) + end context "valid branch, no path" do let(:id) { 'master' } - it { should respond_with(:success) } + it { is_expected.to respond_with(:success) } end context "valid branch, valid path" do let(:id) { 'master/encoding/' } - it { should respond_with(:success) } + it { is_expected.to respond_with(:success) } end context "valid branch, invalid path" do let(:id) { 'master/invalid-path/' } - it { should respond_with(:not_found) } + it { is_expected.to respond_with(:not_found) } end context "invalid branch, valid path" do let(:id) { 'invalid-branch/encoding/' } - it { should respond_with(:not_found) } + it { is_expected.to respond_with(:not_found) } end end @@ -45,12 +48,17 @@ describe Projects::TreeController do render_views before do - get :show, project_id: project.to_param, id: id + get(:show, namespace_id: project.namespace.to_param, + project_id: project.to_param, id: id) end context 'redirect to blob' do let(:id) { 'master/README.md' } - it { should redirect_to("/#{project.path_with_namespace}/blob/master/README.md") } + it 'redirects' do + redirect_url = "/#{project.path_with_namespace}/blob/master/README.md" + expect(subject). + to redirect_to(redirect_url) + end end end end diff --git a/spec/controllers/uploads_controller_spec.rb b/spec/controllers/uploads_controller_spec.rb new file mode 100644 index 0000000000..0f9780356b --- /dev/null +++ b/spec/controllers/uploads_controller_spec.rb @@ -0,0 +1,296 @@ +require 'spec_helper' + +describe UploadsController do + let!(:user) { create(:user, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } + + describe "GET show" do + context "when viewing a user avatar" do + context "when signed in" do + before do + sign_in(user) + end + + context "when the user is blocked" do + before do + user.block + end + + it "redirects to the sign in page" do + get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "image.png" + + expect(response).to redirect_to(new_user_session_path) + end + end + + context "when the user isn't blocked" do + it "responds with status 200" do + get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "image.png" + + expect(response.status).to eq(200) + end + end + end + + context "when not signed in" do + it "responds with status 200" do + get :show, model: "user", mounted_as: "avatar", id: user.id, filename: "image.png" + + expect(response.status).to eq(200) + end + end + end + + context "when viewing a project avatar" do + let!(:project) { create(:project, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } + + context "when the project is public" do + before do + project.update_attribute(:visibility_level, Project::PUBLIC) + end + + context "when not signed in" do + it "responds with status 200" do + get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" + + expect(response.status).to eq(200) + end + end + + context "when signed in" do + before do + sign_in(user) + end + + it "responds with status 200" do + get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" + + expect(response.status).to eq(200) + end + end + end + + context "when the project is private" do + before do + project.update_attribute(:visibility_level, Project::PRIVATE) + end + + context "when not signed in" do + it "redirects to the sign in page" do + get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" + + expect(response).to redirect_to(new_user_session_path) + end + end + + context "when signed in" do + before do + sign_in(user) + end + + context "when the user has access to the project" do + before do + project.team << [user, :master] + end + + context "when the user is blocked" do + before do + user.block + project.team << [user, :master] + end + + it "redirects to the sign in page" do + get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" + + expect(response).to redirect_to(new_user_session_path) + end + end + + context "when the user isn't blocked" do + it "responds with status 200" do + get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" + + expect(response.status).to eq(200) + end + end + end + + context "when the user doesn't have access to the project" do + it "responds with status 404" do + get :show, model: "project", mounted_as: "avatar", id: project.id, filename: "image.png" + + expect(response.status).to eq(404) + end + end + end + end + end + + context "when viewing a group avatar" do + let!(:group) { create(:group, avatar: fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "image/png")) } + let!(:project) { create(:project, namespace: group) } + + context "when the group has public projects" do + before do + project.update_attribute(:visibility_level, Project::PUBLIC) + end + + context "when not signed in" do + it "responds with status 200" do + get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" + + expect(response.status).to eq(200) + end + end + + context "when signed in" do + before do + sign_in(user) + end + + it "responds with status 200" do + get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" + + expect(response.status).to eq(200) + end + end + end + + context "when the project doesn't have public projects" do + context "when not signed in" do + it "redirects to the sign in page" do + get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" + + expect(response).to redirect_to(new_user_session_path) + end + end + + context "when signed in" do + before do + sign_in(user) + end + + context "when the user has access to the project" do + before do + project.team << [user, :master] + end + + context "when the user is blocked" do + before do + user.block + project.team << [user, :master] + end + + it "redirects to the sign in page" do + get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" + + expect(response).to redirect_to(new_user_session_path) + end + end + + context "when the user isn't blocked" do + it "responds with status 200" do + get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" + + expect(response.status).to eq(200) + end + end + end + + context "when the user doesn't have access to the project" do + it "responds with status 404" do + get :show, model: "group", mounted_as: "avatar", id: group.id, filename: "image.png" + + expect(response.status).to eq(404) + end + end + end + end + end + + context "when viewing a note attachment" do + let!(:note) { create(:note, :with_attachment) } + let(:project) { note.project } + + context "when the project is public" do + before do + project.update_attribute(:visibility_level, Project::PUBLIC) + end + + context "when not signed in" do + it "responds with status 200" do + get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" + + expect(response.status).to eq(200) + end + end + + context "when signed in" do + before do + sign_in(user) + end + + it "responds with status 200" do + get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" + + expect(response.status).to eq(200) + end + end + end + + context "when the project is private" do + before do + project.update_attribute(:visibility_level, Project::PRIVATE) + end + + context "when not signed in" do + it "redirects to the sign in page" do + get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" + + expect(response).to redirect_to(new_user_session_path) + end + end + + context "when signed in" do + before do + sign_in(user) + end + + context "when the user has access to the project" do + before do + project.team << [user, :master] + end + + context "when the user is blocked" do + before do + user.block + project.team << [user, :master] + end + + it "redirects to the sign in page" do + get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" + + expect(response).to redirect_to(new_user_session_path) + end + end + + context "when the user isn't blocked" do + it "responds with status 200" do + get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" + + expect(response.status).to eq(200) + end + end + end + + context "when the user doesn't have access to the project" do + it "responds with status 404" do + get :show, model: "note", mounted_as: "attachment", id: note.id, filename: "image.png" + + expect(response.status).to eq(404) + end + end + end + end + end + end +end diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb new file mode 100644 index 0000000000..d47a37914d --- /dev/null +++ b/spec/controllers/users_controller_spec.rb @@ -0,0 +1,46 @@ +require 'spec_helper' + +describe UsersController do + let(:user) { create(:user, username: 'user1', name: 'User 1', email: 'user1@gitlab.com') } + + before do + sign_in(user) + end + + describe 'GET #show' do + render_views + + it 'renders the show template' do + get :show, username: user.username + expect(response.status).to eq(200) + expect(response).to render_template('show') + end + end + + describe 'GET #calendar' do + it 'renders calendar' do + get :calendar, username: user.username + expect(response).to render_template('calendar') + end + end + + describe 'GET #calendar_activities' do + let!(:project) { create(:project) } + let!(:user) { create(:user) } + + before do + allow_any_instance_of(User).to receive(:contributed_projects_ids).and_return([project.id]) + project.team << [user, :developer] + end + + it 'assigns @calendar_date' do + get :calendar_activities, username: user.username, date: '2014-07-31' + expect(assigns(:calendar_date)).to eq(Date.parse('2014-07-31')) + end + + it 'renders calendar_activities' do + get :calendar_activities, username: user.username + expect(response).to render_template('calendar_activities') + end + end +end diff --git a/spec/factories.rb b/spec/factories.rb index 03c87fcc6c..a5c335c82b 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -5,10 +5,14 @@ FactoryGirl.define do Faker::Lorem.sentence end - sequence :name, aliases: [:file_name] do + sequence :name do Faker::Name.name end + sequence :file_name do + Faker::Internet.user_name + end + sequence(:url) { Faker::Internet.uri('http') } factory :user, aliases: [:author, :assignee, :owner, :creator] do @@ -16,7 +20,6 @@ FactoryGirl.define do name sequence(:username) { |n| "#{Faker::Internet.user_name}#{n}" } password "12345678" - password_confirmation { password } confirmed_at { Time.now } confirmation_token { nil } @@ -24,6 +27,20 @@ FactoryGirl.define do admin true end + factory :omniauth_user do + ignore do + extern_uid '123456' + provider 'ldapmain' + end + + after(:create) do |user, evaluator| + user.identities << create(:identity, + provider: evaluator.provider, + extern_uid: evaluator.extern_uid + ) + end + end + factory :admin, traits: [:admin] end @@ -39,10 +56,10 @@ FactoryGirl.define do owner end - factory :users_project do + factory :project_member do user project - project_access { UsersProject::MASTER } + access_level { ProjectMember::MASTER } end factory :issue do @@ -84,21 +101,12 @@ FactoryGirl.define do user end - factory :key_with_a_space_in_the_middle do - key do - "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa ++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" - end - end - factory :another_key do key do "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDmTillFzNTrrGgwaCKaSj+QCz81E6jBc/s9av0+3b1Hwfxgkqjl4nAK/OD2NjgyrONDTDfR8cRN4eAAy6nY8GLkOyYBDyuc5nTMqs5z3yVuTwf3koGm/YQQCmo91psZ2BgDFTor8SVEE5Mm1D1k3JDMhDFxzzrOtRYFPci9lskTJaBjpqWZ4E9rDTD2q/QZntCqbC3wE9uSemRQB5f8kik7vD/AD8VQXuzKladrZKkzkONCPWsXDspUitjM8HkQdOf0PsYn1CMUC1xKYbCxkg5TkEosIwGv6CoEArUrdu/4+10LVslq494mAvEItywzrluCLCnwELfW+h/m8UHoVhZ" end - end - factory :invalid_key do - key do - "ssh-rsa this_is_invalid_key==" + factory :another_deploy_key, class: 'DeployKey' do end end end @@ -165,7 +173,6 @@ FactoryGirl.define do factory :service do type "" title "GitLab CI" - token "x56olispAND34ng" project end @@ -178,4 +185,9 @@ FactoryGirl.define do deploy_key project end + + factory :identity do + provider 'ldapmain' + extern_uid 'my-ldap-id' + end end diff --git a/spec/factories/users_groups.rb b/spec/factories/group_members.rb similarity index 81% rename from spec/factories/users_groups.rb rename to spec/factories/group_members.rb index 49c3a367e1..debb86d997 100644 --- a/spec/factories/users_groups.rb +++ b/spec/factories/group_members.rb @@ -1,6 +1,6 @@ # == Schema Information # -# Table name: users_groups +# Table name: group_members # # id :integer not null, primary key # group_access :integer not null @@ -12,8 +12,8 @@ # FactoryGirl.define do - factory :users_group do - group_access { UsersGroup::OWNER } + factory :group_member do + access_level { GroupMember::OWNER } group user end diff --git a/spec/factories/label_links.rb b/spec/factories/label_links.rb index d6b6f8581f..bd304b5db6 100644 --- a/spec/factories/label_links.rb +++ b/spec/factories/label_links.rb @@ -1,3 +1,15 @@ +# == 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 +# + # Read about factories at https://github.com/thoughtbot/factory_girl FactoryGirl.define do diff --git a/spec/factories/labels.rb b/spec/factories/labels.rb index af9f3efa64..6829387c66 100644 --- a/spec/factories/labels.rb +++ b/spec/factories/labels.rb @@ -1,3 +1,15 @@ +# == 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 +# + # Read about factories at https://github.com/thoughtbot/factory_girl FactoryGirl.define do diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb index 3319262c01..77cd37c22d 100644 --- a/spec/factories/merge_requests.rb +++ b/spec/factories/merge_requests.rb @@ -1,3 +1,26 @@ +# == 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 +# + FactoryGirl.define do factory :merge_request do title @@ -17,7 +40,7 @@ FactoryGirl.define do source_branch "master" target_branch "feature" - merge_status :can_be_merged + merge_status "can_be_merged" trait :with_diffs do end diff --git a/spec/factories/notes.rb b/spec/factories/notes.rb index a55ccf289d..f1c33461b5 100644 --- a/spec/factories/notes.rb +++ b/spec/factories/notes.rb @@ -1,3 +1,22 @@ +# == 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 +# + require_relative '../support/repo_helpers' FactoryGirl.define do @@ -11,6 +30,7 @@ FactoryGirl.define do 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_project_snippet, traits: [:on_project_snippet] trait :on_commit do project factory: :project @@ -33,6 +53,11 @@ FactoryGirl.define do noteable_type "Issue" end + trait :on_project_snippet do + noteable_id 1 + noteable_type "Snippet" + end + trait :with_attachment do attachment { fixture_file_upload(Rails.root + "spec/fixtures/dk.png", "`/png") } end diff --git a/spec/factories/projects.rb b/spec/factories/projects.rb index 353d9d645e..0899a7603f 100644 --- a/spec/factories/projects.rb +++ b/spec/factories/projects.rb @@ -1,4 +1,39 @@ +# == 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 +# wall_enabled :boolean default(TRUE), not null +# merge_requests_enabled :boolean default(TRUE), not null +# wiki_enabled :boolean default(TRUE), not null +# namespace_id :integer +# issues_tracker :string(255) default("gitlab"), not null +# issues_tracker_id :string(255) +# snippets_enabled :boolean default(TRUE), not null +# last_activity_at :datetime +# import_url :string(255) +# visibility_level :integer default(0), not null +# archived :boolean default(FALSE), not null +# 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) +# avatar :string(255) +# + FactoryGirl.define do + # Project without repository + # + # Project does not have bare repository. + # Use this factory if you dont need repository in tests factory :empty_project, class: 'Project' do sequence(:name) { |n| "project#{n}" } path { name.downcase.gsub(/\s/, '_') } @@ -19,16 +54,20 @@ FactoryGirl.define do end end - # Generates a test repository from the repository stored under `spec/seed_project.tar.gz`. - # Once you run `rake gitlab:setup`, you can see what the repository looks like under `tmp/repositories/gitlabhq`. - # In order to modify files in the repository, you must untar the seed, modify and remake the tar. - # Before recompressing, do not forget to `git checkout master`. - # After recompressing, you need to run `RAILS_ENV=test bundle exec rake gitlab:setup` to regenerate the seeds under tmp. + # Project with empty repository # - # If you want to modify the repository only for an specific type of tests, e.g., markdown tests, - # consider using a feature branch to reduce the chances of collision with other tests. - # Create a new commit, and use the same commit message that you will use for the change in the main repo. - # Changing the commig message and SHA of branch `master` may break tests. + # 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 + end + + # Project with test repository + # + # Test repository source can be found at + # https://gitlab.com/gitlab-org/gitlab-test factory :project, parent: :empty_project do path { 'gitlabhq' } @@ -38,7 +77,19 @@ FactoryGirl.define do end factory :redmine_project, parent: :project do - issues_tracker { "redmine" } - issues_tracker_id { "project_name_in_redmine" } + after :create do |project| + project.create_redmine_service( + active: true, + properties: { + 'project_url' => 'http://redmine/projects/project_name_in_redmine', + 'issues_url' => "http://redmine/#{project.id}/project_name_in_redmine/:id", + 'new_issue_url' => 'http://redmine/projects/project_name_in_redmine/issues/new' + } + ) + end + after :create do |project| + project.issues_tracker = 'redmine' + project.issues_tracker_id = 'project_name_in_redmine' + end end end diff --git a/spec/factories_spec.rb b/spec/factories_spec.rb index 66bef0761c..457859deda 100644 --- a/spec/factories_spec.rb +++ b/spec/factories_spec.rb @@ -1,15 +1,9 @@ require 'spec_helper' -INVALID_FACTORIES = [ - :key_with_a_space_in_the_middle, - :invalid_key, -] - FactoryGirl.factories.map(&:name).each do |factory_name| - next if INVALID_FACTORIES.include?(factory_name) describe "#{factory_name} factory" do it 'should be valid' do - build(factory_name).should be_valid + expect(build(factory_name)).to be_valid end end end diff --git a/spec/features/admin/admin_hooks_spec.rb b/spec/features/admin/admin_hooks_spec.rb index 120448ae33..25862614d2 100644 --- a/spec/features/admin/admin_hooks_spec.rb +++ b/spec/features/admin/admin_hooks_spec.rb @@ -12,15 +12,15 @@ describe "Admin::Hooks", feature: true do describe "GET /admin/hooks" do it "should be ok" do visit admin_root_path - within ".main-nav" do + within ".sidebar-wrapper" do click_on "Hooks" end - current_path.should == admin_hooks_path + expect(current_path).to eq(admin_hooks_path) end it "should have hooks list" do visit admin_hooks_path - page.should have_content(@system_hook.url) + expect(page).to have_content(@system_hook.url) end end @@ -33,8 +33,8 @@ describe "Admin::Hooks", feature: true do end it "should open new hook popup" do - page.current_path.should == admin_hooks_path - page.should have_content(@url) + expect(current_path).to eq(admin_hooks_path) + expect(page).to have_content(@url) end end @@ -45,7 +45,7 @@ describe "Admin::Hooks", feature: true do click_link "Test Hook" end - it { page.current_path.should == admin_hooks_path } + it { expect(current_path).to eq(admin_hooks_path) } end end diff --git a/spec/features/admin/admin_projects_spec.rb b/spec/features/admin/admin_projects_spec.rb index 3b3d027ab7..101d955d69 100644 --- a/spec/features/admin/admin_projects_spec.rb +++ b/spec/features/admin/admin_projects_spec.rb @@ -8,27 +8,27 @@ describe "Admin::Projects", feature: true do describe "GET /admin/projects" do before do - visit admin_projects_path + visit admin_namespaces_projects_path end it "should be ok" do - current_path.should == admin_projects_path + expect(current_path).to eq(admin_namespaces_projects_path) end it "should have projects list" do - page.should have_content(@project.name) + expect(page).to have_content(@project.name) end end describe "GET /admin/projects/:id" do before do - visit admin_projects_path + visit admin_namespaces_projects_path click_link "#{@project.name}" end it "should have project info" do - page.should have_content(@project.path) - page.should have_content(@project.name) + expect(page).to have_content(@project.path) + expect(page).to have_content(@project.name) end end end diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb index 82da19746f..f97b69713c 100644 --- a/spec/features/admin/admin_users_spec.rb +++ b/spec/features/admin/admin_users_spec.rb @@ -9,12 +9,12 @@ describe "Admin::Users", feature: true do end it "should be ok" do - current_path.should == admin_users_path + expect(current_path).to eq(admin_users_path) end it "should have users list" do - page.should have_content(@user.email) - page.should have_content(@user.name) + expect(page).to have_content(@user.email) + expect(page).to have_content(@user.name) end end @@ -32,31 +32,33 @@ describe "Admin::Users", feature: true do it "should apply defaults to user" do click_button "Create user" - user = User.last - user.projects_limit.should == Gitlab.config.gitlab.default_projects_limit - user.can_create_group.should == Gitlab.config.gitlab.default_can_create_group + user = User.find_by(username: 'bang') + expect(user.projects_limit). + to eq(Gitlab.config.gitlab.default_projects_limit) + expect(user.can_create_group). + to eq(Gitlab.config.gitlab.default_can_create_group) end it "should create user with valid data" do click_button "Create user" - user = User.last - user.name.should == "Big Bang" - user.email.should == "bigbang@mail.com" + user = User.find_by(username: 'bang') + expect(user.name).to eq('Big Bang') + expect(user.email).to eq('bigbang@mail.com') end it "should call send mail" do - Notify.should_receive(:new_user_email) + expect(Notify).to receive(:new_user_email) click_button "Create user" end it "should send valid email to user with email & password" do click_button "Create user" - user = User.last + user = User.find_by(username: 'bang') email = ActionMailer::Base.deliveries.last - email.subject.should have_content("Account was created") - email.text_part.body.should have_content(user.email) - email.text_part.body.should have_content('password') + expect(email.subject).to have_content('Account was created') + expect(email.text_part.body).to have_content(user.email) + expect(email.text_part.body).to have_content('password') end end @@ -67,8 +69,8 @@ describe "Admin::Users", feature: true do end it "should have user info" do - page.should have_content(@user.email) - page.should have_content(@user.name) + expect(page).to have_content(@user.email) + expect(page).to have_content(@user.name) end end @@ -80,8 +82,8 @@ describe "Admin::Users", feature: true do end it "should have user edit page" do - page.should have_content("Name") - page.should have_content("Password") + expect(page).to have_content('Name') + expect(page).to have_content('Password') end describe "Update user" do @@ -93,14 +95,14 @@ describe "Admin::Users", feature: true do end it "should show page with new data" do - page.should have_content("bigbang@mail.com") - page.should have_content("Big Bang") + expect(page).to have_content('bigbang@mail.com') + expect(page).to have_content('Big Bang') end it "should change user entry" do @simple_user.reload - @simple_user.name.should == "Big Bang" - @simple_user.is_admin?.should be_true + expect(@simple_user.name).to eq('Big Bang') + expect(@simple_user.is_admin?).to be_truthy end end end diff --git a/spec/features/admin/security_spec.rb b/spec/features/admin/security_spec.rb index 21b0d8b965..175fa9d464 100644 --- a/spec/features/admin/security_spec.rb +++ b/spec/features/admin/security_spec.rb @@ -2,26 +2,26 @@ require 'spec_helper' describe "Admin::Projects", feature: true do describe "GET /admin/projects" do - subject { admin_projects_path } + subject { admin_namespaces_projects_path } - it { should be_allowed_for :admin } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /admin/users" do subject { admin_users_path } - it { should be_allowed_for :admin } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /admin/hooks" do subject { admin_hooks_path } - it { should be_allowed_for :admin } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end end diff --git a/spec/features/atom/dashboard_issues_spec.rb b/spec/features/atom/dashboard_issues_spec.rb index 9fd2933b2c..b710cb3c72 100644 --- a/spec/features/atom/dashboard_issues_spec.rb +++ b/spec/features/atom/dashboard_issues_spec.rb @@ -17,12 +17,13 @@ describe "Dashboard Issues Feed", feature: true do it "should render atom feed via private token" do visit issues_dashboard_path(:atom, private_token: user.private_token) - page.response_headers['Content-Type'].should have_content("application/atom+xml") - page.body.should have_selector("title", text: "#{user.name} issues") - page.body.should have_selector("author email", text: issue1.author_email) - page.body.should have_selector("entry summary", text: issue1.title) - page.body.should have_selector("author email", text: issue2.author_email) - page.body.should have_selector("entry summary", text: issue2.title) + expect(response_headers['Content-Type']). + to have_content('application/atom+xml') + expect(body).to have_selector('title', text: "#{user.name} issues") + expect(body).to have_selector('author email', text: issue1.author_email) + expect(body).to have_selector('entry summary', text: issue1.title) + expect(body).to have_selector('author email', text: issue2.author_email) + expect(body).to have_selector('entry summary', text: issue2.title) end end end diff --git a/spec/features/atom/dashboard_spec.rb b/spec/features/atom/dashboard_spec.rb index e5d9f8ab5d..ad157d742f 100644 --- a/spec/features/atom/dashboard_spec.rb +++ b/spec/features/atom/dashboard_spec.rb @@ -2,13 +2,43 @@ require 'spec_helper' describe "Dashboard Feed", feature: true do describe "GET /" do - let!(:user) { create(:user) } + let!(:user) { create(:user, name: "Jonh") } context "projects atom feed via private token" do it "should render projects atom feed" do visit dashboard_path(:atom, private_token: user.private_token) - page.body.should have_selector("feed title") + expect(body).to have_selector('feed title') + end + end + + context 'feed content' do + let(:project) { create(:project) } + let(:issue) { create(:issue, project: project, author: user, description: '') } + let(:note) { create(:note, noteable: issue, author: user, note: 'Bug confirmed', project: project) } + + before do + project.team << [user, :master] + issue_event(issue, user) + note_event(note, user) + visit dashboard_path(:atom, private_token: user.private_token) + end + + it "should have issue opened event" do + expect(body).to have_content("#{user.name} opened issue ##{issue.iid}") + end + + it "should have issue comment event" do + expect(body). + to have_content("#{user.name} commented on issue ##{issue.iid}") end end end + + def issue_event(issue, user) + EventCreateService.new.open_issue(issue, user) + end + + def note_event(note, user) + EventCreateService.new.leave_note(note, user) + end end diff --git a/spec/features/atom/issues_spec.rb b/spec/features/atom/issues_spec.rb index 6ff03ec862..baa7814e96 100644 --- a/spec/features/atom/issues_spec.rb +++ b/spec/features/atom/issues_spec.rb @@ -1,33 +1,36 @@ require 'spec_helper' -describe "Issues Feed", feature: true do - describe "GET /issues" do +describe 'Issues Feed', feature: true do + describe 'GET /issues' do let!(:user) { create(:user) } let!(:project) { create(:project) } let!(:issue) { create(:issue, author: user, project: project) } before { project.team << [user, :developer] } - context "when authenticated" do - it "should render atom feed" do + context 'when authenticated' do + it 'should render atom feed' do login_with user - visit project_issues_path(project, :atom) + visit namespace_project_issues_path(project.namespace, project, :atom) - page.response_headers['Content-Type'].should have_content("application/atom+xml") - page.body.should have_selector("title", text: "#{project.name} issues") - page.body.should have_selector("author email", text: issue.author_email) - page.body.should have_selector("entry summary", text: issue.title) + expect(response_headers['Content-Type']). + to have_content('application/atom+xml') + expect(body).to have_selector('title', text: "#{project.name} issues") + expect(body).to have_selector('author email', text: issue.author_email) + expect(body).to have_selector('entry summary', text: issue.title) end end - context "when authenticated via private token" do - it "should render atom feed" do - visit project_issues_path(project, :atom, private_token: user.private_token) + context 'when authenticated via private token' do + it 'should render atom feed' do + visit namespace_project_issues_path(project.namespace, project, :atom, + private_token: user.private_token) - page.response_headers['Content-Type'].should have_content("application/atom+xml") - page.body.should have_selector("title", text: "#{project.name} issues") - page.body.should have_selector("author email", text: issue.author_email) - page.body.should have_selector("entry summary", text: issue.title) + expect(response_headers['Content-Type']). + to have_content('application/atom+xml') + expect(body).to have_selector('title', text: "#{project.name} issues") + expect(body).to have_selector('author email', text: issue.author_email) + expect(body).to have_selector('entry summary', text: issue.title) end end end diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb new file mode 100644 index 0000000000..770ac04c2c --- /dev/null +++ b/spec/features/atom/users_spec.rb @@ -0,0 +1,77 @@ +require 'spec_helper' + +describe "User Feed", feature: true do + describe "GET /" do + let!(:user) { create(:user) } + + context 'user atom feed via private token' do + it "should render user atom feed" do + visit user_path(user, :atom, private_token: user.private_token) + expect(body).to have_selector('feed title') + end + end + + context 'feed content' do + let(:project) { create(:project) } + let(:issue) do + create(:issue, project: project, + author: user, description: "Houston, we have a bug!\n\n***\n\nI guess.") + end + let(:note) do + create(:note, noteable: issue, author: user, + note: 'Bug confirmed :+1:', project: project) + end + let(:merge_request) do + create(:merge_request, + title: 'Fix bug', author: user, + source_project: project, target_project: project, + description: "Here is the fix: ![an image](image.png)") + end + + before do + project.team << [user, :master] + issue_event(issue, user) + note_event(note, user) + merge_request_event(merge_request, user) + visit user_path(user, :atom, private_token: user.private_token) + end + + it 'should have issue opened event' do + expect(body).to have_content("#{safe_name} opened issue ##{issue.iid}") + end + + it 'should have issue comment event' do + expect(body). + to have_content("#{safe_name} commented on issue ##{issue.iid}") + end + + it 'should have XHTML summaries in issue descriptions' do + expect(body).to match /we have a bug!<\/p>\n\n
    \n\n

    I guess/ + end + + it 'should have XHTML summaries in notes' do + expect(body).to match /Bug confirmed ]*\/>/ + end + + it 'should have XHTML summaries in merge request descriptions' do + expect(body).to match /Here is the fix: ]*\/>/ + end + end + end + + def issue_event(issue, user) + EventCreateService.new.open_issue(issue, user) + end + + def note_event(note, user) + EventCreateService.new.leave_note(note, user) + end + + def merge_request_event(request, user) + EventCreateService.new.open_mr(request, user) + end + + def safe_name + html_escape(user.name) + end +end diff --git a/spec/features/gitlab_flavored_markdown_spec.rb b/spec/features/gitlab_flavored_markdown_spec.rb index 9f50d1c973..fca1a06eb8 100644 --- a/spec/features/gitlab_flavored_markdown_spec.rb +++ b/spec/features/gitlab_flavored_markdown_spec.rb @@ -23,27 +23,27 @@ describe "GitLab Flavored Markdown", feature: true do describe "for commits" do it "should render title in commits#index" do - visit project_commits_path(project, 'master', limit: 1) + visit namespace_project_commits_path(project.namespace, project, 'master', limit: 1) - page.should have_link("##{issue.iid}") + expect(page).to have_link("##{issue.iid}") end it "should render title in commits#show" do - visit project_commit_path(project, commit) + visit namespace_project_commit_path(project.namespace, project, commit) - page.should have_link("##{issue.iid}") + expect(page).to have_link("##{issue.iid}") end it "should render description in commits#show" do - visit project_commit_path(project, commit) + visit namespace_project_commit_path(project.namespace, project, commit) - page.should have_link("@#{fred.username}") + expect(page).to have_link("@#{fred.username}") end it "should render title in repositories#branches" do - visit project_branches_path(project) + visit namespace_project_branches_path(project.namespace, project) - page.should have_link("##{issue.iid}") + expect(page).to have_link("##{issue.iid}") end end @@ -62,21 +62,21 @@ describe "GitLab Flavored Markdown", feature: true do end it "should render subject in issues#index" do - visit project_issues_path(project) + visit namespace_project_issues_path(project.namespace, project) - page.should have_link("##{@other_issue.iid}") + expect(page).to have_link("##{@other_issue.iid}") end it "should render subject in issues#show" do - visit project_issue_path(project, @issue) + visit namespace_project_issue_path(project.namespace, project, @issue) - page.should have_link("##{@other_issue.iid}") + expect(page).to have_link("##{@other_issue.iid}") end it "should render details in issues#show" do - visit project_issue_path(project, @issue) + visit namespace_project_issue_path(project.namespace, project, @issue) - page.should have_link("@#{fred.username}") + expect(page).to have_link("@#{fred.username}") end end @@ -87,15 +87,15 @@ describe "GitLab Flavored Markdown", feature: true do end it "should render title in merge_requests#index" do - visit project_merge_requests_path(project) + visit namespace_project_merge_requests_path(project.namespace, project) - page.should have_link("##{issue.iid}") + expect(page).to have_link("##{issue.iid}") end it "should render title in merge_requests#show" do - visit project_merge_request_path(project, @merge_request) + visit namespace_project_merge_request_path(project.namespace, project, @merge_request) - page.should have_link("##{issue.iid}") + expect(page).to have_link("##{issue.iid}") end end @@ -109,21 +109,21 @@ describe "GitLab Flavored Markdown", feature: true do end it "should render title in milestones#index" do - visit project_milestones_path(project) + visit namespace_project_milestones_path(project.namespace, project) - page.should have_link("##{issue.iid}") + expect(page).to have_link("##{issue.iid}") end it "should render title in milestones#show" do - visit project_milestone_path(project, @milestone) + visit namespace_project_milestone_path(project.namespace, project, @milestone) - page.should have_link("##{issue.iid}") + expect(page).to have_link("##{issue.iid}") end it "should render description in milestones#show" do - visit project_milestone_path(project, @milestone) + visit namespace_project_milestone_path(project.namespace, project, @milestone) - page.should have_link("@#{fred.username}") + expect(page).to have_link("@#{fred.username}") end end end diff --git a/spec/features/help_pages_spec.rb b/spec/features/help_pages_spec.rb new file mode 100644 index 0000000000..8c6b669ce7 --- /dev/null +++ b/spec/features/help_pages_spec.rb @@ -0,0 +1,13 @@ +require 'spec_helper' + +describe 'Help Pages', feature: true do + describe 'Show SSH page' do + before do + login_as :user + end + it 'replace the variable $your_email with the email of the user' do + visit help_page_path('ssh', 'README') + expect(page).to have_content("ssh-keygen -t rsa -C \"#{@user.email}\"") + end + end +end diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb index f0daf08101..e5f33d5a25 100644 --- a/spec/features/issues_spec.rb +++ b/spec/features/issues_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' -describe "Issues", feature: true do +describe 'Issues', feature: true do + include SortingHelper + let(:project) { create(:project) } before do @@ -10,7 +12,7 @@ describe "Issues", feature: true do project.team << [[@user, user2], :developer] end - describe "Edit issue" do + describe 'Edit issue' do let!(:issue) do create(:issue, author: @user, @@ -19,34 +21,38 @@ describe "Issues", feature: true do end before do - visit project_issues_path(project) + visit namespace_project_issues_path(project.namespace, project) click_link "Edit" end - it "should open new issue popup" do - page.should have_content("Issue ##{issue.iid}") + it 'should open new issue popup' do + expect(page).to have_content("Issue ##{issue.iid}") end - describe "fill in" do + describe 'fill in' do before do - fill_in "issue_title", with: "bug 345" - fill_in "issue_description", with: "bug description" + fill_in 'issue_title', with: 'bug 345' + fill_in 'issue_description', with: 'bug description' end - it { expect { click_button "Save changes" }.to_not change {Issue.count} } + it 'does not change issue count' do + expect { + click_button 'Save changes' + }.to_not change { Issue.count } + end - it "should update issue fields" do - click_button "Save changes" + it 'should update issue fields' do + click_button 'Save changes' - page.should have_content @user.name - page.should have_content "bug 345" - page.should have_content project.name + expect(page).to have_content @user.name + expect(page).to have_content 'bug 345' + expect(page).to have_content project.name end end end - describe "Editing issue assignee" do + describe 'Editing issue assignee' do let!(:issue) do create(:issue, author: @user, @@ -54,23 +60,23 @@ describe "Issues", feature: true do project: project) end - it 'allows user to select unasigned', :js => true do - visit edit_project_issue_path(project, issue) + it 'allows user to select unasigned', js: true do + visit edit_namespace_project_issue_path(project.namespace, project, issue) - page.should have_content "Assign to #{@user.name}" + expect(page).to have_content "Assign to #{@user.name}" - page.first('#s2id_issue_assignee_id').click + first('#s2id_issue_assignee_id').click sleep 2 # wait for ajax stuff to complete - page.first('.user-result').click + first('.user-result').click - click_button "Save changes" + click_button 'Save changes' - page.should have_content "Assignee: Select assignee" - issue.reload.assignee.should be_nil + expect(page).to have_content 'Assignee: none' + expect(issue.reload.assignee).to be_nil end end - describe "Filter issue" do + describe 'Filter issue' do before do ['foobar', 'barbaz', 'gitlab'].each do |title| create(:issue, @@ -80,7 +86,7 @@ describe "Issues", feature: true do title: title) end - @issue = Issue.first # with title 'foobar' + @issue = Issue.find_by(title: 'foobar') @issue.milestone = create(:milestone, project: project) @issue.assignee = nil @issue.save @@ -88,75 +94,79 @@ describe "Issues", feature: true do let(:issue) { @issue } - it "should allow filtering by issues with no specified milestone" do - visit project_issues_path(project, milestone_id: '0') + it 'should allow filtering by issues with no specified milestone' do + visit namespace_project_issues_path(project.namespace, project, milestone_id: IssuableFinder::NONE) - page.should_not have_content 'foobar' - page.should have_content 'barbaz' - page.should have_content 'gitlab' + expect(page).not_to have_content 'foobar' + expect(page).to have_content 'barbaz' + expect(page).to have_content 'gitlab' end - it "should allow filtering by a specified milestone" do - visit project_issues_path(project, milestone_id: issue.milestone.id) + it 'should allow filtering by a specified milestone' do + visit namespace_project_issues_path(project.namespace, project, milestone_id: issue.milestone.id) - page.should have_content 'foobar' - page.should_not have_content 'barbaz' - page.should_not have_content 'gitlab' + expect(page).to have_content 'foobar' + expect(page).not_to have_content 'barbaz' + expect(page).not_to have_content 'gitlab' end - it "should allow filtering by issues with no specified assignee" do - visit project_issues_path(project, assignee_id: '0') + it 'should allow filtering by issues with no specified assignee' do + visit namespace_project_issues_path(project.namespace, project, assignee_id: IssuableFinder::NONE) - page.should have_content 'foobar' - page.should_not have_content 'barbaz' - page.should_not have_content 'gitlab' + expect(page).to have_content 'foobar' + expect(page).not_to have_content 'barbaz' + expect(page).not_to have_content 'gitlab' end - it "should allow filtering by a specified assignee" do - visit project_issues_path(project, assignee_id: @user.id) + it 'should allow filtering by a specified assignee' do + visit namespace_project_issues_path(project.namespace, project, assignee_id: @user.id) - page.should_not have_content 'foobar' - page.should have_content 'barbaz' - page.should have_content 'gitlab' + expect(page).not_to have_content 'foobar' + expect(page).to have_content 'barbaz' + expect(page).to have_content 'gitlab' end end describe 'filter issue' do titles = ['foo','bar','baz'] titles.each_with_index do |title, index| - let!(title.to_sym) { create(:issue, title: title, project: project, created_at: Time.now - (index * 60)) } + let!(title.to_sym) do + create(:issue, title: title, + project: project, + created_at: Time.now - (index * 60)) + end end let(:newer_due_milestone) { create(:milestone, due_date: '2013-12-11') } let(:later_due_milestone) { create(:milestone, due_date: '2013-12-12') } it 'sorts by newest' do - visit project_issues_path(project, sort: 'newest') + visit namespace_project_issues_path(project.namespace, project, sort: sort_value_recently_created) - first_issue.should include("foo") - last_issue.should include("baz") + expect(first_issue).to include('foo') + expect(last_issue).to include('baz') end it 'sorts by oldest' do - visit project_issues_path(project, sort: 'oldest') + visit namespace_project_issues_path(project.namespace, project, sort: sort_value_oldest_created) - first_issue.should include("baz") - last_issue.should include("foo") + expect(first_issue).to include('baz') + expect(last_issue).to include('foo') end it 'sorts by most recently updated' do baz.updated_at = Time.now + 100 baz.save - visit project_issues_path(project, sort: 'recently_updated') + visit namespace_project_issues_path(project.namespace, project, sort: sort_value_recently_updated) - first_issue.should include("baz") + expect(first_issue).to include('baz') end it 'sorts by least recently updated' do baz.updated_at = Time.now - 100 baz.save - visit project_issues_path(project, sort: 'last_updated') + visit namespace_project_issues_path(project.namespace, project, sort: sort_value_oldest_updated) - first_issue.should include("baz") + expect(first_issue).to include('baz') end describe 'sorting by milestone' do @@ -168,15 +178,15 @@ describe "Issues", feature: true do end it 'sorts by recently due milestone' do - visit project_issues_path(project, sort: 'milestone_due_soon') + visit namespace_project_issues_path(project.namespace, project, sort: sort_value_milestone_soon) - first_issue.should include("foo") + expect(first_issue).to include('foo') end it 'sorts by least recently due milestone' do - visit project_issues_path(project, sort: 'milestone_due_later') + visit namespace_project_issues_path(project.namespace, project, sort: sort_value_milestone_later) - first_issue.should include("bar") + expect(first_issue).to include('bar') end end @@ -191,11 +201,13 @@ describe "Issues", feature: true do end it 'sorts with a filter applied' do - visit project_issues_path(project, sort: 'oldest', assignee_id: user2.id) + visit namespace_project_issues_path(project.namespace, project, + sort: sort_value_oldest_created, + assignee_id: user2.id) - first_issue.should include("bar") - last_issue.should include("foo") - page.should_not have_content 'baz' + expect(first_issue).to include('bar') + expect(last_issue).to include('foo') + expect(page).not_to have_content 'baz' end end end @@ -206,13 +218,15 @@ describe "Issues", feature: true do context 'by autorized user' do it 'with dropdown menu' do - visit project_issue_path(project, issue) + visit namespace_project_issue_path(project.namespace, project, issue) - find('.edit-issue.inline-update #issue_assignee_id').set project.team.members.first.id + find('.edit-issue.inline-update #issue_assignee_id'). + set project.team.members.first.id click_button 'Update Issue' - page.should have_content "Assignee:" - page.has_select?('issue_assignee_id', :selected => project.team.members.first.name) + expect(page).to have_content 'Assignee:' + has_select?('issue_assignee_id', + selected: project.team.members.first.name) end end @@ -226,12 +240,12 @@ describe "Issues", feature: true do issue.save end - it "shows assignee text", js: true do + it 'shows assignee text', js: true do logout login_with guest - visit project_issue_path(project, issue) - page.should have_content issue.assignee.name + visit namespace_project_issue_path(project.namespace, project, issue) + expect(page).to have_content issue.assignee.name end end end @@ -243,13 +257,15 @@ describe "Issues", feature: true do context 'by authorized user' do it 'with dropdown menu' do - visit project_issue_path(project, issue) + visit namespace_project_issue_path(project.namespace, project, issue) - find('.edit-issue.inline-update').select(milestone.title, from: 'issue_milestone_id') + find('.edit-issue.inline-update'). + select(milestone.title, from: 'issue_milestone_id') click_button 'Update Issue' - page.should have_content "Milestone changed to #{milestone.title}" - page.has_select?('issue_assignee_id', :selected => milestone.title) + expect(page).to have_content "Milestone changed to #{milestone.title}" + expect(page).to have_content "Milestone: #{milestone.title}" + has_select?('issue_assignee_id', selected: milestone.title) end end @@ -262,12 +278,12 @@ describe "Issues", feature: true do issue.save end - it "shows milestone text", js: true do + it 'shows milestone text', js: true do logout login_with guest - visit project_issue_path(project, issue) - page.should have_content milestone.title + visit namespace_project_issue_path(project.namespace, project, issue) + expect(page).to have_content milestone.title end end @@ -280,25 +296,25 @@ describe "Issues", feature: true do end it 'allows user to remove assignee', :js => true do - visit project_issue_path(project, issue) - page.should have_content "Assignee: #{user2.name}" + visit namespace_project_issue_path(project.namespace, project, issue) + expect(page).to have_content "Assignee: #{user2.name}" - page.first('#s2id_issue_assignee_id').click + first('#s2id_issue_assignee_id').click sleep 2 # wait for ajax stuff to complete - page.first('.user-result').click + first('.user-result').click - page.should have_content "Assignee: Unassigned" + expect(page).to have_content 'Assignee: none' sleep 2 # wait for ajax stuff to complete - issue.reload.assignee.should be_nil + expect(issue.reload.assignee).to be_nil end end end def first_issue - all("ul.issues-list li").first.text + all('ul.issues-list li').first.text end def last_issue - all("ul.issues-list li").last.text + all('ul.issues-list li').last.text end end diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb index 47776ba7f3..c47368b1fd 100644 --- a/spec/features/notes_on_merge_requests_spec.rb +++ b/spec/features/notes_on_merge_requests_spec.rb @@ -3,197 +3,213 @@ require 'spec_helper' describe 'Comments' do include RepoHelpers - describe "On a merge request", js: true, feature: true do + describe 'On a merge request', js: true, feature: true do let!(:merge_request) { create(:merge_request) } let!(:project) { merge_request.source_project } - let!(:note) { create(:note_on_merge_request, :with_attachment, project: project) } + let!(:note) do + create(:note_on_merge_request, :with_attachment, project: project) + end before do login_as :admin - visit project_merge_request_path(project, merge_request) + visit namespace_project_merge_request_path(project.namespace, project, merge_request) end subject { page } - describe "the note form" do + describe 'the note form' do it 'should be valid' do - should have_css(".js-main-target-form", visible: true, count: 1) - find(".js-main-target-form input[type=submit]").value.should == "Add Comment" - within(".js-main-target-form") { should_not have_link("Cancel") } - within(".js-main-target-form") { should have_css(".js-note-preview-button", visible: false) } + is_expected.to have_css('.js-main-target-form', visible: true, count: 1) + expect(find('.js-main-target-form input[type=submit]').value). + to eq('Add Comment') + within('.js-main-target-form') do + expect(page).not_to have_link('Cancel') + end end - describe "with text" do + describe 'with text' do before do - within(".js-main-target-form") do - fill_in "note[note]", with: "This is awesome" + within('.js-main-target-form') do + fill_in 'note[note]', with: 'This is awesome' end end it 'should have enable submit button and preview button' do - within(".js-main-target-form") { should_not have_css(".js-comment-button[disabled]") } - within(".js-main-target-form") { should have_css(".js-note-preview-button", visible: true) } + within('.js-main-target-form') do + expect(page).not_to have_css('.js-comment-button[disabled]') + expect(page).to have_css('.js-md-preview-button', visible: true) + end end end end - describe "when posting a note" do + describe 'when posting a note' do before do - within(".js-main-target-form") do - fill_in "note[note]", with: "This is awsome!" - find(".js-note-preview-button").trigger("click") - click_button "Add Comment" + within('.js-main-target-form') do + fill_in 'note[note]', with: 'This is awsome!' + find('.js-md-preview-button').click + click_button 'Add Comment' end end it 'should be added and form reset' do - should have_content("This is awsome!") - within(".js-main-target-form") { should have_no_field("note[note]", with: "This is awesome!") } - within(".js-main-target-form") { should have_css(".js-note-preview", visible: false) } - within(".js-main-target-form") { should have_css(".js-note-text", visible: true) } + is_expected.to have_content('This is awsome!') + within('.js-main-target-form') do + expect(page).to have_no_field('note[note]', with: 'This is awesome!') + expect(page).to have_css('.js-md-preview', visible: :hidden) + end + within('.js-main-target-form') do + is_expected.to have_css('.js-note-text', visible: true) + end end end - describe "when editing a note", js: true do - it "should contain the hidden edit form" do - within("#note_#{note.id}") { should have_css(".note-edit-form", visible: false) } + describe 'when editing a note', js: true do + it 'should contain the hidden edit form' do + within("#note_#{note.id}") do + is_expected.to have_css('.note-edit-form', visible: false) + end end - describe "editing the note" do + describe 'editing the note' do before do find('.note').hover find(".js-note-edit").click end - it "should show the note edit form and hide the note body" do + it 'should show the note edit form and hide the note body' do within("#note_#{note.id}") do - find(".note-edit-form", visible: true).should be_visible - find(".note-text", visible: false).should_not be_visible + expect(find('.current-note-edit-form', visible: true)).to be_visible + expect(find('.note-edit-form', visible: true)).to be_visible + expect(find(:css, '.note-body > .note-text', visible: false)).not_to be_visible end end - it "should reset the edit note form textarea with the original content of the note if cancelled" do - find('.note').hover - find(".js-note-edit").click + # TODO: fix after 7.7 release + #it "should reset the edit note form textarea with the original content of the note if cancelled" do + #within(".current-note-edit-form") do + #fill_in "note[note]", with: "Some new content" + #find(".btn-cancel").click + #find(".js-note-text", visible: false).text.should == note.note + #end + #end - within(".note-edit-form") do - fill_in "note[note]", with: "Some new content" - find(".btn-cancel").click - find(".js-note-text", visible: false).text.should == note.note - end - end - - it "appends the edited at time to the note" do - find('.note').hover - find(".js-note-edit").click - - within(".note-edit-form") do - fill_in "note[note]", with: "Some new content" - find(".btn-save").click + it 'appends the edited at time to the note' do + within('.current-note-edit-form') do + fill_in 'note[note]', with: 'Some new content' + find('.btn-save').click end within("#note_#{note.id}") do - should have_css(".note-last-update small") - find(".note-last-update small").text.should match(/Edited less than a minute ago/) + is_expected.to have_css('.note_edited_ago') + expect(find('.note_edited_ago').text). + to match(/less than a minute ago/) end end end - describe "deleting an attachment" do + describe 'deleting an attachment' do before do find('.note').hover - find(".js-note-edit").click + find('.js-note-edit').click end - it "shows the delete link" do - within(".note-attachment") do - should have_css(".js-note-attachment-delete") + it 'shows the delete link' do + within('.note-attachment') do + is_expected.to have_css('.js-note-attachment-delete') end end - it "removes the attachment div and resets the edit form" do - find(".js-note-attachment-delete").click - should_not have_css(".note-attachment") - find(".note-edit-form", visible: false).should_not be_visible + it 'removes the attachment div and resets the edit form' do + find('.js-note-attachment-delete').click + is_expected.not_to have_css('.note-attachment') + expect(find('.current-note-edit-form', visible: false)). + not_to be_visible end end end end - describe "On a merge request diff", js: true, feature: true do + describe 'On a merge request diff', js: true, feature: true do let(:merge_request) { create(:merge_request) } let(:project) { merge_request.source_project } before do login_as :admin - visit diffs_project_merge_request_path(project, merge_request) + visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) end subject { page } - describe "when adding a note" do + describe 'when adding a note' do before do - find("a[data-line-code=\"#{line_code}\"]").click + click_diff_line end - describe "the notes holder" do - it { should have_css(".js-temp-notes-holder") } + describe 'the notes holder' do + it { is_expected.to have_css('.js-temp-notes-holder') } - it { within(".js-temp-notes-holder") { should have_css(".new_note") } } + it 'has .new_note css class' do + within('.js-temp-notes-holder') do + expect(subject).to have_css('.new_note') + end + end end - describe "the note form" do + describe 'the note form' do it "shouldn't add a second form for same row" do - find("a[data-line-code=\"#{line_code}\"]").click + click_diff_line - should have_css("tr[id='#{line_code}'] + .js-temp-notes-holder form", count: 1) + is_expected. + to have_css("tr[id='#{line_code}'] + .js-temp-notes-holder form", + count: 1) end - it "should be removed when canceled" do + it 'should be removed when canceled' do within(".diff-file form[rel$='#{line_code}']") do - find(".js-close-discussion-note-form").trigger("click") + find('.js-close-discussion-note-form').trigger('click') end - should have_no_css(".js-temp-notes-holder") + is_expected.to have_no_css('.js-temp-notes-holder') end end end - describe "with muliple note forms" do + describe 'with muliple note forms' do before do - find("a[data-line-code=\"#{line_code}\"]").click - find("a[data-line-code=\"#{line_code_2}\"]").click + click_diff_line + click_diff_line(line_code_2) end - it { should have_css(".js-temp-notes-holder", count: 2) } + it { is_expected.to have_css('.js-temp-notes-holder', count: 2) } - describe "previewing them separately" do + describe 'previewing them separately' do before do # add two separate texts and trigger previews on both within("tr[id='#{line_code}'] + .js-temp-notes-holder") do - fill_in "note[note]", with: "One comment on line 7" - find(".js-note-preview-button").trigger("click") + fill_in 'note[note]', with: 'One comment on line 7' + find('.js-md-preview-button').click end within("tr[id='#{line_code_2}'] + .js-temp-notes-holder") do - fill_in "note[note]", with: "Another comment on line 10" - find(".js-note-preview-button").trigger("click") + fill_in 'note[note]', with: 'Another comment on line 10' + find('.js-md-preview-button').click end end end - describe "posting a note" do + describe 'posting a note' do before do within("tr[id='#{line_code_2}'] + .js-temp-notes-holder") do - fill_in "note[note]", with: "Another comment on line 10" - click_button("Add Comment") + fill_in 'note[note]', with: 'Another comment on line 10' + click_button('Add Comment') end end it 'should be added as discussion' do - should have_content("Another comment on line 10") - should have_css(".notes_holder") - should have_css(".notes_holder .note", count: 1) - should have_link("Reply") + is_expected.to have_content('Another comment on line 10') + is_expected.to have_css('.notes_holder') + is_expected.to have_css('.notes_holder .note', count: 1) + is_expected.to have_button('Reply') end end end @@ -206,4 +222,9 @@ describe 'Comments' do def line_code_2 sample_compare.changes.last[:line_code] end + + def click_diff_line(data = nil) + data ||= line_code + find("button[data-line-code=\"#{data}\"]").click + end end diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb index bdf7b59114..3d36a3c02d 100644 --- a/spec/features/profile_spec.rb +++ b/spec/features/profile_spec.rb @@ -1,35 +1,35 @@ require 'spec_helper' -describe "Profile account page", feature: true do +describe 'Profile account page', feature: true do let(:user) { create(:user) } before do login_as :user end - describe "when signup is enabled" do + describe 'when signup is enabled' do before do - Gitlab.config.gitlab.stub(:signup_enabled).and_return(true) + ApplicationSetting.any_instance.stub(signup_enabled?: true) visit profile_account_path end - it { page.should have_content("Remove account") } + it { expect(page).to have_content('Remove account') } - it "should delete the account" do - expect { click_link "Delete account" }.to change {User.count}.by(-1) - current_path.should == new_user_session_path + it 'should delete the account' do + expect { click_link 'Delete account' }.to change { User.count }.by(-1) + expect(current_path).to eq(new_user_session_path) end end - describe "when signup is disabled" do + describe 'when signup is disabled' do before do - Gitlab.config.gitlab.stub(:signup_enabled).and_return(false) + ApplicationSetting.any_instance.stub(signup_enabled?: false) visit profile_account_path end - it "should not have option to remove account" do - page.should_not have_content("Remove account") - current_path.should == profile_account_path + it 'should not have option to remove account' do + expect(page).not_to have_content('Remove account') + expect(current_path).to eq(profile_account_path) end end end diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb index 524c4d5fa2..cae11be7cd 100644 --- a/spec/features/projects_spec.rb +++ b/spec/features/projects_spec.rb @@ -1,17 +1,32 @@ require 'spec_helper' -describe "Projects", feature: true do +describe "Projects", feature: true, js: true do before { login_as :user } describe "DELETE /projects/:id" do before do @project = create(:project, namespace: @user.namespace) @project.team << [@user, :master] - visit edit_project_path(@project) + visit edit_namespace_project_path(@project.namespace, @project) end - it "should be correct path" do - expect { click_link "Remove project" }.to change {Project.count}.by(-1) + it "should remove project" do + expect { remove_project }.to change {Project.count}.by(-1) + end + + it 'should delete the project from disk' do + expect(GitlabShellWorker).to( + receive(:perform_async).with(:remove_repository, + /#{@project.path_with_namespace}/) + ).twice + + remove_project end end + + def remove_project + click_link "Remove project" + fill_in 'confirm_name_input', with: @project.path + click_button 'Confirm' + end end diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb index cce9f06cb6..73987739a7 100644 --- a/spec/features/search_spec.rb +++ b/spec/features/search_spec.rb @@ -14,7 +14,7 @@ describe "Search", feature: true do end it "should show project in search results" do - page.should have_content @project.name + expect(page).to have_content @project.name end end diff --git a/spec/features/security/dashboard_access_spec.rb b/spec/features/security/dashboard_access_spec.rb index 1cca82cef6..67238e3ab7 100644 --- a/spec/features/security/dashboard_access_spec.rb +++ b/spec/features/security/dashboard_access_spec.rb @@ -4,52 +4,60 @@ describe "Dashboard access", feature: true do describe "GET /dashboard" do subject { dashboard_path } - it { should be_allowed_for :admin } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /dashboard/issues" do subject { issues_dashboard_path } - it { should be_allowed_for :admin } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /dashboard/merge_requests" do subject { merge_requests_dashboard_path } - it { should be_allowed_for :admin } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } end - describe "GET /dashboard/projects" do - subject { projects_dashboard_path } + describe "GET /dashboard/projects/starred" do + subject { starred_dashboard_projects_path } - it { should be_allowed_for :admin } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /help" do subject { help_path } - it { should be_allowed_for :admin } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /projects/new" do - it { new_project_path.should be_allowed_for :admin } - it { new_project_path.should be_allowed_for :user } - it { new_project_path.should be_denied_for :visitor } + it { expect(new_project_path).to be_allowed_for :admin } + it { expect(new_project_path).to be_allowed_for :user } + it { expect(new_project_path).to be_denied_for :visitor } end describe "GET /groups/new" do - it { new_group_path.should be_allowed_for :admin } - it { new_group_path.should be_allowed_for :user } - it { new_group_path.should be_denied_for :visitor } + it { expect(new_group_path).to be_allowed_for :admin } + it { expect(new_group_path).to be_allowed_for :user } + it { expect(new_group_path).to be_denied_for :visitor } + end + + describe "GET /profile/groups" do + subject { dashboard_groups_path } + + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } end end diff --git a/spec/features/security/group/group_access_spec.rb b/spec/features/security/group/group_access_spec.rb index 44de499e6d..6379314945 100644 --- a/spec/features/security/group/group_access_spec.rb +++ b/spec/features/security/group/group_access_spec.rb @@ -2,9 +2,9 @@ require 'spec_helper' describe "Group access", feature: true do describe "GET /projects/new" do - it { new_group_path.should be_allowed_for :admin } - it { new_group_path.should be_allowed_for :user } - it { new_group_path.should be_denied_for :visitor } + it { expect(new_group_path).to be_allowed_for :admin } + it { expect(new_group_path).to be_allowed_for :user } + it { expect(new_group_path).to be_denied_for :visitor } end describe "Group" do @@ -26,73 +26,73 @@ describe "Group access", feature: true do describe "GET /groups/:path" do subject { group_path(group) } - it { should be_allowed_for owner } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /groups/:path/issues" do subject { issues_group_path(group) } - it { should be_allowed_for owner } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /groups/:path/merge_requests" do subject { merge_requests_group_path(group) } - it { should be_allowed_for owner } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end - describe "GET /groups/:path/members" do - subject { members_group_path(group) } + describe "GET /groups/:path/group_members" do + subject { group_group_members_path(group) } - it { should be_allowed_for owner } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /groups/:path/edit" do subject { edit_group_path(group) } - it { should be_allowed_for owner } - it { should be_denied_for master } - it { should be_denied_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_denied_for master } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /groups/:path/projects" do subject { projects_group_path(group) } - it { should be_allowed_for owner } - it { should be_denied_for master } - it { should be_denied_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_denied_for master } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end end end diff --git a/spec/features/security/group/internal_group_access_spec.rb b/spec/features/security/group/internal_group_access_spec.rb index da5c6eb4e9..d17a7412e4 100644 --- a/spec/features/security/group/internal_group_access_spec.rb +++ b/spec/features/security/group/internal_group_access_spec.rb @@ -22,61 +22,61 @@ describe "Group with internal project access", feature: true do describe "GET /groups/:path" do subject { group_path(group) } - it { should be_allowed_for owner } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /groups/:path/issues" do subject { issues_group_path(group) } - it { should be_allowed_for owner } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /groups/:path/merge_requests" do subject { merge_requests_group_path(group) } - it { should be_allowed_for owner } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } end - describe "GET /groups/:path/members" do - subject { members_group_path(group) } + describe "GET /groups/:path/group_members" do + subject { group_group_members_path(group) } - it { should be_allowed_for owner } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /groups/:path/edit" do subject { edit_group_path(group) } - it { should be_allowed_for owner } - it { should be_denied_for master } - it { should be_denied_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_denied_for master } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end end end diff --git a/spec/features/security/group/mixed_group_access_spec.rb b/spec/features/security/group/mixed_group_access_spec.rb index c9889d9959..b3db7b5dea 100644 --- a/spec/features/security/group/mixed_group_access_spec.rb +++ b/spec/features/security/group/mixed_group_access_spec.rb @@ -23,61 +23,61 @@ describe "Group access", feature: true do describe "GET /groups/:path" do subject { group_path(group) } - it { should be_allowed_for owner } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_allowed_for :visitor } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } end describe "GET /groups/:path/issues" do subject { issues_group_path(group) } - it { should be_allowed_for owner } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_allowed_for :visitor } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } end describe "GET /groups/:path/merge_requests" do subject { merge_requests_group_path(group) } - it { should be_allowed_for owner } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_allowed_for :visitor } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } end - describe "GET /groups/:path/members" do - subject { members_group_path(group) } + describe "GET /groups/:path/group_members" do + subject { group_group_members_path(group) } - it { should be_allowed_for owner } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_allowed_for :visitor } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } end describe "GET /groups/:path/edit" do subject { edit_group_path(group) } - it { should be_allowed_for owner } - it { should be_denied_for master } - it { should be_denied_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_denied_for master } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end end end diff --git a/spec/features/security/group/public_group_access_spec.rb b/spec/features/security/group/public_group_access_spec.rb index 2e76ab154f..c16f0c0d1e 100644 --- a/spec/features/security/group/public_group_access_spec.rb +++ b/spec/features/security/group/public_group_access_spec.rb @@ -22,61 +22,61 @@ describe "Group with public project access", feature: true do describe "GET /groups/:path" do subject { group_path(group) } - it { should be_allowed_for owner } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_allowed_for :visitor } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } end describe "GET /groups/:path/issues" do subject { issues_group_path(group) } - it { should be_allowed_for owner } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_allowed_for :visitor } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } end describe "GET /groups/:path/merge_requests" do subject { merge_requests_group_path(group) } - it { should be_allowed_for owner } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_allowed_for :visitor } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } end - describe "GET /groups/:path/members" do - subject { members_group_path(group) } + describe "GET /groups/:path/group_members" do + subject { group_group_members_path(group) } - it { should be_allowed_for owner } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_allowed_for :visitor } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } end describe "GET /groups/:path/edit" do subject { edit_group_path(group) } - it { should be_allowed_for owner } - it { should be_denied_for master } - it { should be_denied_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for owner } + it { is_expected.to be_denied_for master } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end end end diff --git a/spec/features/security/profile_access_spec.rb b/spec/features/security/profile_access_spec.rb index 4efc0ffdcd..2512a9c0e3 100644 --- a/spec/features/security/profile_access_spec.rb +++ b/spec/features/security/profile_access_spec.rb @@ -1,76 +1,65 @@ require 'spec_helper' -describe "Users Security", feature: true do - describe "Project" do - before do - @u1 = create(:user) - end +describe "Profile access", feature: true do + before do + @u1 = create(:user) + end - describe "GET /login" do - it { new_user_session_path.should_not be_404_for :visitor } - end + describe "GET /login" do + it { expect(new_user_session_path).not_to be_404_for :visitor } + end - describe "GET /profile/keys" do - subject { profile_keys_path } + describe "GET /profile/keys" do + subject { profile_keys_path } - it { should be_allowed_for @u1 } - it { should be_allowed_for :admin } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } - end + it { is_expected.to be_allowed_for @u1 } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } + end - describe "GET /profile" do - subject { profile_path } + describe "GET /profile" do + subject { profile_path } - it { should be_allowed_for @u1 } - it { should be_allowed_for :admin } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } - end + it { is_expected.to be_allowed_for @u1 } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } + end - describe "GET /profile/account" do - subject { profile_account_path } + describe "GET /profile/account" do + subject { profile_account_path } - it { should be_allowed_for @u1 } - it { should be_allowed_for :admin } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } - end + it { is_expected.to be_allowed_for @u1 } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } + end - describe "GET /profile/design" do - subject { design_profile_path } + describe "GET /profile/design" do + subject { design_profile_path } - it { should be_allowed_for @u1 } - it { should be_allowed_for :admin } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } - end + it { is_expected.to be_allowed_for @u1 } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } + end - describe "GET /profile/history" do - subject { history_profile_path } + describe "GET /profile/history" do + subject { history_profile_path } - it { should be_allowed_for @u1 } - it { should be_allowed_for :admin } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } - end + it { is_expected.to be_allowed_for @u1 } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } + end - describe "GET /profile/notifications" do - subject { profile_notifications_path } + describe "GET /profile/notifications" do + subject { profile_notifications_path } - it { should be_allowed_for @u1 } - it { should be_allowed_for :admin } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } - end - - describe "GET /profile/groups" do - subject { profile_groups_path } - - it { should be_allowed_for @u1 } - it { should be_allowed_for :admin } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } - end + it { is_expected.to be_allowed_for @u1 } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } end end diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb index f86b3db32e..8d1bfd2522 100644 --- a/spec/features/security/project/internal_access_spec.rb +++ b/spec/features/security/project/internal_access_spec.rb @@ -18,207 +18,210 @@ describe "Internal Project Access", feature: true do describe "Project should be internal" do subject { project } - its(:internal?) { should be_true } + describe '#internal?' do + subject { super().internal? } + it { is_expected.to be_truthy } + end end describe "GET /:project_path" do - subject { project_path(project) } + subject { namespace_project_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/tree/master" do - subject { project_tree_path(project, project.repository.root_ref) } + subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/commits/master" do - subject { project_commits_path(project, project.repository.root_ref, limit: 1) } + subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/commit/:sha" do - subject { project_commit_path(project, project.repository.commit) } + subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/compare" do - subject { project_compare_index_path(project) } + subject { namespace_project_compare_index_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } end - describe "GET /:project_path/team" do - subject { project_team_index_path(project) } + describe "GET /:project_path/project_members" do + subject { namespace_project_project_members_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_denied_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/blob" do before do commit = project.repository.commit - path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob) }.first.name - @blob_path = project_blob_path(project, File.join(commit.id, path)) + path = '.gitignore' + @blob_path = namespace_project_blob_path(project.namespace, project, File.join(commit.id, path)) end - it { @blob_path.should be_allowed_for master } - it { @blob_path.should be_allowed_for reporter } - it { @blob_path.should be_allowed_for :admin } - it { @blob_path.should be_allowed_for guest } - it { @blob_path.should be_allowed_for :user } - it { @blob_path.should be_denied_for :visitor } + it { expect(@blob_path).to be_allowed_for master } + it { expect(@blob_path).to be_allowed_for reporter } + it { expect(@blob_path).to be_allowed_for :admin } + it { expect(@blob_path).to be_allowed_for guest } + it { expect(@blob_path).to be_allowed_for :user } + it { expect(@blob_path).to be_denied_for :visitor } end describe "GET /:project_path/edit" do - subject { edit_project_path(project) } + subject { edit_namespace_project_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_denied_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/deploy_keys" do - subject { project_deploy_keys_path(project) } + subject { namespace_project_deploy_keys_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_denied_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/issues" do - subject { project_issues_path(project) } + subject { namespace_project_issues_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/snippets" do - subject { project_snippets_path(project) } + subject { namespace_project_snippets_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/snippets/new" do - subject { new_project_snippet_path(project) } + subject { new_namespace_project_snippet_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/merge_requests" do - subject { project_merge_requests_path(project) } + subject { namespace_project_merge_requests_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/merge_requests/new" do - subject { new_project_merge_request_path(project) } + subject { new_namespace_project_merge_request_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_denied_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/branches" do - subject { project_branches_path(project) } + subject { namespace_project_branches_path(project.namespace, project) } before do # Speed increase - Project.any_instance.stub(:branches).and_return([]) + allow_any_instance_of(Project).to receive(:branches).and_return([]) end - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/tags" do - subject { project_tags_path(project) } + subject { namespace_project_tags_path(project.namespace, project) } before do # Speed increase - Project.any_instance.stub(:tags).and_return([]) + allow_any_instance_of(Project).to receive(:tags).and_return([]) end - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/hooks" do - subject { project_hooks_path(project) } + subject { namespace_project_hooks_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_denied_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end end diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index a27361f4d1..9021ff3318 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -18,185 +18,188 @@ describe "Private Project Access", feature: true do describe "Project should be private" do subject { project } - its(:private?) { should be_true } + describe '#private?' do + subject { super().private? } + it { is_expected.to be_truthy } + end end describe "GET /:project_path" do - subject { project_path(project) } + subject { namespace_project_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/tree/master" do - subject { project_tree_path(project, project.repository.root_ref) } + subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/commits/master" do - subject { project_commits_path(project, project.repository.root_ref, limit: 1) } + subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/commit/:sha" do - subject { project_commit_path(project, project.repository.commit) } + subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/compare" do - subject { project_compare_index_path(project) } + subject { namespace_project_compare_index_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end - describe "GET /:project_path/team" do - subject { project_team_index_path(project) } + describe "GET /:project_path/project_members" do + subject { namespace_project_project_members_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_denied_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/blob" do before do commit = project.repository.commit - path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob) }.first.name - @blob_path = project_blob_path(project, File.join(commit.id, path)) + path = '.gitignore' + @blob_path = namespace_project_blob_path(project.namespace, project, File.join(commit.id, path)) end - it { @blob_path.should be_allowed_for master } - it { @blob_path.should be_allowed_for reporter } - it { @blob_path.should be_allowed_for :admin } - it { @blob_path.should be_denied_for guest } - it { @blob_path.should be_denied_for :user } - it { @blob_path.should be_denied_for :visitor } + it { expect(@blob_path).to be_allowed_for master } + it { expect(@blob_path).to be_allowed_for reporter } + it { expect(@blob_path).to be_allowed_for :admin } + it { expect(@blob_path).to be_denied_for guest } + it { expect(@blob_path).to be_denied_for :user } + it { expect(@blob_path).to be_denied_for :visitor } end describe "GET /:project_path/edit" do - subject { edit_project_path(project) } + subject { edit_namespace_project_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_denied_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/deploy_keys" do - subject { project_deploy_keys_path(project) } + subject { namespace_project_deploy_keys_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_denied_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/issues" do - subject { project_issues_path(project) } + subject { namespace_project_issues_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/snippets" do - subject { project_snippets_path(project) } + subject { namespace_project_snippets_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/merge_requests" do - subject { project_merge_requests_path(project) } + subject { namespace_project_merge_requests_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/branches" do - subject { project_branches_path(project) } + subject { namespace_project_branches_path(project.namespace, project) } before do # Speed increase - Project.any_instance.stub(:branches).and_return([]) + allow_any_instance_of(Project).to receive(:branches).and_return([]) end - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/tags" do - subject { project_tags_path(project) } + subject { namespace_project_tags_path(project.namespace, project) } before do # Speed increase - Project.any_instance.stub(:tags).and_return([]) + allow_any_instance_of(Project).to receive(:tags).and_return([]) end - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/hooks" do - subject { project_hooks_path(project) } + subject { namespace_project_hooks_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_denied_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end end diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index f114965bd4..6ec190ed77 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -23,207 +23,210 @@ describe "Public Project Access", feature: true do describe "Project should be public" do subject { project } - its(:public?) { should be_true } + describe '#public?' do + subject { super().public? } + it { is_expected.to be_truthy } + end end describe "GET /:project_path" do - subject { project_path(project) } + subject { namespace_project_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_allowed_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } end describe "GET /:project_path/tree/master" do - subject { project_tree_path(project, project.repository.root_ref) } + subject { namespace_project_tree_path(project.namespace, project, project.repository.root_ref) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_allowed_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } end describe "GET /:project_path/commits/master" do - subject { project_commits_path(project, project.repository.root_ref, limit: 1) } + subject { namespace_project_commits_path(project.namespace, project, project.repository.root_ref, limit: 1) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_allowed_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } end describe "GET /:project_path/commit/:sha" do - subject { project_commit_path(project, project.repository.commit) } + subject { namespace_project_commit_path(project.namespace, project, project.repository.commit) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_allowed_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } end describe "GET /:project_path/compare" do - subject { project_compare_index_path(project) } + subject { namespace_project_compare_index_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_allowed_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } end - describe "GET /:project_path/team" do - subject { project_team_index_path(project) } + describe "GET /:project_path/project_members" do + subject { namespace_project_project_members_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_denied_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/blob" do before do commit = project.repository.commit - path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob) }.first.name - @blob_path = project_blob_path(project, File.join(commit.id, path)) + path = '.gitignore' + @blob_path = namespace_project_blob_path(project.namespace, project, File.join(commit.id, path)) end - it { @blob_path.should be_allowed_for master } - it { @blob_path.should be_allowed_for reporter } - it { @blob_path.should be_allowed_for :admin } - it { @blob_path.should be_allowed_for guest } - it { @blob_path.should be_allowed_for :user } - it { @blob_path.should be_allowed_for :visitor } + it { expect(@blob_path).to be_allowed_for master } + it { expect(@blob_path).to be_allowed_for reporter } + it { expect(@blob_path).to be_allowed_for :admin } + it { expect(@blob_path).to be_allowed_for guest } + it { expect(@blob_path).to be_allowed_for :user } + it { expect(@blob_path).to be_allowed_for :visitor } end describe "GET /:project_path/edit" do - subject { edit_project_path(project) } + subject { edit_namespace_project_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_denied_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/deploy_keys" do - subject { project_deploy_keys_path(project) } + subject { namespace_project_deploy_keys_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_denied_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/issues" do - subject { project_issues_path(project) } + subject { namespace_project_issues_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_allowed_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } end describe "GET /:project_path/snippets" do - subject { project_snippets_path(project) } + subject { namespace_project_snippets_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_allowed_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } end describe "GET /:project_path/snippets/new" do - subject { new_project_snippet_path(project) } + subject { new_namespace_project_snippet_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/merge_requests" do - subject { project_merge_requests_path(project) } + subject { namespace_project_merge_requests_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_allowed_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } end describe "GET /:project_path/merge_requests/new" do - subject { new_project_merge_request_path(project) } + subject { new_namespace_project_merge_request_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_denied_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end describe "GET /:project_path/branches" do - subject { project_branches_path(project) } + subject { namespace_project_branches_path(project.namespace, project) } before do # Speed increase - Project.any_instance.stub(:branches).and_return([]) + allow_any_instance_of(Project).to receive(:branches).and_return([]) end - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_allowed_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } end describe "GET /:project_path/tags" do - subject { project_tags_path(project) } + subject { namespace_project_tags_path(project.namespace, project) } before do # Speed increase - Project.any_instance.stub(:tags).and_return([]) + allow_any_instance_of(Project).to receive(:tags).and_return([]) end - it { should be_allowed_for master } - it { should be_allowed_for reporter } - it { should be_allowed_for :admin } - it { should be_allowed_for guest } - it { should be_allowed_for :user } - it { should be_allowed_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_allowed_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_allowed_for guest } + it { is_expected.to be_allowed_for :user } + it { is_expected.to be_allowed_for :visitor } end describe "GET /:project_path/hooks" do - subject { project_hooks_path(project) } + subject { namespace_project_hooks_path(project.namespace, project) } - it { should be_allowed_for master } - it { should be_denied_for reporter } - it { should be_allowed_for :admin } - it { should be_denied_for guest } - it { should be_denied_for :user } - it { should be_denied_for :visitor } + it { is_expected.to be_allowed_for master } + it { is_expected.to be_denied_for reporter } + it { is_expected.to be_allowed_for :admin } + it { is_expected.to be_denied_for guest } + it { is_expected.to be_denied_for :user } + it { is_expected.to be_denied_for :visitor } end end diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb index 7b831c4861..4cfaab03ca 100644 --- a/spec/features/users_spec.rb +++ b/spec/features/users_spec.rb @@ -1,19 +1,37 @@ require 'spec_helper' -describe 'Users', feature: true do - describe "GET /users/sign_up" do - before do - Gitlab.config.gitlab.stub(:signup_enabled).and_return(true) - end +feature 'Users' do + around do |ex| + old_url_options = Rails.application.routes.default_url_options + Rails.application.routes.default_url_options = { host: 'example.foo' } + ex.run + Rails.application.routes.default_url_options = old_url_options + end - it "should create a new user account" do - visit new_user_registration_path - fill_in "user_name", with: "Name Surname" - fill_in "user_username", with: "Great" - fill_in "user_email", with: "name@mail.com" - fill_in "user_password", with: "password1234" - fill_in "user_password_confirmation", with: "password1234" - expect { click_button "Sign up" }.to change {User.count}.by(1) - end + scenario 'GET /users/sign_in creates a new user account' do + visit new_user_session_path + fill_in 'user_name', with: 'Name Surname' + fill_in 'user_username', with: 'Great' + fill_in 'user_email', with: 'name@mail.com' + fill_in 'user_password_sign_up', with: 'password1234' + expect { click_button 'Sign up' }.to change { User.count }.by(1) + end + + scenario 'Successful user signin invalidates password reset token' do + user = create(:user) + expect(user.reset_password_token).to be_nil + + visit new_user_password_path + fill_in 'user_email', with: user.email + click_button 'Reset password' + + user.reload + expect(user.reset_password_token).not_to be_nil + + login_with(user) + expect(current_path).to eq root_path + + user.reload + expect(user.reset_password_token).to be_nil end end diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 7489e56f42..479fa95038 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -5,9 +5,10 @@ describe IssuesFinder do let(:user2) { create :user } let(:project1) { create(:project) } let(:project2) { create(:project) } - let(:issue1) { create(:issue, assignee: user, project: project1) } - let(:issue2) { create(:issue, assignee: user, project: project2) } - let(:issue3) { create(:issue, assignee: user2, project: project2) } + let(:milestone) { create(:milestone, project: project1) } + let(:issue1) { create(:issue, author: user, assignee: user, project: project1, milestone: milestone) } + let(:issue2) { create(:issue, author: user, assignee: user, project: project2) } + let(:issue3) { create(:issue, author: user2, assignee: user2, project: project2) } before do project1.team << [user, :master] @@ -22,37 +23,59 @@ describe IssuesFinder do issue3 end - it 'should filter by all' do - params = { scope: "all", state: 'opened' } - issues = IssuesFinder.new.execute(user, params) - issues.size.should == 3 + context 'scope: all' do + it 'should filter by all' do + params = { scope: "all", state: 'opened' } + issues = IssuesFinder.new.execute(user, params) + expect(issues.size).to eq(3) + end + + it 'should filter by assignee id' do + params = { scope: "all", assignee_id: user.id, state: 'opened' } + issues = IssuesFinder.new.execute(user, params) + expect(issues.size).to eq(2) + end + + it 'should filter by author id' do + params = { scope: "all", author_id: user2.id, state: 'opened' } + issues = IssuesFinder.new.execute(user, params) + expect(issues).to eq([issue3]) + end + + it 'should filter by milestone id' do + params = { scope: "all", milestone_id: milestone.id, state: 'opened' } + issues = IssuesFinder.new.execute(user, params) + expect(issues).to eq([issue1]) + end + + it 'should be empty for unauthorized user' do + params = { scope: "all", state: 'opened' } + issues = IssuesFinder.new.execute(nil, params) + expect(issues.size).to be_zero + end + + it 'should not include unauthorized issues' do + params = { scope: "all", state: 'opened' } + issues = IssuesFinder.new.execute(user2, params) + expect(issues.size).to eq(2) + expect(issues).not_to include(issue1) + expect(issues).to include(issue2) + expect(issues).to include(issue3) + end end - it 'should filter by assignee' do - params = { scope: "assigned-to-me", state: 'opened' } - issues = IssuesFinder.new.execute(user, params) - issues.size.should == 2 - end + context 'personal scope' do + it 'should filter by assignee' do + params = { scope: "assigned-to-me", state: 'opened' } + issues = IssuesFinder.new.execute(user, params) + expect(issues.size).to eq(2) + end - it 'should filter by project' do - params = { scope: "assigned-to-me", state: 'opened', project_id: project1.id } - issues = IssuesFinder.new.execute(user, params) - issues.size.should == 1 - end - - it 'should be empty for unauthorized user' do - params = { scope: "all", state: 'opened' } - issues = IssuesFinder.new.execute(nil, params) - issues.size.should be_zero - end - - it 'should not include unauthorized issues' do - params = { scope: "all", state: 'opened' } - issues = IssuesFinder.new.execute(user2, params) - issues.size.should == 2 - issues.should_not include(issue1) - issues.should include(issue2) - issues.should include(issue3) + it 'should filter by project' do + params = { scope: "assigned-to-me", state: 'opened', project_id: project1.id } + issues = IssuesFinder.new.execute(user, params) + expect(issues.size).to eq(1) + end end end end diff --git a/spec/finders/merge_requests_finder_spec.rb b/spec/finders/merge_requests_finder_spec.rb index 94b4d4c4ff..8536377a7f 100644 --- a/spec/finders/merge_requests_finder_spec.rb +++ b/spec/finders/merge_requests_finder_spec.rb @@ -21,13 +21,13 @@ describe MergeRequestsFinder do it 'should filter by scope' do params = { scope: 'authored', state: 'opened' } merge_requests = MergeRequestsFinder.new.execute(user, params) - merge_requests.size.should == 2 + expect(merge_requests.size).to eq(2) end it 'should filter by project' do params = { project_id: project1.id, scope: 'authored', state: 'opened' } merge_requests = MergeRequestsFinder.new.execute(user, params) - merge_requests.size.should == 1 + expect(merge_requests.size).to eq(1) end end end diff --git a/spec/finders/notes_finder_spec.rb b/spec/finders/notes_finder_spec.rb index 4f8a5f909d..c83824b900 100644 --- a/spec/finders/notes_finder_spec.rb +++ b/spec/finders/notes_finder_spec.rb @@ -21,7 +21,7 @@ describe NotesFinder do it 'should find all notes' do notes = NotesFinder.new.execute(project, user, params) - notes.size.should eq(2) + expect(notes.size).to eq(2) end it 'should raise an exception for an invalid target_type' do @@ -32,7 +32,7 @@ describe NotesFinder do it 'filters out old notes' do note2.update_attribute(:updated_at, 2.hours.ago) notes = NotesFinder.new.execute(project, user, params) - notes.should eq([note1]) + expect(notes).to eq([note1]) end end end diff --git a/spec/finders/projects_finder_spec.rb b/spec/finders/projects_finder_spec.rb index 6e3ae4d615..2ab71b0596 100644 --- a/spec/finders/projects_finder_spec.rb +++ b/spec/finders/projects_finder_spec.rb @@ -12,19 +12,19 @@ describe ProjectsFinder do context 'non authenticated' do subject { ProjectsFinder.new.execute(nil, group: group) } - it { should include(project1) } - it { should_not include(project2) } - it { should_not include(project3) } - it { should_not include(project4) } + it { is_expected.to include(project1) } + it { is_expected.not_to include(project2) } + it { is_expected.not_to include(project3) } + it { is_expected.not_to include(project4) } end context 'authenticated' do subject { ProjectsFinder.new.execute(user, group: group) } - it { should include(project1) } - it { should include(project2) } - it { should_not include(project3) } - it { should_not include(project4) } + it { is_expected.to include(project1) } + it { is_expected.to include(project2) } + it { is_expected.not_to include(project3) } + it { is_expected.not_to include(project4) } end context 'authenticated, project member' do @@ -32,10 +32,10 @@ describe ProjectsFinder do subject { ProjectsFinder.new.execute(user, group: group) } - it { should include(project1) } - it { should include(project2) } - it { should include(project3) } - it { should_not include(project4) } + it { is_expected.to include(project1) } + it { is_expected.to include(project2) } + it { is_expected.to include(project3) } + it { is_expected.not_to include(project4) } end context 'authenticated, group member' do @@ -43,9 +43,9 @@ describe ProjectsFinder do subject { ProjectsFinder.new.execute(user, group: group) } - it { should include(project1) } - it { should include(project2) } - it { should include(project3) } - it { should include(project4) } + it { is_expected.to include(project1) } + it { is_expected.to include(project2) } + it { is_expected.to include(project3) } + it { is_expected.to include(project4) } end end diff --git a/spec/finders/snippets_finder_spec.rb b/spec/finders/snippets_finder_spec.rb new file mode 100644 index 0000000000..1b4ffc2d71 --- /dev/null +++ b/spec/finders/snippets_finder_spec.rb @@ -0,0 +1,101 @@ +require 'spec_helper' + +describe SnippetsFinder do + let(:user) { create :user } + let(:user1) { create :user } + let(:group) { create :group } + + let(:project1) { create(:empty_project, :public, group: group) } + let(:project2) { create(:empty_project, :private, group: group) } + + + context ':all filter' do + before do + @snippet1 = create(:personal_snippet, visibility_level: Snippet::PRIVATE) + @snippet2 = create(:personal_snippet, visibility_level: Snippet::INTERNAL) + @snippet3 = create(:personal_snippet, visibility_level: Snippet::PUBLIC) + end + + it "returns all private and internal snippets" do + snippets = SnippetsFinder.new.execute(user, filter: :all) + expect(snippets).to include(@snippet2, @snippet3) + expect(snippets).not_to include(@snippet1) + end + + it "returns all public snippets" do + snippets = SnippetsFinder.new.execute(nil, filter: :all) + expect(snippets).to include(@snippet3) + expect(snippets).not_to include(@snippet1, @snippet2) + end + end + + context ':by_user filter' do + before do + @snippet1 = create(:personal_snippet, visibility_level: Snippet::PRIVATE, author: user) + @snippet2 = create(:personal_snippet, visibility_level: Snippet::INTERNAL, author: user) + @snippet3 = create(:personal_snippet, visibility_level: Snippet::PUBLIC, author: user) + end + + it "returns all public and internal snippets" do + snippets = SnippetsFinder.new.execute(user1, filter: :by_user, user: user) + expect(snippets).to include(@snippet2, @snippet3) + expect(snippets).not_to include(@snippet1) + end + + it "returns internal snippets" do + snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_internal") + expect(snippets).to include(@snippet2) + expect(snippets).not_to include(@snippet1, @snippet3) + end + + it "returns private snippets" do + snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_private") + expect(snippets).to include(@snippet1) + expect(snippets).not_to include(@snippet2, @snippet3) + end + + it "returns public snippets" do + snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user, scope: "are_public") + expect(snippets).to include(@snippet3) + expect(snippets).not_to include(@snippet1, @snippet2) + end + + it "returns all snippets" do + snippets = SnippetsFinder.new.execute(user, filter: :by_user, user: user) + expect(snippets).to include(@snippet1, @snippet2, @snippet3) + end + + it "returns only public snippets if unauthenticated user" do + snippets = SnippetsFinder.new.execute(nil, filter: :by_user, user: user) + expect(snippets).to include(@snippet3) + expect(snippets).not_to include(@snippet2, @snippet1) + end + + end + + context 'by_project filter' do + before do + @snippet1 = create(:project_snippet, visibility_level: Snippet::PRIVATE, project: project1) + @snippet2 = create(:project_snippet, visibility_level: Snippet::INTERNAL, project: project1) + @snippet3 = create(:project_snippet, visibility_level: Snippet::PUBLIC, project: project1) + end + + it "returns public snippets for unauthorized user" do + snippets = SnippetsFinder.new.execute(nil, filter: :by_project, project: project1) + expect(snippets).to include(@snippet3) + expect(snippets).not_to include(@snippet1, @snippet2) + end + + it "returns public and internal snippets for none project members" do + snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1) + expect(snippets).to include(@snippet2, @snippet3) + expect(snippets).not_to include(@snippet1) + end + + it "returns all snippets for project members" do + project1.team << [user, :developer] + snippets = SnippetsFinder.new.execute(user, filter: :by_project, project: project1) + expect(snippets).to include(@snippet1, @snippet2, @snippet3) + end + end +end diff --git a/spec/fixtures/GoogleCodeProjectHosting.json b/spec/fixtures/GoogleCodeProjectHosting.json new file mode 100644 index 0000000000..d05e77271a --- /dev/null +++ b/spec/fixtures/GoogleCodeProjectHosting.json @@ -0,0 +1,407 @@ +{ + "kind" : "projecthosting#user", + "id" : "@WRRVSlFXARlCVgB6", + "projects" : [ { + "kind" : "projecthosting#project", + "name" : "pmn", + "externalId" : "pmn", + "htmlLink" : "/p/pmn/", + "summary" : "Shows an icon in the system tray when you have new emails", + "description" : "IMAP client that shows an icon in the system tray when you have new emails.", + "labels" : [ "Mail" ], + "versionControlSystem" : "svn", + "repositoryUrls" : [ "https://pmn.googlecode.com/svn/" ], + "issuesConfig" : { + "kind" : "projecthosting#projectIssueConfig", + "statuses" : [ { + "status" : "New", + "meansOpen" : true, + "description" : "Issue has not had initial review yet" + }, { + "status" : "Accepted", + "meansOpen" : true, + "description" : "Problem reproduced / Need acknowledged" + }, { + "status" : "Started", + "meansOpen" : true, + "description" : "Work on this issue has begun" + }, { + "status" : "Fixed", + "meansOpen" : false, + "description" : "Developer made source code changes, QA should verify" + }, { + "status" : "Verified", + "meansOpen" : false, + "description" : "QA has verified that the fix worked" + }, { + "status" : "Invalid", + "meansOpen" : false, + "description" : "This was not a valid issue report" + }, { + "status" : "Duplicate", + "meansOpen" : false, + "description" : "This report duplicates an existing issue" + }, { + "status" : "WontFix", + "meansOpen" : false, + "description" : "We decided to not take action on this issue" + }, { + "status" : "Done", + "meansOpen" : false, + "description" : "The requested non-coding task was completed" + } ], + "labels" : [ { + "label" : "Type-Defect", + "description" : "Report of a software defect" + }, { + "label" : "Type-Enhancement", + "description" : "Request for enhancement" + }, { + "label" : "Type-Task", + "description" : "Work item that doesn't change the code or docs" + }, { + "label" : "Type-Review", + "description" : "Request for a source code review" + }, { + "label" : "Type-Other", + "description" : "Some other kind of issue" + }, { + "label" : "Priority-Critical", + "description" : "Must resolve in the specified milestone" + }, { + "label" : "Priority-High", + "description" : "Strongly want to resolve in the specified milestone" + }, { + "label" : "Priority-Medium", + "description" : "Normal priority" + }, { + "label" : "Priority-Low", + "description" : "Might slip to later milestone" + }, { + "label" : "OpSys-All", + "description" : "Affects all operating systems" + }, { + "label" : "OpSys-Windows", + "description" : "Affects Windows users" + }, { + "label" : "OpSys-Linux", + "description" : "Affects Linux users" + }, { + "label" : "OpSys-OSX", + "description" : "Affects Mac OS X users" + }, { + "label" : "Milestone-Release1.0", + "description" : "All essential functionality working" + }, { + "label" : "Component-UI", + "description" : "Issue relates to program UI" + }, { + "label" : "Component-Logic", + "description" : "Issue relates to application logic" + }, { + "label" : "Component-Persistence", + "description" : "Issue relates to data storage components" + }, { + "label" : "Component-Scripts", + "description" : "Utility and installation scripts" + }, { + "label" : "Component-Docs", + "description" : "Issue relates to end-user documentation" + }, { + "label" : "Security", + "description" : "Security risk to users" + }, { + "label" : "Performance", + "description" : "Performance issue" + }, { + "label" : "Usability", + "description" : "Affects program usability" + }, { + "label" : "Maintainability", + "description" : "Hinders future changes" + } ], + "prompts" : [ { + "name" : "Defect report from user", + "title" : "Enter one-line summary", + "description" : "What steps will reproduce the problem?\n1. \n2. \n3. \n\nWhat is the expected output? What do you see instead?\n\n\nWhat version of the product are you using? On what operating system?\n\n\nPlease provide any additional information below.\n", + "titleMustBeEdited" : true, + "status" : "New", + "labels" : [ "Type-Defect", "Priority-Medium" ] + }, { + "name" : "Defect report from developer", + "title" : "Enter one-line summary", + "description" : "What steps will reproduce the problem?\n1. \n2. \n3. \n\nWhat is the expected output? What do you see instead?\n\n\nPlease use labels and text to provide additional information.\n", + "titleMustBeEdited" : true, + "status" : "Accepted", + "labels" : [ "Type-Defect", "Priority-Medium" ], + "membersOnly" : true + }, { + "name" : "Review request", + "title" : "Code review request", + "description" : "Branch name:\n\nPurpose of code changes on this branch:\n\n\nWhen reviewing my code changes, please focus on:\n\n\nAfter the review, I'll merge this branch into:\n/trunk\n", + "status" : "New", + "labels" : [ "Type-Review", "Priority-Medium" ], + "membersOnly" : true, + "defaultToMember" : false + } ], + "defaultPromptForMembers" : 1, + "defaultPromptForNonMembers" : 0 + }, + "role" : "owner", + "members" : [ { + "kind" : "projecthosting#issuePerson", + "name" : "mrovi9000", + "htmlLink" : "https://code.google.com/u/106736353629303906862/" + } ], + "issues" : { + "kind" : "projecthosting#issueList", + "totalResults" : 0, + "items" : [ ] + } + }, { + "kind" : "projecthosting#project", + "name" : "tint2", + "externalId" : "tint2", + "htmlLink" : "/p/tint2/", + "summary" : "tint2 is a lightweight panel/taskbar.", + "description" : "tint2 is a simple _*panel/taskbar*_ unintrusive and light (memory / cpu / aestetic).
    We follow freedesktop specifications.\r\n \r\n=== 0.11 features ===\r\n * panel with taskbar, systray, clock and battery status\r\n * easy to customize : color/transparency on font, icon, border and background\r\n * pager like capability : send task from one workspace to another, switch workspace\r\n * multi-monitor capability : one panel per monitor, show task from current monitor\r\n * customize mouse event\r\n * window manager's menu\r\n * tooltip\r\n * autohide\r\n * clock timezones\r\n * real & fake transparency with autodetection of composite manager\r\n * panel's theme switcher 'tint2conf' \r\n\r\n=== Other project ===\r\n * Lightweight volume control http://softwarebakery.com/maato/volumeicon.html\r\n * Lightweight calendar http://code.google.com/p/gsimplecal/\r\n * Graphical config tool http://code.google.com/p/tintwizard/\r\n * Command line theme switcher http://github.com/dbbolton/scripts/blob/master/tint2theme\r\n\r\n\r\n=== Snapshot SVN ===\r\n\r\nhttp://img252.imageshack.us/img252/1433/wallpaper2td.jpg\r\n\r\n\r\n", + "labels" : [ "taskbar", "panel", "lightweight", "desktop", "openbox", "pager", "tint2" ], + "versionControlSystem" : "git", + "repositoryUrls" : [ "https://tint2.googlecode.com/git/" ], + "issuesConfig" : { + "kind" : "projecthosting#projectIssueConfig", + "defaultColumns" : [ "ID", "Status", "Type", "Milestone", "Priority", "Component", "Owner", "Summary", "Modified", "Stars" ], + "defaultSorting" : [ "-ID" ], + "statuses" : [ { + "status" : "New", + "meansOpen" : true, + "description" : "Issue has not had initial review yet" + }, { + "status" : "NeedInfo", + "meansOpen" : true, + "description" : "More information is needed before deciding what action should be taken" + }, { + "status" : "Accepted", + "meansOpen" : true, + "description" : "A Defect that a developer has reproduced or an Enhancement that a developer has committed to addressing" + }, { + "status" : "Wishlist", + "meansOpen" : true, + "description" : "An Enhancement which is valid, but no developers have committed to addressing" + }, { + "status" : "Started", + "meansOpen" : true, + "description" : "Work on this issue has begun" + }, { + "status" : "Fixed", + "meansOpen" : false, + "description" : "Work has completed" + }, { + "status" : "Invalid", + "meansOpen" : false, + "description" : "This was not a valid issue report" + }, { + "status" : "Duplicate", + "meansOpen" : false, + "description" : "This report duplicates an existing issue" + }, { + "status" : "WontFix", + "meansOpen" : false, + "description" : "We decided to not take action on this issue" + }, { + "status" : "Incomplete", + "meansOpen" : false, + "description" : "Not enough information and no activity for a long period of time" + } ], + "labels" : [ { + "label" : "Type-Defect", + "description" : "Report of a software defect" + }, { + "label" : "Type-Enhancement", + "description" : "Request for enhancement" + }, { + "label" : "Type-Task", + "description" : "Work item that does not change the code" + }, { + "label" : "Type-Review", + "description" : "Request for a source code review" + }, { + "label" : "Type-Other", + "description" : "Some other kind of issue" + }, { + "label" : "Milestone-0.12", + "description" : "Fix should be included in release 0.12" + }, { + "label" : "Priority-Critical", + "description" : "Must resolve in the specified milestone" + }, { + "label" : "Priority-High", + "description" : "Strongly want to resolve in the specified milestone" + }, { + "label" : "Priority-Medium", + "description" : "Normal priority" + }, { + "label" : "Priority-Low", + "description" : "Might slip to later milestone" + }, { + "label" : "OpSys-All", + "description" : "Affects all operating systems" + }, { + "label" : "OpSys-Windows", + "description" : "Affects Windows users" + }, { + "label" : "OpSys-Linux", + "description" : "Affects Linux users" + }, { + "label" : "OpSys-OSX", + "description" : "Affects Mac OS X users" + }, { + "label" : "Security", + "description" : "Security risk to users" + }, { + "label" : "Performance", + "description" : "Performance issue" + }, { + "label" : "Usability", + "description" : "Affects program usability" + }, { + "label" : "Maintainability", + "description" : "Hinders future changes" + }, { + "label" : "Component-Panel", + "description" : "Issue relates to the panel (e.g. positioning, hiding, transparency)" + }, { + "label" : "Component-Taskbar", + "description" : "Issue relates to the taskbar (e.g. tasks, multiple desktops)" + }, { + "label" : "Component-Battery", + "description" : "Issue relates to the battery" + }, { + "label" : "Component-Systray", + "description" : "Issue relates to the system tray" + }, { + "label" : "Component-Clock", + "description" : "Issue relates to the clock" + }, { + "label" : "Component-Launcher", + "description" : "Issue relates to the launcher" + }, { + "label" : "Component-Tint2conf", + "description" : "Issue relates to the configuration GUI (tint2conf)" + }, { + "label" : "Component-Docs", + "description" : "Issue relates to end-user documentation" + }, { + "label" : "Component-New", + "description" : "Issue describes a new component proposal" + } ], + "prompts" : [ { + "name" : "Defect report from user", + "title" : "Enter one-line summary", + "description" : "What steps will reproduce the problem?\n1.\n2.\n3.\n\nWhat is the expected output? What do you see instead?\n\n\nWhat version of the product are you using? On what operating system?\n\n\nWhich window manager (e.g. openbox, xfwm, metacity, mutter, kwin) or\nwhich desktop environment (e.g. Gnome 2, Gnome 3, LXDE, XFCE, KDE)\nare you using?\n\n\nPlease provide any additional information below. It might be helpful\nto attach your tint2rc file (usually located at ~/.config/tint2/tint2rc).", + "titleMustBeEdited" : true, + "status" : "New", + "labels" : [ "Priority-Medium" ], + "defaultToMember" : true + }, { + "name" : "Defect report from developer", + "title" : "Enter one-line summary", + "description" : "What steps will reproduce the problem?\n1.\n2.\n3.\n\nWhat is the expected output? What do you see instead?\n\n\nPlease use labels and text to provide additional information.", + "titleMustBeEdited" : true, + "status" : "Accepted", + "labels" : [ "Type-Defect", "Priority-Medium" ], + "membersOnly" : true, + "defaultToMember" : true + }, { + "name" : "Review request", + "title" : "Code review request", + "description" : "Purpose of code changes on this branch:\n\n\nWhen reviewing my code changes, please focus on:\n\n\nAfter the review, I'll merge this branch into:\n/trunk", + "status" : "New", + "labels" : [ "Type-Review", "Priority-Medium" ], + "membersOnly" : true, + "defaultToMember" : true + } ], + "defaultPromptForMembers" : 1, + "defaultPromptForNonMembers" : 0, + "usersCanSetLabels" : false + }, + "role" : "owner", + "issues" : { + "kind" : "projecthosting#issueList", + "totalResults" : 473, + "items" : [ { + "kind" : "projecthosting#issue", + "id" : 169, + "title" : "Scrolling through tasks", + "summary" : "Scrolling through tasks", + "stars" : 1, + "starred" : false, + "status" : "Fixed", + "state" : "closed", + "labels" : [ "Type-Enhancement", "Priority-Medium" ], + "author" : { + "kind" : "projecthosting#issuePerson", + "name" : "schattenpr...", + "htmlLink" : "https://code.google.com/u/106498139506637530000/" + }, + "owner" : { + "kind" : "projecthosting#issuePerson", + "name" : "thilo...", + "htmlLink" : "https://code.google.com/u/104224918623172014000/" + }, + "updated" : "2009-11-18T05:14:58.000Z", + "published" : "2009-11-18T00:20:19.000Z", + "closed" : "2009-11-18T05:14:58.000Z", + "projectId" : "tint2", + "canComment" : true, + "canEdit" : true, + "comments" : { + "kind" : "projecthosting#issueCommentList", + "totalResults" : 2, + "items" : [ { + "id" : 0, + "kind" : "projecthosting#issueComment", + "author" : { + "kind" : "projecthosting#issuePerson", + "name" : "schattenpr...", + "htmlLink" : "https://code.google.com/u/10649813950663753000/" + }, + "content" : "I like to scroll through the tasks with my scrollwheel (like in fluxbox). \r\n\r\nPatch is attached that adds two new mouse-actions (next_task+prev_task) \r\nthat can be used for exactly that purpose. \r\n\r\nall the best!", + "published" : "2009-11-18T00:20:19.000Z", + "updates" : { + "kind" : "projecthosting#issueCommentUpdate" + }, + "canDelete" : true, + "attachments" : [ { + "attachmentId" : "8901002890399325565", + "fileName" : "tint2_task_scrolling.diff", + "fileSize" : 3059, + "mimetype" : "text/x-c++; charset=us-ascii" + }, { + "attachmentId" : "000", + "fileName" : "screenshot.png", + "fileSize" : 0, + "mimetype" : "image/png" + } ] + }, { + "id" : 1, + "kind" : "projecthosting#issueComment", + "author" : { + "kind" : "projecthosting#issuePerson", + "name" : "thilo...", + "htmlLink" : "https://code.google.com/u/104224918623172014000/" + }, + "content" : "applied, thanks.\r\n", + "published" : "2009-11-18T05:14:58.000Z", + "updates" : { + "kind" : "projecthosting#issueCommentUpdate", + "status" : "Fixed", + "labels" : [ "-Type-Defect", "Type-Enhancement" ] + }, + "canDelete" : true + } ] + } + } ] + } + } ] +} diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb index 2db67cfdf9..d4cf654008 100644 --- a/spec/helpers/application_helper_spec.rb +++ b/spec/helpers/application_helper_spec.rb @@ -3,20 +3,20 @@ require 'spec_helper' describe ApplicationHelper do describe 'current_controller?' do before do - controller.stub(:controller_name).and_return('foo') + allow(controller).to receive(:controller_name).and_return('foo') end - it "returns true when controller matches argument" do - current_controller?(:foo).should be_true + it 'returns true when controller matches argument' do + expect(current_controller?(:foo)).to be_truthy end - it "returns false when controller does not match argument" do - current_controller?(:bar).should_not be_true + it 'returns false when controller does not match argument' do + expect(current_controller?(:bar)).not_to be_truthy end - it "should take any number of arguments" do - current_controller?(:baz, :bar).should_not be_true - current_controller?(:baz, :bar, :foo).should be_true + it 'should take any number of arguments' do + expect(current_controller?(:baz, :bar)).not_to be_truthy + expect(current_controller?(:baz, :bar, :foo)).to be_truthy end end @@ -25,99 +25,124 @@ describe ApplicationHelper do allow(self).to receive(:action_name).and_return('foo') end - it "returns true when action matches argument" do - current_action?(:foo).should be_true + it 'returns true when action matches argument' do + expect(current_action?(:foo)).to be_truthy end - it "returns false when action does not match argument" do - current_action?(:bar).should_not be_true + it 'returns false when action does not match argument' do + expect(current_action?(:bar)).not_to be_truthy end - it "should take any number of arguments" do - current_action?(:baz, :bar).should_not be_true - current_action?(:baz, :bar, :foo).should be_true + it 'should take any number of arguments' do + expect(current_action?(:baz, :bar)).not_to be_truthy + expect(current_action?(:baz, :bar, :foo)).to be_truthy end end - describe "group_icon" do + describe 'project_icon' do avatar_file_path = File.join(Rails.root, 'public', 'gitlab_logo.png') - it "should return an url for the avatar" do - group = create(:group) - group.avatar = File.open(avatar_file_path) - group.save! - group_icon(group.path).to_s.should match("/uploads/group/avatar/#{ group.id }/gitlab_logo.png") + it 'should return an url for the avatar' do + project = create(:project) + project.avatar = File.open(avatar_file_path) + project.save! + avatar_url = "http://localhost/uploads/project/avatar/#{ project.id }/gitlab_logo.png" + expect(project_icon("#{project.namespace.to_param}/#{project.to_param}").to_s).to eq( + "\"Gitlab" + ) end - it "should give default avatar_icon when no avatar is present" do - group = create(:group) - group.save! - group_icon(group.path).should match("group_avatar.png") + it 'should give uploaded icon when present' do + project = create(:project) + project.save! + + allow_any_instance_of(Project).to receive(:avatar_in_git).and_return(true) + + avatar_url = 'http://localhost' + namespace_project_avatar_path(project.namespace, project) + expect(project_icon("#{project.namespace.to_param}/#{project.to_param}").to_s).to match( + image_tag(avatar_url)) end end - describe "avatar_icon" do + describe 'avatar_icon' do avatar_file_path = File.join(Rails.root, 'public', 'gitlab_logo.png') - it "should return an url for the avatar" do + it 'should return an url for the avatar' do user = create(:user) user.avatar = File.open(avatar_file_path) user.save! - avatar_icon(user.email).to_s.should match("/uploads/user/avatar/#{ user.id }/gitlab_logo.png") + expect(avatar_icon(user.email).to_s). + to match("/uploads/user/avatar/#{ user.id }/gitlab_logo.png") end - it "should call gravatar_icon when no avatar is present" do + it 'should return an url for the avatar with relative url' do + Gitlab.config.gitlab.stub(relative_url_root: '/gitlab') + Gitlab.config.gitlab.stub(url: Settings.send(:build_gitlab_url)) + + user = create(:user) + user.avatar = File.open(avatar_file_path) + user.save! + expect(avatar_icon(user.email).to_s). + to match("/gitlab/uploads/user/avatar/#{ user.id }/gitlab_logo.png") + end + + it 'should call gravatar_icon when no avatar is present' do user = create(:user, email: 'test@example.com') user.save! - avatar_icon(user.email).to_s.should == "http://www.gravatar.com/avatar/55502f40dc8b7c769880b10874abc9d0?s=40&d=identicon" + expect(avatar_icon(user.email).to_s).to eq('http://www.gravatar.com/avatar/55502f40dc8b7c769880b10874abc9d0?s=40&d=identicon') end end - describe "gravatar_icon" do + describe 'gravatar_icon' do let(:user_email) { 'user@email.com' } - it "should return a generic avatar path when Gravatar is disabled" do - Gitlab.config.gravatar.stub(:enabled).and_return(false) - gravatar_icon(user_email).should match('no_avatar.png') + it 'should return a generic avatar path when Gravatar is disabled' do + ApplicationSetting.any_instance.stub(gravatar_enabled?: false) + expect(gravatar_icon(user_email)).to match('no_avatar.png') end - it "should return a generic avatar path when email is blank" do - gravatar_icon('').should match('no_avatar.png') + it 'should return a generic avatar path when email is blank' do + expect(gravatar_icon('')).to match('no_avatar.png') end - it "should return default gravatar url" do + it 'should return default gravatar url' do Gitlab.config.gitlab.stub(https: false) - gravatar_icon(user_email).should match('http://www.gravatar.com/avatar/b58c6f14d292556214bd64909bcdb118') + url = 'http://www.gravatar.com/avatar/b58c6f14d292556214bd64909bcdb118' + expect(gravatar_icon(user_email)).to match(url) end - it "should use SSL when appropriate" do + it 'should use SSL when appropriate' do Gitlab.config.gitlab.stub(https: true) - gravatar_icon(user_email).should match('https://secure.gravatar.com') + expect(gravatar_icon(user_email)).to match('https://secure.gravatar.com') end - it "should return custom gravatar path when gravatar_url is set" do + it 'should return custom gravatar path when gravatar_url is set' do allow(self).to receive(:request).and_return(double(:ssl? => false)) - Gitlab.config.gravatar.stub(:plain_url).and_return('http://example.local/?s=%{size}&hash=%{hash}') - gravatar_icon(user_email, 20).should == 'http://example.local/?s=20&hash=b58c6f14d292556214bd64909bcdb118' + allow(Gitlab.config.gravatar). + to receive(:plain_url). + and_return('http://example.local/?s=%{size}&hash=%{hash}') + url = 'http://example.local/?s=20&hash=b58c6f14d292556214bd64909bcdb118' + expect(gravatar_icon(user_email, 20)).to eq(url) end - it "should accept a custom size" do + it 'should accept a custom size' do allow(self).to receive(:request).and_return(double(:ssl? => false)) - gravatar_icon(user_email, 64).should match(/\?s=64/) + expect(gravatar_icon(user_email, 64)).to match(/\?s=64/) end - it "should use default size when size is wrong" do + it 'should use default size when size is wrong' do allow(self).to receive(:request).and_return(double(:ssl? => false)) - gravatar_icon(user_email, nil).should match(/\?s=40/) + expect(gravatar_icon(user_email, nil)).to match(/\?s=40/) end - it "should be case insensitive" do + it 'should be case insensitive' do allow(self).to receive(:request).and_return(double(:ssl? => false)) - gravatar_icon(user_email).should == gravatar_icon(user_email.upcase + " ") + expect(gravatar_icon(user_email)). + to eq(gravatar_icon(user_email.upcase + ' ')) end end - describe "grouped_options_refs" do + describe 'grouped_options_refs' do # Override Rails' grouped_options_for_select helper since HTML is harder to work with def grouped_options_for_select(options, *args) options @@ -130,91 +155,109 @@ describe ApplicationHelper do @project = create(:project) end - it "includes a list of branch names" do - options[0][0].should == 'Branches' - options[0][1].should include('master', 'feature') + it 'includes a list of branch names' do + expect(options[0][0]).to eq('Branches') + expect(options[0][1]).to include('master', 'feature') end - it "includes a list of tag names" do - options[1][0].should == 'Tags' - options[1][1].should include('v1.0.0','v1.1.0') + it 'includes a list of tag names' do + expect(options[1][0]).to eq('Tags') + expect(options[1][1]).to include('v1.0.0', 'v1.1.0') end - it "includes a specific commit ref if defined" do + it 'includes a specific commit ref if defined' do # Must be an instance variable @ref = '2ed06dc41dbb5936af845b87d79e05bbf24c73b8' - options[2][0].should == 'Commit' - options[2][1].should == [@ref] + expect(options[2][0]).to eq('Commit') + expect(options[2][1]).to eq([@ref]) end - it "sorts tags in a natural order" do + it 'sorts tags in a natural order' do # Stub repository.tag_names to make sure we get some valid testing data - expect(@project.repository).to receive(:tag_names).and_return(["v1.0.9", "v1.0.10", "v2.0", "v3.1.4.2", "v1.0.9a"]) + expect(@project.repository).to receive(:tag_names). + and_return(['v1.0.9', 'v1.0.10', 'v2.0', 'v3.1.4.2', 'v2.0rc1¿', + 'v1.0.9a', 'v2.0-rc1', 'v2.0rc2']) - options[1][1].should == ["v3.1.4.2", "v2.0", "v1.0.10", "v1.0.9a", "v1.0.9"] + expect(options[1][1]). + to eq(['v3.1.4.2', 'v2.0', 'v2.0rc2', 'v2.0rc1¿', 'v2.0-rc1', 'v1.0.10', + 'v1.0.9', 'v1.0.9a']) end end - describe "user_color_scheme_class" do - context "with current_user is nil" do - it "should return a string" do + describe 'user_color_scheme_class' do + context 'with current_user is nil' do + it 'should return a string' do allow(self).to receive(:current_user).and_return(nil) - user_color_scheme_class.should be_kind_of(String) + expect(user_color_scheme_class).to be_kind_of(String) end end - context "with a current_user" do + context 'with a current_user' do (1..5).each do |color_scheme_id| context "with color_scheme_id == #{color_scheme_id}" do - it "should return a string" do + it 'should return a string' do current_user = double(:color_scheme_id => color_scheme_id) allow(self).to receive(:current_user).and_return(current_user) - user_color_scheme_class.should be_kind_of(String) + expect(user_color_scheme_class).to be_kind_of(String) end end end end end - describe "simple_sanitize" do + describe 'simple_sanitize' do let(:a_tag) { 'Foo' } - it "allows the a tag" do - simple_sanitize(a_tag).should == a_tag + it 'allows the a tag' do + expect(simple_sanitize(a_tag)).to eq(a_tag) end - it "allows the span tag" do + it 'allows the span tag' do input = 'Bar' - simple_sanitize(input).should == input + expect(simple_sanitize(input)).to eq(input) end - it "disallows other tags" do + it 'disallows other tags' do input = "#{a_tag}" - simple_sanitize(input).should == a_tag + expect(simple_sanitize(input)).to eq(a_tag) end end - describe "link_to" do - - it "should not include rel=nofollow for internal links" do - expect(link_to("Home", root_path)).to eq("Home") + describe 'link_to' do + it 'should not include rel=nofollow for internal links' do + expect(link_to('Home', root_path)).to eq('Home') end - it "should include rel=nofollow for external links" do - expect(link_to("Example", "http://www.example.com")).to eq("Example") + it 'should include rel=nofollow for external links' do + expect(link_to('Example', 'http://www.example.com')). + to eq 'Example' end - it "should include re=nofollow for external links and honor existing html_options" do - expect( - link_to("Example", "http://www.example.com", class: "toggle", data: {toggle: "dropdown"}) - ).to eq("Example") + it 'should include rel=nofollow for external links and honor existing html_options' do + expect(link_to('Example', 'http://www.example.com', class: 'toggle', data: {toggle: 'dropdown'})) + .to eq 'Example' end - it "should include rel=nofollow for external links and preserver other rel values" do - expect( - link_to("Example", "http://www.example.com", rel: "noreferrer") - ).to eq("Example") + it 'should include rel=nofollow for external links and preserve other rel values' do + expect(link_to('Example', 'http://www.example.com', rel: 'noreferrer')) + .to eq 'Example' + end + + it 'should not include rel=nofollow for external links on the same host as GitLab' do + expect(Gitlab.config.gitlab).to receive(:host).and_return('example.foo') + expect(link_to('Example', 'http://example.foo/bar')). + to eq 'Example' + end + + it 'should not raise an error when given a bad URI' do + expect { link_to('default', 'if real=1 RANDOM; if real>1 IDLHS; if real>500 LHS') }. + not_to raise_error + end + + it 'should not raise an error when given a bad mailto URL' do + expect { link_to('email', 'mailto://foo.bar@example.es?subject=Subject%20Line') }. + not_to raise_error end end @@ -222,7 +265,7 @@ describe ApplicationHelper do let(:content) { 'Noël' } it 'should preserve encoding' do - content.encoding.name.should == 'UTF-8' + expect(content.encoding.name).to eq('UTF-8') expect(render_markup('foo.rst', content).encoding.name).to eq('UTF-8') end end diff --git a/spec/helpers/broadcast_messages_helper_spec.rb b/spec/helpers/broadcast_messages_helper_spec.rb index 1338ce4873..f6df12662b 100644 --- a/spec/helpers/broadcast_messages_helper_spec.rb +++ b/spec/helpers/broadcast_messages_helper_spec.rb @@ -6,7 +6,7 @@ describe BroadcastMessagesHelper do context "default style" do it "should have no style" do - broadcast_styling(broadcast_message).should match('') + expect(broadcast_styling(broadcast_message)).to match('') end end @@ -14,7 +14,8 @@ describe BroadcastMessagesHelper do before { broadcast_message.stub(color: "#f2dede", font: "#b94a48") } it "should have a customized style" do - broadcast_styling(broadcast_message).should match('background-color:#f2dede;color:#b94a48') + expect(broadcast_styling(broadcast_message)). + to match('background-color:#f2dede;color:#b94a48') end end end diff --git a/spec/helpers/diff_helper_spec.rb b/spec/helpers/diff_helper_spec.rb new file mode 100644 index 0000000000..5bd09793b1 --- /dev/null +++ b/spec/helpers/diff_helper_spec.rb @@ -0,0 +1,102 @@ +require 'spec_helper' + +describe DiffHelper do + include RepoHelpers + + let(:project) { create(:project) } + let(:commit) { project.repository.commit(sample_commit.id) } + let(:diff) { commit.diffs.first } + let(:diff_file) { Gitlab::Diff::File.new(diff) } + + describe 'diff_hard_limit_enabled?' do + it 'should return true if param is provided' do + allow(controller).to receive(:params) { { force_show_diff: true } } + expect(diff_hard_limit_enabled?).to be_truthy + end + + it 'should return false if param is not provided' do + expect(diff_hard_limit_enabled?).to be_falsey + end + end + + describe 'allowed_diff_size' do + it 'should return hard limit for a diff if force diff is true' do + allow(controller).to receive(:params) { { force_show_diff: true } } + expect(allowed_diff_size).to eq(1000) + end + + it 'should return safe limit for a diff if force diff is false' do + expect(allowed_diff_size).to eq(100) + end + end + + describe 'parallel_diff' do + it 'should return an array of arrays containing the parsed diff' do + expect(parallel_diff(diff_file, 0)). + to match_array(parallel_diff_result_array) + end + end + + describe 'generate_line_code' do + it 'should generate correct line code' do + expect(generate_line_code(diff_file.file_path, diff_file.diff_lines.first)). + to eq('2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6') + end + end + + describe 'unfold_bottom_class' do + it 'should return empty string when bottom line shouldnt be unfolded' do + expect(unfold_bottom_class(false)).to eq('') + end + + it 'should return js class when bottom lines should be unfolded' do + expect(unfold_bottom_class(true)).to eq('js-unfold-bottom') + end + end + + describe 'diff_line_content' do + + it 'should return non breaking space when line is empty' do + expect(diff_line_content(nil)).to eq('  ') + end + + it 'should return the line itself' do + expect(diff_line_content(diff_file.diff_lines.first.text)). + to eq('@@ -6,12 +6,18 @@ module Popen') + expect(diff_line_content(diff_file.diff_lines.first.type)).to eq('match') + expect(diff_line_content(diff_file.diff_lines.first.new_pos)).to eq(6) + end + end + + def parallel_diff_result_array + [ + ["match", 6, "@@ -6,12 +6,18 @@ module Popen", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6", "match", 6, "@@ -6,12 +6,18 @@ module Popen", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6"], + [nil, 6, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6", nil, 6, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_6_6"], [nil, 7, " def popen(cmd, path=nil)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7", nil, 7, " def popen(cmd, path=nil)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_7_7"], + [nil, 8, " unless cmd.is_a?(Array)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8", nil, 8, " unless cmd.is_a?(Array)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_8_8"], + ["old", 9, "- raise "System commands must be given as an array of strings"", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_9_9", "new", 9, "+ raise RuntimeError, "System commands must be given as an array of strings"", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_9"], + [nil, 10, " end", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10", nil, 10, " end", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_10_10"], + [nil, 11, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11", nil, 11, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_11_11"], + [nil, 12, " path ||= Dir.pwd", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12", nil, 12, " path ||= Dir.pwd", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_12_12"], + ["old", 13, "- vars = { "PWD" => path }", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_13_13", "old", nil, " ", nil], + ["old", 14, "- options = { chdir: path }", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_14_13", "new", 13, "+", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_13"], + [nil, nil, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14", "new", 14, "+ vars = {", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_14"], + [nil, nil, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15", "new", 15, "+ "PWD" => path", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_15"], + [nil, nil, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16", "new", 16, "+ }", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_16"], + [nil, nil, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17", "new", 17, "+", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_17"], + [nil, nil, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18", "new", 18, "+ options = {", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_18"], + [nil, nil, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19", "new", 19, "+ chdir: path", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_19"], + [nil, nil, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20", "new", 20, "+ }", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_20"], + [nil, 15, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21", nil, 21, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_15_21"], + [nil, 16, " unless File.directory?(path)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22", nil, 22, " unless File.directory?(path)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_16_22"], + [nil, 17, " FileUtils.mkdir_p(path)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23", nil, 23, " FileUtils.mkdir_p(path)", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_17_23"], + ["match", 19, "@@ -19,6 +25,7 @@ module Popen", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25", "match", 25, "@@ -19,6 +25,7 @@ module Popen", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25"], + [nil, 19, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25", nil, 25, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_19_25"], + [nil, 20, " @cmd_output = """, "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26", nil, 26, " @cmd_output = """, "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_20_26"], + [nil, 21, " @cmd_status = 0", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27", nil, 27, " @cmd_status = 0", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_21_27"], + [nil, nil, " ", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28", "new", 28, "+", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_28"], + [nil, 22, " Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29", nil, 29, " Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_22_29"], + [nil, 23, " @cmd_output << stdout.read", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30", nil, 30, " @cmd_output << stdout.read", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_23_30"], + [nil, 24, " @cmd_output << stderr.read", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31", nil, 31, " @cmd_output << stderr.read", "2f6fcd96b88b36ce98c38da085c795a27d92a3dd_24_31"] + ] + end +end diff --git a/spec/helpers/events_helper_spec.rb b/spec/helpers/events_helper_spec.rb new file mode 100644 index 0000000000..b392371deb --- /dev/null +++ b/spec/helpers/events_helper_spec.rb @@ -0,0 +1,65 @@ +require 'spec_helper' + +describe EventsHelper do + include ApplicationHelper + include GitlabMarkdownHelper + + 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(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 inline code' + + 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...' + + 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 = '

    ' \
    +               'Code block...
    ' + + 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}#{text}#{text}#{text}" # 200 chars + expected = "#{text}#{text}".sub(/.{3}/, '...') + + 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...' + + expect(event_note(input)).to match(link_url) + expect(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 = '
    ' \
    +      "def test\n" \
    +      "  \'hello world\'\n" \
    +      "end\n" \
    +      '
    ' + expect(event_note(input)).to eq(expected) + end +end diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb index ba6af6f8b4..944e743675 100644 --- a/spec/helpers/gitlab_markdown_helper_spec.rb +++ b/spec/helpers/gitlab_markdown_helper_spec.rb @@ -1,89 +1,151 @@ -require "spec_helper" +require 'spec_helper' describe GitlabMarkdownHelper do include ApplicationHelper include IssuesHelper + # TODO: Properly test this + def can?(*) + true + end + let!(:project) { create(:project) } let(:empty_project) { create(:empty_project) } let(:user) { create(:user, username: 'gfm') } let(:commit) { project.repository.commit } + let(:earlier_commit){ project.repository.commit("HEAD~2") } 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(:member) { project.users_projects.where(user_id: user).first } + let(:member) { project.project_members.where(user_id: user).first } + + # Helper expects a current_user method. + let(:current_user) { user } + + def url_helper(image_name) + File.join(root_url, 'assets', image_name) + end before do # Helper expects a @project instance variable @project = project @ref = 'markdown' @repository = project.repository + @request.host = Gitlab.config.gitlab.host end describe "#gfm" do it "should return unaltered text if project is nil" do actual = "Testing references: ##{issue.iid}" - gfm(actual).should_not == actual + expect(gfm(actual)).not_to eq(actual) @project = nil - gfm(actual).should == actual + expect(gfm(actual)).to eq(actual) end it "should not alter non-references" do actual = expected = "_Please_ *stop* 'helping' and all the other b*$#%' you do." - gfm(actual).should == expected + expect(gfm(actual)).to eq(expected) end it "should not touch HTML entities" do - @project.issues.stub(:where).with(id: '39').and_return([issue]) - actual = expected = "We'll accept good pull requests." - gfm(actual).should == expected + allow(@project.issues).to receive(:where). + with(id: '39').and_return([issue]) + actual = 'We'll accept good pull requests.' + expect(gfm(actual)).to eq("We'll accept good pull requests.") end it "should forward HTML options to links" do - gfm("Fixed in #{commit.id}", @project, class: 'foo'). - should have_selector('a.gfm.foo') + expect(gfm("Fixed in #{commit.id}", @project, class: 'foo')). + to have_selector('a.gfm.foo') end - describe "referencing a commit" do - let(:expected) { project_commit_path(project, commit) } + describe "referencing a commit range" do + let(:expected) { namespace_project_compare_path(project.namespace, project, from: earlier_commit.id, to: commit.id) } it "should link using a full id" do - actual = "Reverts #{commit.id}" - gfm(actual).should match(expected) + actual = "What happened in #{earlier_commit.id}...#{commit.id}" + expect(gfm(actual)).to match(expected) end it "should link using a short id" do - actual = "Backported from #{commit.short_id(6)}" - gfm(actual).should match(expected) + actual = "What happened in #{earlier_commit.short_id}...#{commit.short_id}" + expected = namespace_project_compare_path(project.namespace, project, from: earlier_commit.short_id, to: commit.short_id) + expect(gfm(actual)).to match(expected) + end + + it "should link inclusively" do + actual = "What happened in #{earlier_commit.id}..#{commit.id}" + expected = namespace_project_compare_path(project.namespace, project, from: "#{earlier_commit.id}^", to: commit.id) + expect(gfm(actual)).to match(expected) + end + + it "should link with adjacent text" do + actual = "(see #{earlier_commit.id}...#{commit.id})" + expect(gfm(actual)).to match(expected) + end + + it "should keep whitespace intact" do + actual = "Changes #{earlier_commit.id}...#{commit.id} dramatically" + expected = /Changes #{earlier_commit.id}...#{commit.id}<\/a> dramatically/ + expect(gfm(actual)).to match(expected) + end + + it "should not link with an invalid id" do + actual = expected = "What happened in #{earlier_commit.id.reverse}...#{commit.id.reverse}" + expect(gfm(actual)).to eq(expected) + end + + it "should include a title attribute" do + actual = "What happened in #{earlier_commit.id}...#{commit.id}" + expect(gfm(actual)).to match(/title="Commits #{earlier_commit.id} through #{commit.id}"/) + end + + it "should include standard gfm classes" do + actual = "What happened in #{earlier_commit.id}...#{commit.id}" + expect(gfm(actual)).to match(/class="\s?gfm gfm-commit_range\s?"/) + end + end + + describe "referencing a commit" do + let(:expected) { namespace_project_commit_path(project.namespace, project, commit) } + + it "should link using a full id" do + actual = "Reverts #{commit.id}" + expect(gfm(actual)).to match(expected) + end + + it "should link using a short id" do + actual = "Backported from #{commit.short_id}" + expect(gfm(actual)).to match(expected) end it "should link with adjacent text" do actual = "Reverted (see #{commit.id})" - gfm(actual).should match(expected) + expect(gfm(actual)).to match(expected) end it "should keep whitespace intact" do actual = "Changes #{commit.id} dramatically" expected = /Changes #{commit.id}<\/a> dramatically/ - gfm(actual).should match(expected) + expect(gfm(actual)).to match(expected) end it "should not link with an invalid id" do actual = expected = "What happened in #{commit.id.reverse}" - gfm(actual).should == expected + expect(gfm(actual)).to eq(expected) end it "should include a title attribute" do actual = "Reverts #{commit.id}" - gfm(actual).should match(/title="#{commit.link_title}"/) + expect(gfm(actual)).to match(/title="#{commit.link_title}"/) end it "should include standard gfm classes" do actual = "Reverts #{commit.id}" - gfm(actual).should match(/class="\s?gfm gfm-commit\s?"/) + expect(gfm(actual)).to match(/class="\s?gfm gfm-commit\s?"/) end end @@ -96,37 +158,37 @@ describe GitlabMarkdownHelper do end it "should link using a simple name" do - gfm(actual).should match(expected) + expect(gfm(actual)).to match(expected) end it "should link using a name with dots" do user.update_attributes(name: "alphA.Beta") - gfm(actual).should match(expected) + expect(gfm(actual)).to match(expected) end it "should link using name with underscores" do user.update_attributes(name: "ping_pong_king") - gfm(actual).should match(expected) + expect(gfm(actual)).to match(expected) end it "should link with adjacent text" do actual = "Mail the admin (@#{user.username})" - gfm(actual).should match(expected) + expect(gfm(actual)).to match(expected) end it "should keep whitespace intact" do actual = "Yes, @#{user.username} is right." expected = /Yes, @#{user.username}<\/a> is right/ - gfm(actual).should match(expected) + expect(gfm(actual)).to match(expected) end it "should not link with an invalid id" do actual = expected = "@#{user.username.reverse} you are right." - gfm(actual).should == expected + expect(gfm(actual)).to eq(expected) end it "should include standard gfm classes" do - gfm(actual).should match(/class="\s?gfm gfm-team_member\s?"/) + expect(gfm(actual)).to match(/class="\s?gfm gfm-project_member\s?"/) end end @@ -140,40 +202,110 @@ describe GitlabMarkdownHelper do # Currently limited to Snippets, Issues and MergeRequests shared_examples 'referenced object' do let(:actual) { "Reference to #{reference}" } - let(:expected) { polymorphic_path([project, object]) } + let(:expected) { polymorphic_path([project.namespace, project, object]) } it "should link using a valid id" do - gfm(actual).should match(expected) + expect(gfm(actual)).to match(expected) end it "should link with adjacent text" do # Wrap the reference in parenthesis - gfm(actual.gsub(reference, "(#{reference})")).should match(expected) + expect(gfm(actual.gsub(reference, "(#{reference})"))).to match(expected) # Append some text to the end of the reference - gfm(actual.gsub(reference, "#{reference}, right?")).should match(expected) + expect(gfm(actual.gsub(reference, "#{reference}, right?"))). + to match(expected) end it "should keep whitespace intact" do actual = "Referenced #{reference} already." expected = /Referenced [^\s]+<\/a> already/ - gfm(actual).should match(expected) + expect(gfm(actual)).to match(expected) end it "should not link with an invalid id" do # Modify the reference string so it's still parsed, but is invalid reference.gsub!(/^(.)(\d+)$/, '\1' + ('\2' * 2)) - gfm(actual).should == actual + expect(gfm(actual)).to eq(actual) end it "should include a title attribute" do title = "#{object.class.to_s.titlecase}: #{object.title}" - gfm(actual).should match(/title="#{title}"/) + expect(gfm(actual)).to match(/title="#{title}"/) end it "should include standard gfm classes" do css = object.class.to_s.underscore - gfm(actual).should match(/class="\s?gfm gfm-#{css}\s?"/) + expect(gfm(actual)).to match(/class="\s?gfm gfm-#{css}\s?"/) + end + end + + # Shared examples for referencing an object in a different project + # + # Expects the following attributes to be available in the example group: + # + # - object - The object itself + # - reference - The object reference string (e.g., #1234, $1234, !1234) + # - other_project - The project that owns the target object + # + # Currently limited to Snippets, Issues and MergeRequests + shared_examples 'cross-project referenced object' do + let(:project_path) { @other_project.path_with_namespace } + let(:full_reference) { "#{project_path}#{reference}" } + let(:actual) { "Reference to #{full_reference}" } + let(:expected) do + if object.is_a?(Commit) + namespace_project_commit_path(@other_project.namespace, @other_project, object) + else + polymorphic_path([@other_project.namespace, @other_project, object]) + end + end + + it 'should link using a valid id' do + expect(gfm(actual)).to match( + /#{expected}.*#{Regexp.escape(full_reference)}/ + ) + end + + it 'should link with adjacent text' do + # Wrap the reference in parenthesis + expect(gfm(actual.gsub(full_reference, "(#{full_reference})"))).to( + match(expected) + ) + + # Append some text to the end of the reference + expect(gfm(actual.gsub(full_reference, "#{full_reference}, right?"))). + to(match(expected)) + end + + it 'should keep whitespace intact' do + actual = "Referenced #{full_reference} already." + expected = /Referenced [^\s]+<\/a> already/ + expect(gfm(actual)).to match(expected) + end + + it 'should not link with an invalid id' do + # Modify the reference string so it's still parsed, but is invalid + if object.is_a?(Commit) + reference.gsub!(/^(.).+$/, '\1' + '12345abcd') + else + reference.gsub!(/^(.)(\d+)$/, '\1' + ('\2' * 2)) + end + expect(gfm(actual)).to eq(actual) + end + + it 'should include a title attribute' do + if object.is_a?(Commit) + title = object.link_title + else + title = "#{object.class.to_s.titlecase}: #{object.title}" + end + expect(gfm(actual)).to match(/title="#{title}"/) + end + + it 'should include standard gfm classes' do + css = object.class.to_s.underscore + expect(gfm(actual)).to match(/class="\s?gfm gfm-#{css}\s?"/) end end @@ -184,49 +316,85 @@ describe GitlabMarkdownHelper do include_examples 'referenced object' end + context 'cross-repo references' do + before(:all) do + @other_project = create(:project, :public) + @commit2 = @other_project.repository.commit + @issue2 = create(:issue, project: @other_project) + @merge_request2 = create(:merge_request, + source_project: @other_project, + target_project: @other_project) + end + + describe 'referencing an issue in another project' do + let(:object) { @issue2 } + let(:reference) { "##{@issue2.iid}" } + + include_examples 'cross-project referenced object' + end + + describe 'referencing an merge request in another project' do + let(:object) { @merge_request2 } + let(:reference) { "!#{@merge_request2.iid}" } + + include_examples 'cross-project referenced object' + end + + describe 'referencing a commit in another project' do + let(:object) { @commit2 } + let(:reference) { "@#{@commit2.id}" } + + include_examples 'cross-project referenced object' + end + end + describe "referencing a Jira issue" do let(:actual) { "Reference to JIRA-#{issue.iid}" } let(:expected) { "http://jira.example/browse/JIRA-#{issue.iid}" } let(:reference) { "JIRA-#{issue.iid}" } before do - issue_tracker_config = { "jira" => { "title" => "JIRA tracker", "issues_url" => "http://jira.example/browse/:id" } } - Gitlab.config.stub(:issues_tracker).and_return(issue_tracker_config) - @project.stub(:issues_tracker).and_return("jira") - @project.stub(:issues_tracker_id).and_return("JIRA") + jira = @project.create_jira_service if @project.jira_service.nil? + properties = {"title"=>"JIRA tracker", "project_url"=>"http://jira.example/issues/?jql=project=A", "issues_url"=>"http://jira.example/browse/:id", "new_issue_url"=>"http://jira.example/secure/CreateIssue.jspa"} + jira.update_attributes(properties: properties, active: true) + end + + after do + @project.jira_service.destroy! unless @project.jira_service.nil? end it "should link using a valid id" do - gfm(actual).should match(expected) + expect(gfm(actual)).to match(expected) end it "should link with adjacent text" do # Wrap the reference in parenthesis - gfm(actual.gsub(reference, "(#{reference})")).should match(expected) + expect(gfm(actual.gsub(reference, "(#{reference})"))).to match(expected) # Append some text to the end of the reference - gfm(actual.gsub(reference, "#{reference}, right?")).should match(expected) + expect(gfm(actual.gsub(reference, "#{reference}, right?"))). + to match(expected) end it "should keep whitespace intact" do actual = "Referenced #{reference} already." expected = /Referenced [^\s]+<\/a> already/ - gfm(actual).should match(expected) + expect(gfm(actual)).to match(expected) end it "should not link with an invalid id" do # Modify the reference string so it's still parsed, but is invalid invalid_reference = actual.gsub(/(\d+)$/, "r45") - gfm(invalid_reference).should == invalid_reference + expect(gfm(invalid_reference)).to eq(invalid_reference) end it "should include a title attribute" do title = "Issue in JIRA tracker" - gfm(actual).should match(/title="#{title}"/) + expect(gfm(actual)).to match(/title="#{title}"/) end it "should include standard gfm classes" do - gfm(actual).should match(/class="\s?gfm gfm-issue\s?"/) + expect(gfm(actual)).to match(/class="\s?gfm gfm-issue\s?"/) end end @@ -241,40 +409,40 @@ describe GitlabMarkdownHelper do let(:object) { snippet } let(:reference) { "$#{snippet.id}" } let(:actual) { "Reference to #{reference}" } - let(:expected) { project_snippet_path(project, object) } + let(:expected) { namespace_project_snippet_path(project.namespace, project, object) } it "should link using a valid id" do - gfm(actual).should match(expected) + expect(gfm(actual)).to match(expected) end it "should link with adjacent text" do # Wrap the reference in parenthesis - gfm(actual.gsub(reference, "(#{reference})")).should match(expected) + expect(gfm(actual.gsub(reference, "(#{reference})"))).to match(expected) # Append some text to the end of the reference - gfm(actual.gsub(reference, "#{reference}, right?")).should match(expected) + expect(gfm(actual.gsub(reference, "#{reference}, right?"))).to match(expected) end it "should keep whitespace intact" do actual = "Referenced #{reference} already." expected = /Referenced [^\s]+<\/a> already/ - gfm(actual).should match(expected) + expect(gfm(actual)).to match(expected) end it "should not link with an invalid id" do # Modify the reference string so it's still parsed, but is invalid reference.gsub!(/^(.)(\d+)$/, '\1' + ('\2' * 2)) - gfm(actual).should == actual + expect(gfm(actual)).to eq(actual) end it "should include a title attribute" do title = "Snippet: #{object.title}" - gfm(actual).should match(/title="#{title}"/) + expect(gfm(actual)).to match(/title="#{title}"/) end it "should include standard gfm classes" do css = object.class.to_s.underscore - gfm(actual).should match(/class="\s?gfm gfm-snippet\s?"/) + expect(gfm(actual)).to match(/class="\s?gfm gfm-snippet\s?"/) end end @@ -283,69 +451,70 @@ describe GitlabMarkdownHelper do let(:actual) { "!#{merge_request.iid} -> #{commit.id} -> ##{issue.iid}" } it "should link to the merge request" do - expected = project_merge_request_path(project, merge_request) - gfm(actual).should match(expected) + expected = namespace_project_merge_request_path(project.namespace, project, merge_request) + expect(gfm(actual)).to match(expected) end it "should link to the commit" do - expected = project_commit_path(project, commit) - gfm(actual).should match(expected) + expected = namespace_project_commit_path(project.namespace, project, commit) + expect(gfm(actual)).to match(expected) end it "should link to the issue" do - expected = project_issue_path(project, issue) - gfm(actual).should match(expected) + expected = namespace_project_issue_path(project.namespace, project, issue) + expect(gfm(actual)).to match(expected) end end describe "emoji" do it "matches at the start of a string" do - gfm(":+1:").should match(/ big time/) + expect(gfm('This deserves a :+1: big time.')). + to match(/deserves a big time/) end it "ignores invalid emoji" do - gfm(":invalid-emoji:").should_not match(/") # Leading commit link - groups[0].should match(/href="#{commit_path}"/) - groups[0].should match(/This should finally fix $/) + expect(groups[0]).to match(/href="#{commit_path}"/) + expect(groups[0]).to match(/This should finally fix $/) # First issue link - groups[1].should match(/href="#{project_issue_url(project, issues[0])}"/) - groups[1].should match(/##{issues[0].iid}$/) + expect(groups[1]). + to match(/href="#{namespace_project_issue_path(project.namespace, project, issues[0])}"/) + expect(groups[1]).to match(/##{issues[0].iid}$/) # Internal commit link - groups[2].should match(/href="#{commit_path}"/) - groups[2].should match(/ and /) + expect(groups[2]).to match(/href="#{commit_path}"/) + expect(groups[2]).to match(/ and /) # Second issue link - groups[3].should match(/href="#{project_issue_url(project, issues[1])}"/) - groups[3].should match(/##{issues[1].iid}$/) + expect(groups[3]). + to match(/href="#{namespace_project_issue_path(project.namespace, project, issues[1])}"/) + expect(groups[3]).to match(/##{issues[1].iid}$/) # Trailing commit link - groups[4].should match(/href="#{commit_path}"/) - groups[4].should match(/ for real$/) + expect(groups[4]).to match(/href="#{commit_path}"/) + expect(groups[4]).to match(/ for real$/) end it "should forward HTML options" do actual = link_to_gfm("Fixed in #{commit.id}", commit_path, class: 'foo') - actual.should have_selector 'a.gfm.gfm-commit.foo' + expect(actual).to have_selector 'a.gfm.gfm-commit.foo' end it "escapes HTML passed in as the body" do actual = "This is a

    test

    - see ##{issues[0].iid}" - link_to_gfm(actual, commit_path).should match('<h1>test</h1>') + expect(link_to_gfm(actual, commit_path)). + to match('<h1>test</h1>') end end describe "#markdown" do it "should handle references in paragraphs" do actual = "\n\nLorem ipsum dolor sit amet. #{commit.id} Nam pulvinar sapien eget.\n" - expected = project_commit_path(project, commit) - markdown(actual).should match(expected) + expected = namespace_project_commit_path(project.namespace, project, commit) + expect(markdown(actual)).to match(expected) end it "should handle references in headers" do actual = "\n# Working around ##{issue.iid}\n## Apply !#{merge_request.iid}" - markdown(actual, {no_header_anchors:true}).should match(%r{Working around ##{issue.iid}

    }) - markdown(actual, {no_header_anchors:true}).should match(%r{Apply !#{merge_request.iid}
  • W@DE_jYEp3A(NFa{&3R2iaH+L5~za7Wl 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 literal 0 HcmV?d00001 diff --git a/doc/workflow/workflow.md b/doc/workflow/workflow.md index ab29cfb670..f70e41df84 100644 --- a/doc/workflow/workflow.md +++ b/doc/workflow/workflow.md @@ -1,4 +1,4 @@ -# Workflow +# Feature branch workflow 1. Clone project: diff --git a/docker/.dockerignore b/docker/.dockerignore new file mode 100644 index 0000000000..dd449725e1 --- /dev/null +++ b/docker/.dockerignore @@ -0,0 +1 @@ +*.md diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000000..bb25bb677c --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,33 @@ +FROM ubuntu:14.04 + +# Install required packages +RUN apt-get update -q \ + && DEBIAN_FRONTEND=noninteractive apt-get install -qy --no-install-recommends \ + ca-certificates \ + openssh-server \ + wget + +# Download & Install GitLab +# If the Omnibus package version below is outdated please contribute a merge request to update it. +# If you run GitLab Enterprise Edition point it to a location where you have downloaded it. +RUN TMP_FILE=$(mktemp); \ + wget -q -O $TMP_FILE https://downloads-packages.s3.amazonaws.com/ubuntu-14.04/gitlab_7.9.2-omnibus-1_amd64.deb \ + && dpkg -i $TMP_FILE \ + && rm -f $TMP_FILE + +# Manage SSHD through runit +RUN mkdir -p /opt/gitlab/sv/sshd/supervise \ + && mkfifo /opt/gitlab/sv/sshd/supervise/ok \ + && printf "#!/bin/sh\nexec 2>&1\numask 077\nexec /usr/sbin/sshd -D" > /opt/gitlab/sv/sshd/run \ + && chmod a+x /opt/gitlab/sv/sshd/run \ + && ln -s /opt/gitlab/sv/sshd /opt/gitlab/service \ + && mkdir -p /var/run/sshd + +# Expose web & ssh +EXPOSE 80 22 + +# Copy assets +COPY assets/wrapper /usr/local/bin/ + +# Wrapper to handle signal, trigger runit and reconfigure GitLab +CMD ["/usr/local/bin/wrapper"] diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 0000000000..b7e8b0db7e --- /dev/null +++ b/docker/README.md @@ -0,0 +1,88 @@ +What is GitLab? +=============== + +GitLab offers git repository management, code reviews, issue tracking, activity feeds, wikis. It has LDAP/AD integration, handles 25,000 users on a single server but can also run on a highly available active/active cluster. A subscription gives you access to our support team and to GitLab Enterprise Edition that contains extra features aimed at larger organizations. + + + +![GitLab Logo](https://gitlab.com/uploads/appearance/logo/1/brand_logo-c37eb221b456bb4b472cc1084480991f.png) + + +How to use these images +====================== + +At this moment GitLab doesn't have official Docker images. For convinience we will use suffix _xy where xy is current version of GitLab. +Build your own based on the Omnibus packages with the following commands (it assumes you're in the GitLab repo root directory): + +```bash +sudo docker build --tag gitlab_data_image docker/data/ +sudo docker build --tag gitlab_app_image_xy docker/ +``` + +We assume using a data volume container, this will simplify migrations and backups. +This empty container will exist to persist as volumes the 3 directories used by GitLab, so remember not to delete it. + +The directories on data container are: + +- `/var/opt/gitlab` for application data +- `/var/log/gitlab` for logs +- `/etc/gitlab` for configuration + +Create the data container with: + +```bash +sudo docker run --name gitlab_data gitlab_data_image /bin/true +``` + +After creating data container run GitLab container: + +```bash +sudo docker run --detach --name gitlab_app_xy --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_app_image_xy +``` + +It might take a while before the docker container is responding to queries. You can follow the configuration process with `sudo docker logs -f gitlab_app_xy`. + +You can then go to `http://localhost:8080/` (or `http://192.168.59.103:8080/` if you use boot2docker). +You can login with username `root` and password `5iveL!fe`. +Next time, you can just use `sudo docker start gitlab_app` and `sudo docker stop gitlab_app`. + + +How to configure GitLab +======================== + +This container uses the official Omnibus GitLab distribution, so all configuration is done in the unique configuration file `/etc/gitlab/gitlab.rb`. + +To access GitLab configuration, you can start an interactive command line in a new container using the shared data volume container, you will be able to browse the 3 directories and use your favorite text editor: + +```bash +sudo docker run -ti -e TERM=linux --rm --volumes-from gitlab_data ubuntu +vi /etc/gitlab/gitlab.rb +``` + +**Note** that GitLab will reconfigure itself **at each container start.** You will need to restart the container to reconfigure your GitLab. + +You can find all available options in [Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration). + +How to upgrade GitLab +======================== + +To updgrade GitLab to new versions, stop running container, create new docker image and container from that image. + +It Assumes that you're upgrading from 7.8 to 7.9 and you're in the updated GitLab repo root directory: + +```bash +sudo docker stop gitlab_app_78 +sudo docker build --tag gitlab_app_image_79 docker/ +sudo docker run --detach --name gitlab_app_79 --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_app_image_79 +``` + +On the first run GitLab will reconfigure and update itself. If everything runs OK don't forget to cleanup old container and image: + +```bash +sudo docker rm gitlab_app_78 +sudo docker rmi gitlab_app_image_78 +``` + +Troubleshooting +========================= +Please see the [troubleshooting](troubleshooting.md) file in this directory. diff --git a/docker/assets/wrapper b/docker/assets/wrapper new file mode 100755 index 0000000000..9e6e7a0590 --- /dev/null +++ b/docker/assets/wrapper @@ -0,0 +1,17 @@ +#!/bin/bash + +function sigterm_handler() { + echo "SIGTERM signal received, try to gracefully shutdown all services..." + gitlab-ctl stop +} + +trap "sigterm_handler; exit" TERM + +function entrypoint() { + # Default is to run runit and reconfigure GitLab + gitlab-ctl reconfigure & + /opt/gitlab/embedded/bin/runsvdir-start & + wait +} + +entrypoint diff --git a/docker/data/Dockerfile b/docker/data/Dockerfile new file mode 100644 index 0000000000..ea0175c4aa --- /dev/null +++ b/docker/data/Dockerfile @@ -0,0 +1,8 @@ +FROM busybox + +# Declare volumes +VOLUME ["/var/opt/gitlab", "/var/log/gitlab", "/etc/gitlab"] +# Copy assets +COPY assets/gitlab.rb /etc/gitlab/ + +CMD /bin/sh diff --git a/docker/data/assets/gitlab.rb b/docker/data/assets/gitlab.rb new file mode 100644 index 0000000000..7fddf309c0 --- /dev/null +++ b/docker/data/assets/gitlab.rb @@ -0,0 +1,37 @@ +# External URL should be your Docker instance. +# By default, this example is the "standard" boot2docker IP. +# Always use port 80 here to force the internal nginx to bind port 80, +# even if you intend to use another port in Docker. +external_url "http://192.168.59.103/" + +# Prevent Postgres from trying to allocate 25% of total memory +postgresql['shared_buffers'] = '1MB' + +# Configure GitLab to redirect PostgreSQL logs to the data volume +postgresql['log_directory'] = '/var/log/gitlab/postgresql' + +# Some configuration of GitLab +# You can find more at https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration +gitlab_rails['gitlab_email_from'] = 'gitlab@example.com' +gitlab_rails['gitlab_support_email'] = 'support@example.com' +gitlab_rails['time_zone'] = 'Europe/Paris' + +# SMTP settings +# You must use an external server, the Docker container does not install an SMTP server +gitlab_rails['smtp_enable'] = true +gitlab_rails['smtp_address'] = "smtp.example.com" +gitlab_rails['smtp_port'] = 587 +gitlab_rails['smtp_user_name'] = "user" +gitlab_rails['smtp_password'] = "password" +gitlab_rails['smtp_domain'] = "example.com" +gitlab_rails['smtp_authentication'] = "plain" +gitlab_rails['smtp_enable_starttls_auto'] = true + +# Enable LDAP authentication +# gitlab_rails['ldap_enabled'] = true +# gitlab_rails['ldap_host'] = 'ldap.example.com' +# gitlab_rails['ldap_port'] = 389 +# gitlab_rails['ldap_method'] = 'plain' # 'ssl' or 'plain' +# gitlab_rails['ldap_allow_username_or_email_login'] = false +# gitlab_rails['ldap_uid'] = 'uid' +# gitlab_rails['ldap_base'] = 'ou=users,dc=example,dc=com' diff --git a/docker/troubleshooting.md b/docker/troubleshooting.md new file mode 100644 index 0000000000..b1b70de599 --- /dev/null +++ b/docker/troubleshooting.md @@ -0,0 +1,63 @@ +# Troubleshooting + +This is to troubleshoot https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/245 +But it might contain useful commands for other cases as well. + +The configuration to add the postgres log in vim is: +postgresql['log_directory'] = '/var/log/gitlab/postgresql' + +# Commands + +```bash +sudo docker build --tag gitlab_image docker/ + +sudo docker rm -f gitlab_app +sudo docker rm -f gitlab_data + +sudo docker run --name gitlab_data gitlab_image /bin/true + +sudo docker run -ti --rm --volumes-from gitlab_data ubuntu apt-get update && sudo apt-get install -y vim && sudo vim /etc/gitlab/gitlab.rb + +sudo docker run --detach --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image + +sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/reconfigure.log + +sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/postgresql/current + +sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers + +sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /etc/gitlab/gitlab.rb +``` + +# Interactively + +```bash +# First start a GitLab container without starting GitLab +# This is almost the same as starting the GitLab container except: +# - we run interactively (-t -i) +# - we define TERM=linux because it allows to use arrow keys in vi (!!!) +# - we choose another startup command (bash) +sudo docker run -ti -e TERM=linux --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image bash + +# Configure GitLab to redirect PostgreSQL logs +echo "postgresql['log_directory'] = '/var/log/gitlab/postgresql'" >> /etc/gitlab/gitlab.rb + +# Prevent Postgres from allocating 25% of total memory +echo "postgresql['shared_buffers'] = '1MB'" >> /etc/gitlab/gitlab.rb + +# You can now start GitLab manually from Bash (in the background) +# Maybe the command below is still missing something to run in the background +gitlab-ctl reconfigure > /var/log/gitlab/reconfigure.log & /opt/gitlab/embedded/bin/runsvdir-start & + +# Inspect PostgreSQL config +cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers + +# And tail the logs (PostgreSQL log may not exist immediately) +tail -f /var/log/gitlab/reconfigure.log /var/log/gitlab/postgresql/current + +# And get the memory +cat /proc/meminfo +head /proc/sys/kernel/shmmax /proc/sys/kernel/shmall +free -m + +``` diff --git a/features/admin/active_tab.feature b/features/admin/active_tab.feature index b28e16f0d6..5de07e90e2 100644 --- a/features/admin/active_tab.feature +++ b/features/admin/active_tab.feature @@ -1,5 +1,5 @@ @admin -Feature: Admin active tab +Feature: Admin Active Tab Background: Given I sign in as an admin diff --git a/features/admin/applications.feature b/features/admin/applications.feature new file mode 100644 index 0000000000..2a00e1666c --- /dev/null +++ b/features/admin/applications.feature @@ -0,0 +1,18 @@ +@admin +Feature: Admin Applications + Background: + Given I sign in as an admin + And I visit applications page + + Scenario: I can manage application + Then I click on new application button + And I should see application form + Then I fill application form out and submit + And I see application + Then I click edit + And I see edit application form + Then I change name of application and submit + And I see that application was changed + Then I visit applications page + And I click to remove application + Then I see that application is removed \ No newline at end of file diff --git a/features/admin/deploy_keys.feature b/features/admin/deploy_keys.feature new file mode 100644 index 0000000000..9df47eb51f --- /dev/null +++ b/features/admin/deploy_keys.feature @@ -0,0 +1,21 @@ +@admin +Feature: Admin Deploy Keys + Background: + Given I sign in as an admin + And there are public deploy keys in system + + Scenario: Deploy Keys list + When I visit admin deploy keys page + Then I should see all public deploy keys + + Scenario: Deploy Keys show + When I visit admin deploy keys page + And I click on first deploy key + Then I should see deploy key details + + Scenario: Deploy Keys new + When I visit admin deploy keys page + And I click 'New Deploy Key' + And I submit new deploy key + Then I should be on admin deploy keys page + And I should see newly created deploy key diff --git a/features/admin/groups.feature b/features/admin/groups.feature index 1a465c1be5..aa365a6ea1 100644 --- a/features/admin/groups.feature +++ b/features/admin/groups.feature @@ -20,3 +20,10 @@ Feature: Admin Groups When I visit admin group page When I select user "John Doe" from user list as "Reporter" Then I should see "John Doe" in team list in every project as "Reporter" + + @javascript + Scenario: Remove user from group + Given we have user "John Doe" in group + When I visit admin group page + And I remove user "John Doe" from group + Then I should not see "John Doe" in team list diff --git a/features/admin/settings.feature b/features/admin/settings.feature new file mode 100644 index 0000000000..52e47307b2 --- /dev/null +++ b/features/admin/settings.feature @@ -0,0 +1,16 @@ +@admin +Feature: Admin Settings + Background: + Given I sign in as an admin + And I visit admin settings page + + Scenario: Change application settings + When I modify settings and save form + Then I should see application settings saved + + Scenario: Change Slack Service Template settings + When I click on "Service Templates" + And I click on "Slack" service + Then I check all events and submit form + And I should see service template settings saved + And I should see all checkboxes checked diff --git a/features/admin/users.feature b/features/admin/users.feature index d8c1288e5f..1a8720dd77 100644 --- a/features/admin/users.feature +++ b/features/admin/users.feature @@ -16,6 +16,12 @@ Feature: Admin Users Then See username error message And Not changed form action url + Scenario: Show user attributes + Given user "Mike" with groups and projects + Given I visit admin users page + And click on "Mike" link + Then I should see user "Mike" details + Scenario: Edit my user attributes Given I visit admin users page And click edit on my user @@ -29,3 +35,13 @@ Feature: Admin Users And I see the secondary email When I click remove secondary email Then I should not see secondary email anymore + + Scenario: Show user keys + Given user "Pete" with ssh keys + And I visit admin users page + And click on user "Pete" + Then I should see key list + And I click on the key title + Then I should see key details + And I click on remove key + Then I should see the key removed diff --git a/features/dashboard/active_tab.feature b/features/dashboard/active_tab.feature index 22dfa2f784..08b87808f3 100644 --- a/features/dashboard/active_tab.feature +++ b/features/dashboard/active_tab.feature @@ -1,5 +1,5 @@ @dashboard -Feature: Dashboard active tab +Feature: Dashboard Active Tab Background: Given I sign in as a user diff --git a/features/dashboard/archived_projects.feature b/features/dashboard/archived_projects.feature index e23238d225..69b3a77644 100644 --- a/features/dashboard/archived_projects.feature +++ b/features/dashboard/archived_projects.feature @@ -1,5 +1,5 @@ @dashboard -Feature: Dashboard with archived projects +Feature: Dashboard Archived Projects Background: Given I sign in as a user And I own project "Shop" @@ -10,8 +10,3 @@ Feature: Dashboard with archived projects Scenario: I should see non-archived projects on dashboard Then I should see "Shop" project link And I should not see "Forum" project link - - Scenario: I should see all projects on projects page - And I visit dashboard projects page - Then I should see "Shop" project link - And I should see "Forum" project link diff --git a/features/dashboard/dashboard.feature b/features/dashboard/dashboard.feature index bebaa78e46..1959d32708 100644 --- a/features/dashboard/dashboard.feature +++ b/features/dashboard/dashboard.feature @@ -27,11 +27,11 @@ Feature: Dashboard Scenario: I should see User joined Project event Given user with name "John Doe" joined project "Shop" When I visit dashboard page - Then I should see "John Doe joined project at Shop" event + Then I should see "John Doe joined project Shop" event @javascript Scenario: I should see User left Project event Given user with name "John Doe" joined project "Shop" And user with name "John Doe" left project "Shop" When I visit dashboard page - Then I should see "John Doe left project at Shop" event + Then I should see "John Doe left project Shop" event diff --git a/features/dashboard/event_filters.feature b/features/dashboard/event_filters.feature index 41de0ae831..ec5680caba 100644 --- a/features/dashboard/event_filters.feature +++ b/features/dashboard/event_filters.feature @@ -1,5 +1,5 @@ @dashboard -Feature: Event filters +Feature: Event Filters Background: Given I sign in as a user And I own a project diff --git a/features/profile/group.feature b/features/dashboard/group.feature similarity index 73% rename from features/profile/group.feature rename to features/dashboard/group.feature index e2fbfde77b..cf4b8d7283 100644 --- a/features/profile/group.feature +++ b/features/dashboard/group.feature @@ -1,5 +1,5 @@ -@profile -Feature: Profile Group +@dashboard +Feature: Dashboard Group Background: Given I sign in as "John Doe" And "John Doe" is owner of group "Owned" @@ -10,18 +10,18 @@ Feature: Profile Group @javascript Scenario: Owner should be able to leave from group if he is not the last owner Given "Mary Jane" is owner of group "Owned" - When I visit profile groups page + When I visit dashboard groups page Then I should see group "Owned" in group list Then I should see group "Guest" in group list When I click on the "Leave" button for group "Owned" - And I visit profile groups page + And I visit dashboard groups page Then I should not see group "Owned" in group list Then I should see group "Guest" in group list @javascript Scenario: Owner should not be able to leave from group if he is the last owner Given "Mary Jane" is guest of group "Owned" - When I visit profile groups page + When I visit dashboard groups page Then I should see group "Owned" in group list Then I should see group "Guest" in group list Then I should not see the "Leave" button for group "Owned" @@ -29,20 +29,28 @@ Feature: Profile Group @javascript Scenario: Guest should be able to leave from group Given "Mary Jane" is guest of group "Guest" - When I visit profile groups page + When I visit dashboard groups page Then I should see group "Owned" in group list Then I should see group "Guest" in group list When I click on the "Leave" button for group "Guest" - When I visit profile groups page + When I visit dashboard groups page Then I should see group "Owned" in group list Then I should not see group "Guest" in group list @javascript Scenario: Guest should be able to leave from group even if he is the only user in the group - When I visit profile groups page + When I visit dashboard groups page Then I should see group "Owned" in group list Then I should see group "Guest" in group list When I click on the "Leave" button for group "Guest" - When I visit profile groups page + When I visit dashboard groups page Then I should see group "Owned" in group list Then I should not see group "Guest" in group list + + Scenario: Create a group from dasboard + And I visit dashboard groups page + And I click new group link + And submit form with new group "Samurai" info + Then I should be redirected to group "Samurai" page + And I should see newly created group "Samurai" + diff --git a/features/dashboard/help.feature b/features/dashboard/help.feature index 56a0a860ab..bca2772897 100644 --- a/features/dashboard/help.feature +++ b/features/dashboard/help.feature @@ -1,5 +1,5 @@ @dashboard -Feature: Help +Feature: Dashboard Help Background: Given I sign in as a user And I visit the "Rake Tasks" help page diff --git a/features/dashboard/issues.feature b/features/dashboard/issues.feature index 72627e43e0..99dad88a40 100644 --- a/features/dashboard/issues.feature +++ b/features/dashboard/issues.feature @@ -10,10 +10,12 @@ Feature: Dashboard Issues Scenario: I should see assigned issues Then I should see issues assigned to me + @javascript Scenario: I should see authored issues When I click "Authored by me" link Then I should see issues authored by me + @javascript Scenario: I should see all issues When I click "All" link Then I should see all issues diff --git a/features/dashboard/merge_requests.feature b/features/dashboard/merge_requests.feature index dcef1290e7..4a2c997d70 100644 --- a/features/dashboard/merge_requests.feature +++ b/features/dashboard/merge_requests.feature @@ -10,10 +10,12 @@ Feature: Dashboard Merge Requests Scenario: I should see assigned merge_requests Then I should see merge requests assigned to me + @javascript Scenario: I should see authored merge_requests When I click "Authored by me" link Then I should see merge requests authored by me + @javascript Scenario: I should see all merge_requests When I click "All" link Then I should see all merge requests diff --git a/features/dashboard/new_project.feature b/features/dashboard/new_project.feature new file mode 100644 index 0000000000..431dc4ccfc --- /dev/null +++ b/features/dashboard/new_project.feature @@ -0,0 +1,13 @@ +@dashboard +Feature: New Project +Background: + Given I sign in as a user + And I own project "Shop" + And I visit dashboard page + + @javascript + Scenario: I should see New projects page + Given I click "New project" link + Then I see "New project" page + When I click on "Import project from GitHub" + Then I see instructions on how to import from GitHub diff --git a/features/dashboard/projects.feature b/features/dashboard/projects.feature deleted file mode 100644 index 5214cfc28e..0000000000 --- a/features/dashboard/projects.feature +++ /dev/null @@ -1,9 +0,0 @@ -@dashboard -Feature: Dashboard projects - Background: - Given I sign in as a user - And I own project "Shop" - And I visit dashboard projects page - - Scenario: I should see projects list - Then I should see projects list diff --git a/features/dashboard/search.feature b/features/dashboard/search.feature deleted file mode 100644 index 24c4502869..0000000000 --- a/features/dashboard/search.feature +++ /dev/null @@ -1,10 +0,0 @@ -@dashboard -Feature: Dashboard Search - Background: - Given I sign in as a user - And I own project "Shop" - And I visit dashboard search page - - Scenario: I should see project I am looking for - Given I search for "Sho" - Then I should see "Shop" project link diff --git a/features/dashboard/shortcuts.feature b/features/dashboard/shortcuts.feature new file mode 100644 index 0000000000..41d79aa6ec --- /dev/null +++ b/features/dashboard/shortcuts.feature @@ -0,0 +1,21 @@ +@dashboard +Feature: Dashboard Shortcuts + Background: + Given I sign in as a user + And I visit dashboard page + + @javascript + Scenario: Navigate to projects tab + Given I press "g" and "p" + Then the active main tab should be Projects + + @javascript + Scenario: Navigate to issue tab + Given I press "g" and "i" + Then the active main tab should be Issues + + @javascript + Scenario: Navigate to merge requests tab + Given I press "g" and "m" + Then the active main tab should be Merge Requests + diff --git a/features/dashboard/starred_projects.feature b/features/dashboard/starred_projects.feature new file mode 100644 index 0000000000..9dfd2fbab9 --- /dev/null +++ b/features/dashboard/starred_projects.feature @@ -0,0 +1,12 @@ +@dashboard +Feature: Dashboard Starred Projects + Background: + Given I sign in as a user + And public project "Community" + And I starred project "Community" + And I own project "Shop" + And I visit dashboard starred projects page + + Scenario: I should see projects list + Then I should see project "Community" + And I should not see project "Shop" diff --git a/features/explore/public_groups.feature b/features/explore/groups.feature similarity index 97% rename from features/explore/public_groups.feature rename to features/explore/groups.feature index 825e3da934..c11634bd74 100644 --- a/features/explore/public_groups.feature +++ b/features/explore/groups.feature @@ -1,5 +1,5 @@ @public -Feature: Explore Groups Feature +Feature: Explore Groups Background: Given group "TestGroup" has private project "Enterprise" @@ -28,7 +28,6 @@ Feature: Explore Groups Feature Given group "TestGroup" has internal project "Internal" When I sign in as a user And I visit group "TestGroup" issues page - And I change filter to Everyone's Then I should see project "Internal" items And I should not see project "Enterprise" items @@ -36,7 +35,6 @@ Feature: Explore Groups Feature Given group "TestGroup" has internal project "Internal" When I sign in as a user And I visit group "TestGroup" merge requests page - And I change filter to Everyone's Then I should see project "Internal" items And I should not see project "Enterprise" items @@ -94,7 +92,6 @@ Feature: Explore Groups Feature Given group "TestGroup" has public project "Community" When I sign in as a user And I visit group "TestGroup" issues page - And I change filter to Everyone's Then I should see project "Community" items And I should see project "Internal" items And I should not see project "Enterprise" items @@ -104,7 +101,6 @@ Feature: Explore Groups Feature Given group "TestGroup" has public project "Community" When I sign in as a user And I visit group "TestGroup" merge requests page - And I change filter to Everyone's Then I should see project "Community" items And I should see project "Internal" items And I should not see project "Enterprise" items diff --git a/features/explore/projects.feature b/features/explore/projects.feature index 9827ebe3e5..a1b2972267 100644 --- a/features/explore/projects.feature +++ b/features/explore/projects.feature @@ -1,5 +1,5 @@ @public -Feature: Explore Projects Feature +Feature: Explore Projects Background: Given public project "Community" And internal project "Internal" diff --git a/features/group.feature b/features/groups.feature similarity index 91% rename from features/group.feature rename to features/groups.feature index b5ff03db84..415e43d6ae 100644 --- a/features/group.feature +++ b/features/groups.feature @@ -10,14 +10,6 @@ Feature: Groups Then I should see group "Owned" projects list And I should see projects activity feed - Scenario: Create a group from dasboard - When I visit group "Owned" page - And I visit dashboard page - And I click new group link - And submit form with new group "Samurai" info - Then I should be redirected to group "Samurai" page - And I should see newly created group "Samurai" - Scenario: I should see group "Owned" issues list Given project from group "Owned" has issues assigned to me When I visit group "Owned" issues page @@ -55,6 +47,21 @@ Feature: Groups Then I should not see group "Owned" avatar And I should not see the "Remove avatar" button + @javascript + Scenario: Add user to group + Given gitlab user "Mike" + When I visit group "Owned" members page + And I click link "Add members" + When I select "Mike" as "Reporter" + Then I should see "Mike" in team list as "Reporter" + + @javascript + Scenario: Invite user to group + When I visit group "Owned" members page + And I click link "Add members" + When I select "sjobs@apple.com" as "Reporter" + Then I should see "sjobs@apple.com" in team list as invited "Reporter" + # Leave @javascript diff --git a/features/invites.feature b/features/invites.feature new file mode 100644 index 0000000000..dc8eefaeae --- /dev/null +++ b/features/invites.feature @@ -0,0 +1,45 @@ +Feature: Invites + Background: + Given "John Doe" is owner of group "Owned" + And "John Doe" has invited "user@example.com" to group "Owned" + + Scenario: Viewing invitation when signed out + When I visit the invitation page + Then I should be redirected to the sign in page + And I should see a notice telling me to sign in + + Scenario: Signing in to view invitation + When I visit the invitation page + And I sign in as "Mary Jane" + Then I should be redirected to the invitation page + + Scenario: Viewing invitation when signed in + Given I sign in as "Mary Jane" + And I visit the invitation page + Then I should see the invitation details + And I should see an "Accept invitation" button + And I should see a "Decline" button + + Scenario: Viewing invitation as an existing member + Given I sign in as "John Doe" + And I visit the invitation page + Then I should see a message telling me I'm already a member + + Scenario: Accepting the invitation + Given I sign in as "Mary Jane" + And I visit the invitation page + And I click the "Accept invitation" button + Then I should be redirected to the group page + And I should see a notice telling me I have access + + Scenario: Declining the application when signed in + Given I sign in as "Mary Jane" + And I visit the invitation page + And I click the "Decline" button + Then I should be redirected to the dashboard + And I should see a notice telling me I have declined + + Scenario: Declining the application when signed out + When I visit the invitation's decline page + Then I should be redirected to the sign in page + And I should see a notice telling me I have declined diff --git a/features/profile/active_tab.feature b/features/profile/active_tab.feature index a99409d9fd..7801ae5b8c 100644 --- a/features/profile/active_tab.feature +++ b/features/profile/active_tab.feature @@ -1,5 +1,5 @@ @profile -Feature: Profile active tab +Feature: Profile Active Tab Background: Given I sign in as a user diff --git a/features/profile/profile.feature b/features/profile/profile.feature index d2125e013b..d586167cdf 100644 --- a/features/profile/profile.feature +++ b/features/profile/profile.feature @@ -71,6 +71,20 @@ Feature: Profile And I click on my profile picture Then I should see my user page + Scenario: I can manage application + Given I visit profile applications page + Then I click on new application button + And I should see application form + Then I fill application form out and submit + And I see application + Then I click edit + And I see edit application form + Then I change name of application and submit + And I see that application was changed + Then I visit profile applications page + And I click to remove application + Then I see that application is removed + @javascript Scenario: I change my application theme Given I visit profile design page diff --git a/features/project/active_tab.feature b/features/project/active_tab.feature index ce90a08668..05faad4e64 100644 --- a/features/project/active_tab.feature +++ b/features/project/active_tab.feature @@ -1,4 +1,4 @@ -Feature: Project active tab +Feature: Project Active Tab Background: Given I sign in as a user And I own a project @@ -106,24 +106,19 @@ Feature: Project active tab And no other sub tabs should be active And the active main tab should be Commits - # Sub Tabs: Issues - Scenario: On Project Issues/Browse Given I visit my project's issues page - Then the active sub tab should be Browse Issues - And no other sub tabs should be active - And the active main tab should be Issues + Then the active main tab should be Issues + And no other main tabs should be active Scenario: On Project Issues/Milestones Given I visit my project's issues page And I click the "Milestones" tab - Then the active sub tab should be Milestones - And no other sub tabs should be active - And the active main tab should be Issues + Then the active main tab should be Milestones + And no other main tabs should be active Scenario: On Project Issues/Labels Given I visit my project's issues page And I click the "Labels" tab - Then the active sub tab should be Labels - And no other sub tabs should be active - And the active main tab should be Issues + Then the active main tab should be Labels + And no other main tabs should be active diff --git a/features/project/archived.feature b/features/project/archived.feature index 9aac29384b..ad466f4f30 100644 --- a/features/project/archived.feature +++ b/features/project/archived.feature @@ -14,15 +14,6 @@ Feature: Project Archived And I visit project "Forum" page Then I should see "Archived" - Scenario: I should not see archived on projects page with no archived projects - And I visit dashboard projects page - Then I should not see "Archived" - - Scenario: I should see archived on projects page with archived projects - And project "Forum" is archived - And I visit dashboard projects page - Then I should see "Archived" - Scenario: I archive project When project "Shop" has push event And I visit project "Shop" page diff --git a/features/project/commits/branches.feature b/features/project/commits/branches.feature index abebef04fc..65d8e48b9b 100644 --- a/features/project/commits/branches.feature +++ b/features/project/commits/branches.feature @@ -1,4 +1,4 @@ -Feature: Project Browse branches +Feature: Project Commits Branches Background: Given I sign in as a user And I own project "Shop" @@ -15,5 +15,29 @@ Feature: Project Browse branches Scenario: I create a branch Given I visit project branches page And I click new branch link - When I submit new branch form + And I submit new branch form Then I should see new branch created + + @javascript + Scenario: I delete a branch + Given I visit project branches page + And I click branch 'improve/awesome' delete link + Then I should not see branch 'improve/awesome' + + Scenario: I create a branch with invalid name + Given I visit project branches page + And I click new branch link + And I submit new branch form with invalid name + Then I should see new an error that branch is invalid + + Scenario: I create a branch with invalid reference + Given I visit project branches page + And I click new branch link + And I submit new branch form with invalid reference + Then I should see new an error that ref is invalid + + Scenario: I create a branch that already exists + Given I visit project branches page + And I click new branch link + And I submit new branch form with branch that already exists + Then I should see new an error that branch already exists diff --git a/features/project/commits/comments.feature b/features/project/commits/comments.feature index a1aa745a68..c41075d7ad 100644 --- a/features/project/commits/comments.feature +++ b/features/project/commits/comments.feature @@ -1,4 +1,4 @@ -Feature: Comments on commits +Feature: Project Commits Comments Background: Given I sign in as a user And I own project "Shop" @@ -13,15 +13,10 @@ Feature: Comments on commits Scenario: I can't cancel the main form Then I should not see the cancel comment button - @javascript - Scenario: I can't preview without text - Given I haven't written any comment text - Then I should not see the comment preview button - @javascript Scenario: I can preview with text - Given I write a comment like "Nice" - Then I should see the comment preview button + Given I write a comment like ":+1: Nice" + Then The comment preview tab should be display rendered Markdown @javascript Scenario: I preview a comment @@ -32,7 +27,7 @@ Feature: Comments on commits @javascript Scenario: I can edit after preview Given I preview a comment text like "Bug fixed :smile:" - Then I should see the comment edit button + Then I should see the comment write tab @javascript Scenario: I have a reset form after posting from preview @@ -46,3 +41,9 @@ Feature: Comments on commits Given I leave a comment like "XML attached" And I delete a comment Then I should not see a comment saying "XML attached" + + @javascript + Scenario: I can edit a comment with +1 + Given I leave a comment like "XML attached" + And I edit the last comment with a +1 + Then I should see +1 in the description diff --git a/features/project/commits/commits.feature b/features/project/commits/commits.feature index 7c6db3c465..c4b206edc9 100644 --- a/features/project/commits/commits.feature +++ b/features/project/commits/commits.feature @@ -1,4 +1,4 @@ -Feature: Project Browse commits +Feature: Project Commits Background: Given I sign in as a user And I own a project @@ -21,18 +21,22 @@ Feature: Project Browse commits And I click side-by-side diff button Then I see inline diff button + @javascript Scenario: I compare refs Given I visit compare refs page And I fill compare fields with refs Then I see compared refs + And I unfold diff + Then I should see additional file lines Scenario: I browse commits for a specific path Given I visit my project's commits page for a specific path Then I see breadcrumb links - Scenario: I browse commits stats - Given I visit my project's commits stats page - Then I see commits stats + # TODO: Implement feature in graphs + #Scenario: I browse commits stats + #Given I visit my project's commits stats page + #Then I see commits stats Scenario: I browse big commit Given I visit big commit page diff --git a/features/project/commits/diff_comments.feature b/features/project/commits/diff_comments.feature index b26019f832..56b9a13678 100644 --- a/features/project/commits/diff_comments.feature +++ b/features/project/commits/diff_comments.feature @@ -1,4 +1,4 @@ -Feature: Comments on commit diffs +Feature: Project Commits Diff Comments Background: Given I sign in as a user And I own project "Shop" @@ -54,17 +54,11 @@ Feature: Comments on commit diffs Given I leave a diff comment like "Typo, please fix" Then I should see a discussion reply button - @javascript - Scenario: I can't preview without text - Given I open a diff comment form - And I haven't written any diff comment text - Then I should not see the diff comment preview button - @javascript Scenario: I can preview with text Given I open a diff comment form And I write a diff comment like ":-1: I don't like this" - Then I should see the diff comment preview button + Then The diff comment preview tab should display rendered Markdown @javascript Scenario: I preview a diff comment @@ -75,7 +69,7 @@ Feature: Comments on commit diffs @javascript Scenario: I can edit after preview Given I preview a diff comment text like "Should fix it :smile:" - Then I should see the diff comment edit button + Then I should see the diff comment write tab @javascript Scenario: The form gets removed after posting diff --git a/features/project/commits/tags.feature b/features/project/commits/tags.feature index 1ac0f8bfa4..02f399f7ca 100644 --- a/features/project/commits/tags.feature +++ b/features/project/commits/tags.feature @@ -1,4 +1,4 @@ -Feature: Project Browse tags +Feature: Project Commits Tags Background: Given I sign in as a user And I own project "Shop" @@ -7,5 +7,35 @@ Feature: Project Browse tags 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 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 + + @javascript + Scenario: I delete a tag + Given I delete tag 'v1.1.0' + Then I should not see tag 'v1.1.0' + + @javascript + Scenario: I delete all tags and see info message + Given I delete all tags + Then I should see tags info message + # @wip # Scenario: I can download project by tag diff --git a/features/project/commits/user_lookup.feature b/features/project/commits/user_lookup.feature index 7b194ab920..db51d4a6cf 100644 --- a/features/project/commits/user_lookup.feature +++ b/features/project/commits/user_lookup.feature @@ -1,4 +1,4 @@ -Feature: Project Browse Commits User Lookup +Feature: Project Commits User Lookup Background: Given I sign in as a user And I own a project diff --git a/features/project/create.feature b/features/project/create.feature index bb8e3a368e..e9dc4fe6b3 100644 --- a/features/project/create.feature +++ b/features/project/create.feature @@ -1,4 +1,4 @@ -Feature: Create Project +Feature: Project Create In order to get access to project sections A user with ability to create a project Should be able to create a new one diff --git a/features/project/deploy_keys.feature b/features/project/deploy_keys.feature index 13e3b9bbd2..a71f6124d9 100644 --- a/features/project/deploy_keys.feature +++ b/features/project/deploy_keys.feature @@ -6,7 +6,17 @@ Feature: Project Deploy Keys Scenario: I should see deploy keys list Given project has deploy key When I visit project deploy keys page - Then I should see project deploy keys + Then I should see project deploy key + + Scenario: I should see project deploy keys + Given other project has deploy key + When I visit project deploy keys page + Then I should see other project deploy key + + Scenario: I should see public deploy keys + Given public deploy key exists + When I visit project deploy keys page + Then I should see public deploy key Scenario: I add new deploy key Given I visit project deploy keys page @@ -15,9 +25,16 @@ Feature: Project Deploy Keys Then I should be on deploy keys page And I should see newly created deploy key - Scenario: I attach deploy key to project + Scenario: I attach other project deploy key to project Given other project has deploy key And I visit project deploy keys page When I click attach deploy key Then I should be on deploy keys page And I should see newly created deploy key + + Scenario: I attach public deploy key to project + Given public deploy key exists + And I visit project deploy keys page + When I click attach deploy key + Then I should be on deploy keys page + And I should see newly created deploy key diff --git a/features/project/edit_issuetracker.feature b/features/project/edit_issuetracker.feature deleted file mode 100644 index cc0de07ca6..0000000000 --- a/features/project/edit_issuetracker.feature +++ /dev/null @@ -1,18 +0,0 @@ -Feature: Project Issue Tracker - Background: - Given I sign in as a user - And I own project "Shop" - And project "Shop" has issues enabled - And I visit project "Shop" page - - Scenario: I set the issue tracker to "GitLab" - When I visit edit project "Shop" page - And change the issue tracker to "GitLab" - And I save project - Then I the project should have "GitLab" as issue tracker - - Scenario: I set the issue tracker to "Redmine" - When I visit edit project "Shop" page - And change the issue tracker to "Redmine" - And I save project - Then I the project should have "Redmine" as issue tracker diff --git a/features/project/fork.feature b/features/project/fork.feature index dc477ca3bf..22f68e5b34 100644 --- a/features/project/fork.feature +++ b/features/project/fork.feature @@ -1,4 +1,4 @@ -Feature: Fork Project +Feature: Project Fork Background: Given I sign in as a user And I am a member of project "Shop" @@ -6,9 +6,11 @@ Feature: Fork Project Scenario: User fork a project Given I click link "Fork" + When I fork to my namespace Then I should see the forked project page Scenario: User already has forked the project Given I already have a project named "Shop" in my namespace And I click link "Fork" + When I fork to my namespace Then I should see a "Name has already been taken" warning diff --git a/features/project/forked_merge_requests.feature b/features/project/forked_merge_requests.feature index 7442145d87..d9fbb875c2 100644 --- a/features/project/forked_merge_requests.feature +++ b/features/project/forked_merge_requests.feature @@ -11,18 +11,20 @@ Feature: Project Forked Merge Requests And I submit the merge request Then I should see merge request "Merge Request On Forked Project" - @javascript - Scenario: I can edit a forked merge request - Given I visit project "Forked Shop" merge requests page - And I click link "New Merge Request" - And I fill out a "Merge Request On Forked Project" merge request - And I submit the merge request - And I should see merge request "Merge Request On Forked Project" - And I click link edit "Merge Request On Forked Project" - Then I see the edit page prefilled for "Merge Request On Forked Project" - And I update the merge request title - And I save the merge request - Then I should see the edited merge request + # TODO: Improve it so it does not fail randomly + # + #@javascript + #Scenario: I can edit a forked merge request + #Given I visit project "Forked Shop" merge requests page + #And I click link "New Merge Request" + #And I fill out a "Merge Request On Forked Project" merge request + #And I submit the merge request + #And I should see merge request "Merge Request On Forked Project" + #And I click link edit "Merge Request On Forked Project" + #Then I see the edit page prefilled for "Merge Request On Forked Project" + #And I update the merge request title + #And I save the merge request + #Then I should see the edited merge request @javascript Scenario: I cannot submit an invalid merge request diff --git a/features/project/graph.feature b/features/project/graph.feature index cda95f5dda..89064242c1 100644 --- a/features/project/graph.feature +++ b/features/project/graph.feature @@ -2,8 +2,13 @@ Feature: Project Graph Background: Given I sign in as a user And I own project "Shop" - And I visit project "Shop" graph page @javascript Scenario: I should see project graphs + When I visit project "Shop" graph page Then page should have graphs + + @javascript + Scenario: I should see project commits graphs + When I visit project "Shop" commits graph page + Then page should have commits graphs diff --git a/features/project/issues/filter_labels.feature b/features/project/issues/filter_labels.feature index f4a0a7977c..e316f51986 100644 --- a/features/project/issues/filter_labels.feature +++ b/features/project/issues/filter_labels.feature @@ -1,4 +1,4 @@ -Feature: Project Filter Labels +Feature: Project Issues Filter Labels Background: Given I sign in as a user And I own project "Shop" @@ -8,11 +8,7 @@ Feature: Project Filter Labels And project "Shop" has issue "Feature1" with labels: "feature" Given I visit project "Shop" issues page - Scenario: I should see project issues - Then I should see "bug" in labels filter - And I should see "feature" in labels filter - And I should see "enhancement" in labels filter - + @javascript Scenario: I filter by one label Given I click link "bug" Then I should see "Bugfix1" in issues list diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature index b2e6f1f932..eb813884d1 100644 --- a/features/project/issues/issues.feature +++ b/features/project/issues/issues.feature @@ -25,6 +25,12 @@ Feature: Project Issues Given I click link "Release 0.4" Then I should see issue "Release 0.4" + @javascript + Scenario: I visit issue page + Given I add a user to project "Shop" + And I click "author" dropdown + Then I see current user as the first user + Scenario: I submit new unassigned issue Given I click link "New Issue" And I submit new issue "500 error on profile" @@ -42,6 +48,7 @@ Feature: Project Issues Given I visit issue page "Release 0.4" And I leave a comment like "XML attached" Then I should see comment "XML attached" + And I should see an error alert section within the comment form @javascript Scenario: I search issue @@ -63,6 +70,36 @@ Feature: Project Issues Then I should see "Release 0.3" in issues And I should not see "Release 0.4" in issues + @javascript + Scenario: Search issues when search string exactly matches issue description + Given project 'Shop' has issue 'Bugfix1' with description: 'Description for issue1' + And I fill in issue search with 'Description for issue1' + Then I should see 'Bugfix1' in issues + And I should not see "Release 0.4" in issues + And I should not see "Release 0.3" in issues + And I should not see "Tweet control" in issues + + @javascript + Scenario: Search issues when search string partially matches issue description + Given project 'Shop' has issue 'Bugfix1' with description: 'Description for issue1' + And project 'Shop' has issue 'Feature1' with description: 'Feature submitted for issue1' + And I fill in issue search with 'issue1' + Then I should see 'Feature1' in issues + Then I should see 'Bugfix1' in issues + And I should not see "Release 0.4" in issues + And I should not see "Release 0.3" in issues + And I should not see "Tweet control" in issues + + @javascript + Scenario: Search issues when search string matches no issue description + Given project 'Shop' has issue 'Bugfix1' with description: 'Description for issue1' + And I fill in issue search with 'Rock and roll' + Then I should not see 'Bugfix1' in issues + And I should not see "Release 0.4" in issues + And I should not see "Release 0.3" in issues + And I should not see "Tweet control" in issues + + # Markdown Scenario: Headers inside the description should have ids generated for them. @@ -89,3 +126,94 @@ Feature: Project Issues Given I click link "New Issue" And I submit new issue "500 error on profile" Then I should see issue "500 error on profile" + + Scenario: Clickable labels + Given issue 'Release 0.4' has label 'bug' + And I visit project "Shop" issues page + When I click label 'bug' + And I should see "Release 0.4" in issues + And I should not see "Tweet control" in issues + + Scenario: Issue description should render task checkboxes + Given project "Shop" has "Tasks-open" open issue with task markdown + When I visit issue page "Tasks-open" + Then I should see task checkboxes in the description + + @javascript + Scenario: Issue notes should not render task checkboxes + Given project "Shop" has "Tasks-open" open issue with task markdown + When I visit issue page "Tasks-open" + And I leave a comment with task markdown + Then I should not see task checkboxes in the comment + + @javascript + Scenario: Issue notes should be editable with +1 + Given project "Shop" has "Tasks-open" open issue with task markdown + When I visit issue page "Tasks-open" + And I leave a comment with a header containing "Comment with a header" + Then The comment with the header should not have an ID + And I edit the last comment with a +1 + Then I should see +1 in the description + + # Task status in issues list + + Scenario: Issues list should display task status + Given project "Shop" has "Tasks-open" open issue with task markdown + When I visit project "Shop" issues page + Then I should see the task status for the Taskable + + # Toggling task items + + @javascript + Scenario: Task checkboxes should be enabled for an open issue + Given project "Shop" has "Tasks-open" open issue with task markdown + When I visit issue page "Tasks-open" + Then Task checkboxes should be enabled + + @javascript + Scenario: Task checkboxes should be disabled for a closed issue + Given project "Shop" has "Tasks-closed" closed issue with task markdown + When I visit issue page "Tasks-closed" + Then Task checkboxes should be disabled + + # Issue description preview + + @javascript + Scenario: I can't preview without text + Given I click link "New Issue" + And I haven't written any description text + Then The Markdown preview tab should say there is nothing to do + + @javascript + Scenario: I can preview with text + Given I click link "New Issue" + And I write a description like ":+1: Nice" + Then The Markdown preview tab should display rendered Markdown + + @javascript + Scenario: I preview an issue description + Given I click link "New Issue" + And I preview a description text like "Bug fixed :smile:" + Then I should see the Markdown preview + And I should not see the Markdown text field + + @javascript + Scenario: I can edit after preview + Given I click link "New Issue" + And I preview a description text like "Bug fixed :smile:" + Then I should see the Markdown write tab + + @javascript + Scenario: I can preview when editing an existing issue + Given I click link "Release 0.4" + And I click link "Edit" for the issue + And I preview a description text like "Bug fixed :smile:" + Then I should see the Markdown write tab + + @javascript + Scenario: I can unsubscribe from issue + Given project "Shop" has "Tasks-open" open issue with task markdown + When I visit issue page "Tasks-open" + Then I should see that I am subscribed + When I click button "Unsubscribe" + Then I should see that I am unsubscribed diff --git a/features/project/issues/labels.feature b/features/project/issues/labels.feature index 29cf530727..039a7d83cb 100644 --- a/features/project/issues/labels.feature +++ b/features/project/issues/labels.feature @@ -1,4 +1,4 @@ -Feature: Project Labels +Feature: Project Issues Labels Background: Given I sign in as a user And I own project "Shop" @@ -6,8 +6,8 @@ Feature: Project Labels Given I visit project "Shop" labels page Scenario: I should see labels list - Then I should see label "bug" - And I should see label "feature" + Then I should see label 'bug' + And I should see label 'feature' Scenario: I create new label Given I visit project "Shop" new label page @@ -24,6 +24,11 @@ Feature: Project Labels When I remove label 'bug' Then I should not see label 'bug' + @javascript + Scenario: I remove all labels + When I delete all labels + Then I should see labels help message + Scenario: I create a label with invalid color Given I visit project "Shop" new label page When I submit new label with invalid color @@ -40,4 +45,3 @@ Feature: Project Labels And I visit project "Forum" new label page When I submit new label 'bug' Then I should see label 'bug' - diff --git a/features/project/issues/milestones.feature b/features/project/issues/milestones.feature index e67b5d2d86..9ac65b1257 100644 --- a/features/project/issues/milestones.feature +++ b/features/project/issues/milestones.feature @@ -1,4 +1,4 @@ -Feature: Project Milestones +Feature: Project Issues Milestones Background: Given I sign in as a user And I own project "Shop" diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature index 8b6c296dfe..cbb5c8eb39 100644 --- a/features/project/merge_requests.feature +++ b/features/project/merge_requests.feature @@ -96,6 +96,16 @@ Feature: Project Merge Requests And I leave a comment with a header containing "Comment with a header" Then The comment with the header should not have an ID + Scenario: Merge request description should render task checkboxes + Given project "Shop" has "MR-task-open" open MR with task markdown + When I visit merge request page "MR-task-open" + Then I should see task checkboxes in the description + + Scenario: Merge request notes should not render task checkboxes + Given project "Shop" has "MR-task-open" open MR with task markdown + When I visit merge request page "MR-task-open" + Then I should not see task checkboxes in the comment + # Toggling inline comments @javascript @@ -105,7 +115,7 @@ Feature: Project Merge Requests And I switch to the diff tab And I leave a comment like "Line is wrong" on line 39 of the second file And I click link "Hide inline discussion" of the second file - Then I should not see a comment like "Line is wrong" in the second file + Then I should not see a comment like "Line is wrong here" in the second file @javascript Scenario: I show comments on a merge request diff with comments in a single file @@ -113,8 +123,6 @@ Feature: Project Merge Requests And I visit merge request page "Bug NS-05" And I switch to the diff tab And I leave a comment like "Line is wrong" on line 39 of the second file - And I click link "Hide inline discussion" of the second file - And I click link "Show inline discussion" of the second file Then I should see a comment like "Line is wrong" in the second file @javascript @@ -125,7 +133,7 @@ Feature: Project Merge Requests And I leave a comment like "Line is correct" on line 12 of the first file And I leave a comment like "Line is wrong" on line 39 of the second file And I click link "Hide inline discussion" of the second file - Then I should not see a comment like "Line is wrong" in the second file + Then I should not see a comment like "Line is wrong here" in the second file And I should still see a comment like "Line is correct" in the first file @javascript @@ -147,3 +155,87 @@ Feature: Project Merge Requests And I switch to the diff tab And I unfold diff Then I should see additional file lines + + @javascript + Scenario: I show comments on a merge request side-by-side diff with comments in multiple files + Given project "Shop" have "Bug NS-05" open merge request with diffs inside + And I visit merge request page "Bug NS-05" + And I switch to the diff tab + And I leave a comment like "Line is correct" on line 12 of the first file + And I leave a comment like "Line is wrong" on line 39 of the second file + And I click Side-by-side Diff tab + Then I should see comments on the side-by-side diff page + + @javascript + Scenario: I view diffs on a merge request + Given project "Shop" have "Bug NS-05" open merge request with diffs inside + And I visit merge request page "Bug NS-05" + And I click on the Changes tab via Javascript + Then I should see the proper Inline and Side-by-side links + + # Task status in issues list + + Scenario: Merge requests list should display task status + Given project "Shop" has "MR-task-open" open MR with task markdown + When I visit project "Shop" merge requests page + Then I should see the task status for the Taskable + + # Toggling task items + + @javascript + Scenario: Task checkboxes should be enabled for an open merge request + Given project "Shop" has "MR-task-open" open MR with task markdown + When I visit merge request page "MR-task-open" + Then Task checkboxes should be enabled + + @javascript + Scenario: Task checkboxes should be disabled for a closed merge request + Given project "Shop" has "MR-task-open" open MR with task markdown + And I visit merge request page "MR-task-open" + And I click link "Close" + Then Task checkboxes should be disabled + + # Description preview + + @javascript + Scenario: I can't preview without text + Given I visit merge request page "Bug NS-04" + And I click link "Edit" for the merge request + And I haven't written any description text + Then The Markdown preview tab should say there is nothing to do + + @javascript + Scenario: I can preview with text + Given I visit merge request page "Bug NS-04" + And I click link "Edit" for the merge request + And I write a description like ":+1: Nice" + Then The Markdown preview tab should display rendered Markdown + + @javascript + Scenario: I preview a merge request description + Given I visit merge request page "Bug NS-04" + And I click link "Edit" for the merge request + And I preview a description text like "Bug fixed :smile:" + Then I should see the Markdown preview + And I should not see the Markdown text field + + @javascript + Scenario: I can edit after preview + Given I visit merge request page "Bug NS-04" + And I click link "Edit" for the merge request + And I preview a description text like "Bug fixed :smile:" + Then I should see the Markdown write tab + + @javascript + Scenario: I search merge request + Given I click link "All" + When I fill in merge request search with "Fe" + Then I should see "Feature NS-03" in merge requests + And I should not see "Bug NS-04" in merge requests + + @javascript + Scenario: I can unsubscribe from merge request + Given I visit merge request page "Bug NS-04" + Then I should see that I am subscribed + When I click button "Unsubscribe" + Then I should see that I am unsubscribed diff --git a/features/project/network.feature b/features/project/network_graph.feature similarity index 100% rename from features/project/network.feature rename to features/project/network_graph.feature diff --git a/features/project/project.feature b/features/project/project.feature index c1f192f123..3e1fd54bee 100644 --- a/features/project/project.feature +++ b/features/project/project.feature @@ -1,10 +1,23 @@ -Feature: Project Feature +Feature: Project Background: Given I sign in as a user And I own project "Shop" And project "Shop" has push event And I visit project "Shop" page + Scenario: I edit the project avatar + Given I visit edit project "Shop" page + When I change the project avatar + And I should see new project avatar + And I should see the "Remove avatar" button + + Scenario: I remove the project avatar + Given I visit edit project "Shop" page + And I have an project avatar + When I remove my project avatar + Then I should see the default project avatar + And I should not see the "Remove avatar" button + @javascript Scenario: I should see project activity When I visit project "Shop" page @@ -27,7 +40,6 @@ Feature: Project Feature Scenario: I should see project readme and version When I visit project "Shop" page - Then I should see project "Shop" README link And I should see project "Shop" version Scenario: I should change project default branch @@ -35,3 +47,11 @@ Feature: Project Feature And change project default branch And I save project Then I should see project default branch changed + + @javascript + Scenario: I should have default tab per my preference + And I own project "Forum" + When I select project "Forum" README tab + Then I should see project "Forum" README + And I visit project "Shop" page + Then I should see project "Shop" README diff --git a/features/project/service.feature b/features/project/service.feature index a5af065c9e..fdff640ec8 100644 --- a/features/project/service.feature +++ b/features/project/service.feature @@ -19,6 +19,12 @@ Feature: Project Services And I fill hipchat settings Then I should see hipchat service settings saved + Scenario: Activate hipchat service with custom server + When I visit project "Shop" services page + And I click hipchat service link + And I fill hipchat settings with custom server + Then I should see hipchat service settings with custom server saved + Scenario: Activate pivotaltracker service When I visit project "Shop" services page And I click pivotaltracker service link @@ -43,8 +49,38 @@ Feature: Project Services And I fill Slack settings Then I should see Slack service settings saved + Scenario: Activate Pushover service + When I visit project "Shop" services page + And I click Pushover service link + And I fill Pushover settings + Then I should see Pushover service settings saved + Scenario: Activate email on push service When I visit project "Shop" services page And I click email on push service link And I fill email on push settings Then I should see email on push service settings saved + + Scenario: Activate Irker (IRC Gateway) service + When I visit project "Shop" services page + And I click Irker service link + And I fill Irker settings + Then I should see Irker service settings saved + + Scenario: Activate Atlassian Bamboo CI service + When I visit project "Shop" services page + And I click Atlassian Bamboo CI service link + And I fill Atlassian Bamboo CI settings + Then I should see Atlassian Bamboo CI service settings saved + + Scenario: Activate jetBrains TeamCity CI service + When I visit project "Shop" services page + And I click jetBrains TeamCity CI service link + And I fill jetBrains TeamCity CI settings + Then I should see jetBrains TeamCity CI service settings saved + + Scenario: Activate Asana service + When I visit project "Shop" services page + And I click Asana service link + And I fill Asana settings + Then I should see Asana service settings saved diff --git a/features/project/shortcuts.feature b/features/project/shortcuts.feature new file mode 100644 index 0000000000..cfb68bf1f5 --- /dev/null +++ b/features/project/shortcuts.feature @@ -0,0 +1,52 @@ +@dashboard +Feature: Project Shortcuts + Background: + Given I sign in as a user + And I own a project + And I visit my project's home page + + @javascript + Scenario: Navigate to files tab + Given I press "g" and "f" + Then the active main tab should be Files + + @javascript + Scenario: Navigate to commits tab + Given I press "g" and "c" + Then the active main tab should be Commits + + @javascript + Scenario: Navigate to network tab + Given I press "g" and "n" + Then the active main tab should be Network + + @javascript + Scenario: Navigate to graphs tab + Given I press "g" and "g" + Then the active main tab should be Graphs + + @javascript + Scenario: Navigate to issues tab + Given I press "g" and "i" + Then the active main tab should be Issues + + @javascript + Scenario: Navigate to merge requests tab + Given I press "g" and "m" + Then the active main tab should be Merge Requests + + @javascript + Scenario: Navigate to snippets tab + Given I press "g" and "s" + Then the active main tab should be Snippets + + @javascript + Scenario: Navigate to wiki tab + Given I press "g" and "w" + Then the active main tab should be Wiki + + @javascript + Scenario: Navigate to project feed + Given I visit my project's files page + Given I press "g" and "p" + Then the active main tab should be Home diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature index f8934da8de..90b966dd64 100644 --- a/features/project/source/browse_files.feature +++ b/features/project/source/browse_files.feature @@ -1,4 +1,4 @@ -Feature: Project Browse files +Feature: Project Source Browse Files Background: Given I sign in as a user And I own project "Shop" @@ -13,41 +13,148 @@ Feature: Project Browse files Scenario: I browse file content Given I click on ".gitignore" file in repo - Then I should see it content + Then I should see its content Scenario: I browse raw file Given I visit blob file from repo - And I click link "raw" + And I click link "Raw" Then I should see raw file content Scenario: I can create file Given I click on "new file" link in repo Then I can see new file page + @javascript + Scenario: I can create and commit file + Given I click on "new file" link in repo + And I edit code + And I fill the new file name + And I fill the commit message + And I click on "Commit Changes" + Then I am redirected to the new file + And I should see its new content + + @javascript + Scenario: I can create and commit file and specify new branch + Given I click on "new file" link in repo + And I edit code + And I fill the new file name + And I fill the commit message + And I fill the new branch name + And I click on "Commit Changes" + Then I am redirected to the new file on new branch + And I should see its new content + + @javascript @tricky + Scenario: I can create file in empty repo + Given I own an empty project + And I visit my empty project page + And I create bare repo + When I click on "add a file" link + And I edit code + And I fill the new file name + And I fill the commit message + And I click on "Commit Changes" + Then I am redirected to the new file + And I should see its new content + + @javascript + Scenario: If I enter an illegal file name I see an error message + Given I click on "new file" link in repo + And I fill the new file name with an illegal name + And I edit code + And I fill the commit message + And I click on "Commit changes" + Then I am on the new file page + And I see a commit error message + @javascript Scenario: I can edit file Given I click on ".gitignore" file in repo - And I click button "edit" + And I click button "Edit" Then I can edit code + Scenario: If the file is binary the edit link is hidden + Given I visit a binary file in the repo + Then I cannot see the edit button + + Scenario: If I don't have edit permission the edit link is disabled + Given public project "Community" + And I visit project "Community" source page + And I click on ".gitignore" file in repo + Then The edit button is disabled + + @javascript + Scenario: I can edit and commit file + Given I click on ".gitignore" file in repo + And I click button "Edit" + And I edit code + And I fill the commit message + And I click on "Commit Changes" + Then I am redirected to the ".gitignore" + And I should see its new content + + @javascript + Scenario: I can edit and commit file to new branch + Given I click on ".gitignore" file in repo + And I click button "Edit" + And I edit code + And I fill the commit message + And I fill the new branch name + And I click on "Commit Changes" + Then I am redirected to the ".gitignore" on new branch + And I should see its new content + + @javascript @wip + Scenario: If I don't change the content of the file I see an error message + Given I click on ".gitignore" file in repo + And I click button "edit" + And I fill the commit message + And I click on "Commit changes" + # Test fails because carriage returns are added to the file. + Then I am on the ".gitignore" edit file page + And I see a commit error message + @javascript Scenario: I can see editing preview Given I click on ".gitignore" file in repo - And I click button "edit" + And I click button "Edit" And I edit code And I click link "Diff" Then I see diff + @javascript + Scenario: I can remove file and commit + Given I click on ".gitignore" file in repo + And I see the ".gitignore" + And I click on "Remove" + And I fill the commit message + And I click on "Remove file" + Then I am redirected to the files URL + And I don't see the ".gitignore" + Scenario: I can browse directory with Browse Dir Given I click on files directory - And I click on history link + And I click on History link Then I see Browse dir link Scenario: I can browse file with Browse File Given I click on readme file - And I click on history link + And I click on History link Then I see Browse file link Scenario: I can browse code with Browse Code - Given I click on history link + Given I click on History link Then I see Browse code link + + # Permalink + + Scenario: I click on the permalink link from a branch ref + Given I click on ".gitignore" file in repo + And I click on Permalink + Then I am redirected to the permalink URL + + Scenario: I don't see the permalink link from a SHA ref + Given I visit project source page for "6d394385cf567f80a8fd85055db1ab4c5295806f" + And I click on ".gitignore" file in repo + Then I don't see the permalink link diff --git a/features/project/source/git_blame.feature b/features/project/source/git_blame.feature index ae62c166c1..48b1077dc6 100644 --- a/features/project/source/git_blame.feature +++ b/features/project/source/git_blame.feature @@ -1,4 +1,4 @@ -Feature: Project Browse git repo +Feature: Project Source Git Blame Background: Given I sign in as a user And I own project "Shop" @@ -6,5 +6,5 @@ Feature: Project Browse git repo Scenario: I blame file Given I click on ".gitignore" file in repo - And I click blame button + And I click Blame button Then I should see git file blame diff --git a/features/project/source/markdown_render.feature b/features/project/source/markdown_render.feature index fce351317c..ecbd721c28 100644 --- a/features/project/source/markdown_render.feature +++ b/features/project/source/markdown_render.feature @@ -1,4 +1,4 @@ -Feature: Project markdown render +Feature: Project Source Markdown Render Background: Given I sign in as a user And I own project "Delta" diff --git a/features/project/source/multiselect_blob.feature b/features/project/source/multiselect_blob.feature index f60b646a8d..63b7cb77a9 100644 --- a/features/project/source/multiselect_blob.feature +++ b/features/project/source/multiselect_blob.feature @@ -1,4 +1,4 @@ -Feature: Project Multiselect Blob +Feature: Project Source Multiselect Blob Background: Given I sign in as a user And I own project "Shop" diff --git a/features/project/source/search_code.feature b/features/project/source/search_code.feature index 93b326696d..4f9dcea249 100644 --- a/features/project/source/search_code.feature +++ b/features/project/source/search_code.feature @@ -1,4 +1,4 @@ -Feature: Project Search code +Feature: Project Source Search Code Background: Given I sign in as a user diff --git a/features/project/star.feature b/features/project/star.feature index 3322f89180..a45f9c470e 100644 --- a/features/project/star.feature +++ b/features/project/star.feature @@ -13,7 +13,7 @@ Feature: Project Star Given public project "Community" And I visit project "Community" page When I click on the star toggle button - Then The project has 0 stars + Then I redirected to sign in page @javascript Scenario: Signed in users can toggle star diff --git a/features/project/team_management.feature b/features/project/team_management.feature index e153978e04..6cda225ea7 100644 --- a/features/project/team_management.feature +++ b/features/project/team_management.feature @@ -1,4 +1,4 @@ -Feature: Project Team management +Feature: Project Team Management Background: Given I sign in as a user And I own project "Shop" @@ -13,10 +13,16 @@ Feature: Project Team management @javascript Scenario: Add user to project - Given I click link "New Team Member" + Given I click link "Add members" And I select "Mike" as "Reporter" Then I should see "Mike" in team list as "Reporter" + @javascript + Scenario: Invite user to project + Given I click link "Add members" + And I select "sjobs@apple.com" as "Reporter" + Then I should see "sjobs@apple.com" in team list as invited "Reporter" + @javascript Scenario: Update user access Given I should see "Sam" in team list as "Developer" diff --git a/features/project/wiki.feature b/features/project/wiki.feature index 4a8c771dda..977cd609a1 100644 --- a/features/project/wiki.feature +++ b/features/project/wiki.feature @@ -62,3 +62,27 @@ Feature: Project Wiki And I browse to wiki page with images And I click on image link Then I should see the new wiki page form + + @javascript + Scenario: New Wiki page that has a path + Given I create a New page with paths + And I click on the "Pages" button + Then I should see non-escaped link in the pages list + + @javascript + Scenario: Edit Wiki page that has a path + Given I create a New page with paths + And I click on the "Pages" button + And I edit the Wiki page with a path + Then I should see a non-escaped path + And I should see the Editing page + And I change the content + Then I should see the updated content + + @javascript + Scenario: View the page history of a Wiki page that has a path + Given I create a New page with paths + And I click on the "Pages" button + And I view the page history of a Wiki page that has a path + Then I should see a non-escaped path + And I should see the page history diff --git a/features/search.feature b/features/search.feature new file mode 100644 index 0000000000..def21e0092 --- /dev/null +++ b/features/search.feature @@ -0,0 +1,46 @@ +@dashboard +Feature: Search + Background: + Given I sign in as a user + And I own project "Shop" + And I visit dashboard search page + + Scenario: I should see project I am looking for + Given I search for "Sho" + Then I should see "Shop" project link + + Scenario: I should see issues I am looking for + And project has issues + When I search for "Foo" + And I click "Issues" link + Then I should see "Foo" link in the search results + And I should not see "Bar" link in the search results + + Scenario: I should see merge requests I am looking for + And project has merge requests + When I search for "Foo" + When I click "Merge requests" link + Then I should see "Foo" link in the search results + And I should not see "Bar" link in the search results + + Scenario: I should see project code I am looking for + When I click project "Shop" link + And I search for "rspec" + Then I should see code results for project "Shop" + + Scenario: I should see project issues + And project has issues + When I click project "Shop" link + And I search for "Foo" + And I click "Issues" link + Then I should see "Foo" link in the search results + And I should not see "Bar" link in the search results + + Scenario: I should see project merge requests + And project has merge requests + When I click project "Shop" link + And I search for "Foo" + And I click "Merge requests" link + Then I should see "Foo" link in the search results + And I should not see "Bar" link in the search results + diff --git a/features/snippet_search.feature b/features/snippet_search.feature new file mode 100644 index 0000000000..834bd3b237 --- /dev/null +++ b/features/snippet_search.feature @@ -0,0 +1,20 @@ +@dashboard +Feature: Snippet Search + Background: + Given I sign in as a user + And I have public "Personal snippet one" snippet + And I have private "Personal snippet private" snippet + And I have a public many lined snippet + + Scenario: I should see my public and private snippets + When I search for "snippet" in snippet titles + Then I should see "Personal snippet one" in results + And I should see "Personal snippet private" in results + + Scenario: I should see three surrounding lines on either side of a matching snippet line + When I search for "line seven" in snippet contents + Then I should see "line four" in results + And I should see "line seven" in results + And I should see "line ten" in results + And I should not see "line three" in results + And I should not see "line eleven" in results diff --git a/features/snippets/discover.feature b/features/snippets/discover.feature index f0b8d3a408..1a7e132ea2 100644 --- a/features/snippets/discover.feature +++ b/features/snippets/discover.feature @@ -1,11 +1,13 @@ @snippets -Feature: Discover Snippets +Feature: Snippets Discover Background: Given I sign in as a user And I have public "Personal snippet one" snippet And I have private "Personal snippet private" snippet + And I have internal "Personal snippet internal" snippet Scenario: I should see snippets Given I visit snippets page Then I should see "Personal snippet one" in snippets + And I should see "Personal snippet internal" in snippets And I should not see "Personal snippet private" in snippets diff --git a/features/snippets/public_snippets.feature b/features/snippets/public_snippets.feature new file mode 100644 index 0000000000..c2afb63b6d --- /dev/null +++ b/features/snippets/public_snippets.feature @@ -0,0 +1,10 @@ +Feature: Public snippets + Scenario: Unauthenticated user should see public snippets + Given There is public "Personal snippet one" snippet + And I visit snippet page "Personal snippet one" + Then I should see snippet "Personal snippet one" + + Scenario: Unauthenticated user should see raw public snippets + Given There is public "Personal snippet one" snippet + And I visit snippet raw page "Personal snippet one" + Then I should see raw snippet "Personal snippet one" diff --git a/features/snippets/snippets.feature b/features/snippets/snippets.feature index 38216dd5b7..6e8019c326 100644 --- a/features/snippets/snippets.feature +++ b/features/snippets/snippets.feature @@ -1,5 +1,5 @@ @snippets -Feature: Snippets Feature +Feature: Snippets Background: Given I sign in as a user And I have public "Personal snippet one" snippet @@ -25,4 +25,4 @@ Feature: Snippets Feature Scenario: I destroy "Personal snippet one" Given I visit snippet page "Personal snippet one" And I click link "Destroy" - Then I should not see "Personal snippet one" in snippets + Then I should not see "Personal snippet one" in snippets \ No newline at end of file diff --git a/features/snippets/user.feature b/features/snippets/user.feature index d032a33686..5b5dadb7b3 100644 --- a/features/snippets/user.feature +++ b/features/snippets/user.feature @@ -1,19 +1,22 @@ @snippets -Feature: User Snippets +Feature: Snippets User Background: Given I sign in as a user And I have public "Personal snippet one" snippet And I have private "Personal snippet private" snippet + And I have internal "Personal snippet internal" snippet Scenario: I should see all my snippets Given I visit my snippets page Then I should see "Personal snippet one" in snippets And I should see "Personal snippet private" in snippets + And I should see "Personal snippet internal" in snippets Scenario: I can see only my private snippets Given I visit my snippets page And I click "Private" filter Then I should not see "Personal snippet one" in snippets + And I should not see "Personal snippet internal" in snippets And I should see "Personal snippet private" in snippets Scenario: I can see only my public snippets @@ -21,3 +24,11 @@ Feature: User Snippets And I click "Public" filter Then I should see "Personal snippet one" in snippets And I should not see "Personal snippet private" in snippets + And I should not see "Personal snippet internal" in snippets + + Scenario: I can see only my internal snippets + Given I visit my snippets page + And I click "Internal" filter + Then I should see "Personal snippet internal" in snippets + And I should not see "Personal snippet private" in snippets + And I should not see "Personal snippet one" in snippets diff --git a/features/steps/admin/active_tab.rb b/features/steps/admin/active_tab.rb index 8f09e51cce..90d13abdb1 100644 --- a/features/steps/admin/active_tab.rb +++ b/features/steps/admin/active_tab.rb @@ -1,37 +1,37 @@ -class AdminActiveTab < Spinach::FeatureSteps +class Spinach::Features::AdminActiveTab < Spinach::FeatureSteps include SharedAuthentication include SharedPaths include SharedActiveTab - Then 'the active main tab should be Home' do + step 'the active main tab should be Home' do ensure_active_main_tab('Overview') end - Then 'the active main tab should be Projects' do + step 'the active main tab should be Projects' do ensure_active_main_tab('Projects') end - Then 'the active main tab should be Groups' do + step 'the active main tab should be Groups' do ensure_active_main_tab('Groups') end - Then 'the active main tab should be Users' do + step 'the active main tab should be Users' do ensure_active_main_tab('Users') end - Then 'the active main tab should be Logs' do + step 'the active main tab should be Logs' do ensure_active_main_tab('Logs') end - Then 'the active main tab should be Hooks' do + step 'the active main tab should be Hooks' do ensure_active_main_tab('Hooks') end - Then 'the active main tab should be Resque' do + step 'the active main tab should be Resque' do ensure_active_main_tab('Background Jobs') end - Then 'the active main tab should be Messages' do + step 'the active main tab should be Messages' do ensure_active_main_tab('Messages') end end diff --git a/features/steps/admin/applications.rb b/features/steps/admin/applications.rb new file mode 100644 index 0000000000..d59088fa3c --- /dev/null +++ b/features/steps/admin/applications.rb @@ -0,0 +1,55 @@ +class Spinach::Features::AdminApplications < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedAdmin + + step 'I click on new application button' do + click_on 'New Application' + end + + step 'I should see application form' do + page.should have_content "New application" + end + + step 'I fill application form out and submit' do + fill_in :doorkeeper_application_name, with: 'test' + fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com' + click_on "Submit" + end + + step 'I see application' do + page.should have_content "Application: test" + page.should have_content "Application Id" + page.should have_content "Secret" + end + + step 'I click edit' do + click_on "Edit" + end + + step 'I see edit application form' do + page.should have_content "Edit application" + end + + step 'I change name of application and submit' do + page.should have_content "Edit application" + fill_in :doorkeeper_application_name, with: 'test_changed' + click_on "Submit" + end + + step 'I see that application was changed' do + page.should have_content "test_changed" + page.should have_content "Application Id" + page.should have_content "Secret" + end + + step 'I click to remove application' do + within '.oauth-applications' do + click_on "Destroy" + end + end + + step "I see that application is removed" do + page.find(".oauth-applications").should_not have_content "test_changed" + end +end diff --git a/features/steps/admin/deploy_keys.rb b/features/steps/admin/deploy_keys.rb new file mode 100644 index 0000000000..fb0b611762 --- /dev/null +++ b/features/steps/admin/deploy_keys.rb @@ -0,0 +1,57 @@ +class Spinach::Features::AdminDeployKeys < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedAdmin + + step 'there are public deploy keys in system' do + create(:deploy_key, public: true) + create(:another_deploy_key, public: true) + end + + step 'I should see all public deploy keys' do + DeployKey.are_public.each do |p| + page.should have_content p.title + end + end + + step 'I click on first deploy key' do + click_link DeployKey.are_public.first.title + end + + step 'I should see deploy key details' do + deploy_key = DeployKey.are_public.first + current_path.should == admin_deploy_key_path(deploy_key) + page.should have_content(deploy_key.title) + page.should have_content(deploy_key.key) + end + + step 'I visit admin deploy key page' do + visit admin_deploy_key_path(deploy_key) + end + + step 'I visit admin deploy keys page' do + visit admin_deploy_keys_path + end + + step 'I click \'New Deploy Key\'' do + click_link 'New Deploy Key' + end + + step 'I submit new deploy key' do + fill_in "deploy_key_title", with: "laptop" + fill_in "deploy_key_key", with: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop" + click_button "Create" + end + + step 'I should be on admin deploy keys page' do + current_path.should == admin_deploy_keys_path + end + + step 'I should see newly created deploy key' do + page.should have_content(deploy_key.title) + end + + def deploy_key + @deploy_key ||= DeployKey.are_public.first + end +end diff --git a/features/steps/admin/groups.rb b/features/steps/admin/groups.rb index 9c1bcfefb9..721460b937 100644 --- a/features/steps/admin/groups.rb +++ b/features/steps/admin/groups.rb @@ -1,4 +1,4 @@ -class AdminGroups < Spinach::FeatureSteps +class Spinach::Features::AdminGroups < Spinach::FeatureSteps include SharedAuthentication include SharedPaths include SharedUser @@ -13,7 +13,7 @@ class AdminGroups < Spinach::FeatureSteps click_link "New Group" end - And 'I have group with projects' do + step 'I have group with projects' do @group = create(:group) @project = create(:project, group: @group) @event = create(:closed_issue_event, project: @project) @@ -21,31 +21,30 @@ class AdminGroups < Spinach::FeatureSteps @project.team << [current_user, :master] end - And 'submit form with new group info' do - fill_in 'group_name', with: 'gitlab' + step 'submit form with new group info' do + fill_in 'group_path', with: 'gitlab' fill_in 'group_description', with: 'Group description' click_button "Create group" end - Then 'I should see newly created group' do + step 'I should see newly created group' do page.should have_content "Group: gitlab" page.should have_content "Group description" end - Then 'I should be redirected to group page' do - current_path.should == admin_group_path(Group.last) + step 'I should be redirected to group page' do + current_path.should == admin_group_path(Group.find_by(path: 'gitlab')) end When 'I select user "John Doe" from user list as "Reporter"' do - user = User.find_by(name: "John Doe") - select2(user.id, from: "#user_ids", multiple: true) - within "#new_team_member" do - select "Reporter", from: "group_access" + select2(user_john.id, from: "#user_ids", multiple: true) + within "#new_project_member" do + select "Reporter", from: "access_level" end - click_button "Add users into group" + click_button "Add users to group" end - Then 'I should see "John Doe" in team list in every project as "Reporter"' do + step 'I should see "John Doe" in team list in every project as "Reporter"' do within ".group-users-list" do page.should have_content "John Doe" page.should have_content "Reporter" @@ -58,9 +57,29 @@ class AdminGroups < Spinach::FeatureSteps end end + step 'we have user "John Doe" in group' do + current_group.add_user(user_john, Gitlab::Access::REPORTER) + end + + step 'I remove user "John Doe" from group' do + within "#user_#{user_john.id}" do + click_link 'Remove user from group' + end + end + + step 'I should not see "John Doe" in team list' do + within ".group-users-list" do + page.should_not have_content "John Doe" + end + end + protected def current_group @group ||= Group.first end + + def user_john + @user_john ||= User.find_by(name: "John Doe") + end end diff --git a/features/steps/admin/logs.rb b/features/steps/admin/logs.rb index 83958545c4..904e546865 100644 --- a/features/steps/admin/logs.rb +++ b/features/steps/admin/logs.rb @@ -1,9 +1,9 @@ -class AdminLogs < Spinach::FeatureSteps +class Spinach::Features::AdminLogs < Spinach::FeatureSteps include SharedAuthentication include SharedPaths include SharedAdmin - Then 'I should see tabs with available logs' do + step 'I should see tabs with available logs' do page.should have_content 'production.log' page.should have_content 'githost.log' page.should have_content 'application.log' diff --git a/features/steps/admin/projects.rb b/features/steps/admin/projects.rb index 992aa46a8b..9be4d39d2d 100644 --- a/features/steps/admin/projects.rb +++ b/features/steps/admin/projects.rb @@ -1,31 +1,31 @@ -class AdminProjects < Spinach::FeatureSteps +class Spinach::Features::AdminProjects < Spinach::FeatureSteps include SharedAuthentication include SharedPaths include SharedAdmin - And 'I should see all projects' do + step 'I should see all projects' do Project.all.each do |p| page.should have_content p.name_with_namespace end end - And 'I click on first project' do + step 'I click on first project' do click_link Project.first.name_with_namespace end - Then 'I should see project details' do + step 'I should see project details' do project = Project.first - current_path.should == admin_project_path(project) + current_path.should == admin_namespace_project_path(project.namespace, project) page.should have_content(project.name_with_namespace) page.should have_content(project.creator.name) end step 'I visit admin project page' do - visit admin_project_path(project) + visit admin_namespace_project_path(project.namespace, project) end step 'I transfer project to group \'Web\'' do - find(:xpath, "//input[@id='namespace_id']").set group.id + find(:xpath, "//input[@id='new_namespace_id']").set group.id click_button 'Transfer' end diff --git a/features/steps/admin/settings.rb b/features/steps/admin/settings.rb new file mode 100644 index 0000000000..87d4e969ff --- /dev/null +++ b/features/steps/admin/settings.rb @@ -0,0 +1,47 @@ +class Spinach::Features::AdminSettings < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedAdmin + include Gitlab::CurrentSettings + + step 'I modify settings and save form' do + uncheck 'Gravatar enabled' + fill_in 'Home page url', with: 'https://about.gitlab.com/' + click_button 'Save' + end + + step 'I should see application settings saved' do + current_application_settings.gravatar_enabled.should be_false + current_application_settings.home_page_url.should == 'https://about.gitlab.com/' + page.should have_content 'Application settings saved successfully' + end + + step 'I click on "Service Templates"' do + click_link 'Service Templates' + end + + step 'I click on "Slack" service' do + click_link 'Slack' + end + + step 'I check all events and submit form' do + page.check('Active') + page.check('Push events') + page.check('Tag push events') + page.check('Comments') + page.check('Issues events') + page.check('Merge Request events') + fill_in 'Webhook', with: "http://localhost" + click_on 'Save' + end + + step 'I should see service template settings saved' do + page.should have_content 'Application settings saved successfully' + end + + step 'I should see all checkboxes checked' do + all('input[type=checkbox]').each do |checkbox| + checkbox.should be_checked + end + end +end diff --git a/features/steps/admin/users.rb b/features/steps/admin/users.rb index 253c4609e8..e138309724 100644 --- a/features/steps/admin/users.rb +++ b/features/steps/admin/users.rb @@ -1,34 +1,34 @@ -class AdminUsers < Spinach::FeatureSteps +class Spinach::Features::AdminUsers < Spinach::FeatureSteps include SharedAuthentication include SharedPaths include SharedAdmin - Then 'I should see all users' do + step 'I should see all users' do User.all.each do |user| page.should have_content user.name end end - And 'Click edit' do + step 'Click edit' do @user = User.first find("#edit_user_#{@user.id}").click end - And 'Input non ascii char in username' do + step 'Input non ascii char in username' do fill_in 'user_username', with: "\u3042\u3044" end - And 'Click save' do + step 'Click save' do click_button("Save") end - Then 'See username error message' do + step 'See username error message' do within "#error_explanation" do page.should have_content "Username" end end - And 'Not changed form action url' do + step 'Not changed form action url' do page.should have_selector %(form[action="/admin/users/#{@user.username}"]) end @@ -63,4 +63,55 @@ class AdminUsers < Spinach::FeatureSteps step 'I should not see secondary email anymore' do page.should_not have_content "Secondary email:" end + + step 'user "Mike" with groups and projects' do + user = create(:user, name: 'Mike') + + project = create(:empty_project) + project.team << [user, :developer] + + group = create(:group) + group.add_user(user, Gitlab::Access::DEVELOPER) + end + + step 'click on "Mike" link' do + click_link "Mike" + end + + step 'I should see user "Mike" details' do + page.should have_content 'Account' + page.should have_content 'Personal projects limit' + end + + step 'user "Pete" with ssh keys' do + user = create(:user, name: 'Pete') + create(:key, user: user, title: "ssh-rsa Key1", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC4FIEBXGi4bPU8kzxMefudPIJ08/gNprdNTaO9BR/ndy3+58s2HCTw2xCHcsuBmq+TsAqgEidVq4skpqoTMB+Uot5Uzp9z4764rc48dZiI661izoREoKnuRQSsRqUTHg5wrLzwxlQbl1MVfRWQpqiz/5KjBC7yLEb9AbusjnWBk8wvC1bQPQ1uLAauEA7d836tgaIsym9BrLsMVnR4P1boWD3Xp1B1T/ImJwAGHvRmP/ycIqmKdSpMdJXwxcb40efWVj0Ibbe7ii9eeoLdHACqevUZi6fwfbymdow+FeqlkPoHyGg3Cu4vD/D8+8cRc7mE/zGCWcQ15Var83Tczour Key1") + create(:key, user: user, title: "ssh-rsa Key2", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2") + end + + step 'click on user "Pete"' do + click_link 'Pete' + end + + step 'I should see key list' do + page.should have_content 'ssh-rsa Key2' + page.should have_content 'ssh-rsa Key1' + end + + step 'I click on the key title' do + click_link 'ssh-rsa Key2' + end + + step 'I should see key details' do + page.should have_content 'ssh-rsa Key2' + page.should have_content 'ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQSTWXhJAX/He+nG78MiRRRn7m0Pb0XbcgTxE0etArgoFoh9WtvDf36HG6tOSg/0UUNcp0dICsNAmhBKdncp6cIyPaXJTURPRAGvhI0/VDk4bi27bRnccGbJ/hDaUxZMLhhrzY0r22mjVf8PF6dvv5QUIQVm1/LeaWYsHHvLgiIjwrXirUZPnFrZw6VLREoBKG8uWvfSXw1L5eapmstqfsME8099oi+vWLR8MgEysZQmD28M73fgW4zek6LDQzKQyJx9nB+hJkKUDvcuziZjGmRFlNgSA2mguERwL1OXonD8WYUrBDGKroIvBT39zS5d9tQDnidEJZ9Y8gv5ViYP7x Key2' + end + + step 'I click on remove key' do + click_link 'Remove' + end + + step 'I should see the key removed' do + page.should_not have_content 'ssh-rsa Key2' + end end diff --git a/features/steps/dashboard/active_tab.rb b/features/steps/dashboard/active_tab.rb index 68d32ed971..0e2c04fb29 100644 --- a/features/steps/dashboard/active_tab.rb +++ b/features/steps/dashboard/active_tab.rb @@ -1,21 +1,9 @@ -class DashboardActiveTab < Spinach::FeatureSteps +class Spinach::Features::DashboardActiveTab < Spinach::FeatureSteps include SharedAuthentication include SharedPaths include SharedActiveTab - Then 'the active main tab should be Home' do - ensure_active_main_tab('Activity') - end - - Then 'the active main tab should be Issues' do - ensure_active_main_tab('Issues') - end - - Then 'the active main tab should be Merge Requests' do - ensure_active_main_tab('Merge Requests') - end - - Then 'the active main tab should be Help' do + step 'the active main tab should be Help' do ensure_active_main_tab('Help') end end diff --git a/features/steps/dashboard/with_archived_projects.rb b/features/steps/dashboard/archived_projects.rb similarity index 61% rename from features/steps/dashboard/with_archived_projects.rb rename to features/steps/dashboard/archived_projects.rb index 1bc69555b5..969baf9228 100644 --- a/features/steps/dashboard/with_archived_projects.rb +++ b/features/steps/dashboard/archived_projects.rb @@ -1,4 +1,4 @@ -class DashboardWithArchivedProjects < Spinach::FeatureSteps +class Spinach::Features::DashboardArchivedProjects < Spinach::FeatureSteps include SharedAuthentication include SharedPaths include SharedProject @@ -8,15 +8,15 @@ class DashboardWithArchivedProjects < Spinach::FeatureSteps project.update_attribute(:archived, true) end - Then 'I should see "Shop" project link' do + step 'I should see "Shop" project link' do page.should have_link "Shop" end - Then 'I should not see "Forum" project link' do + step 'I should not see "Forum" project link' do page.should_not have_link "Forum" end - Then 'I should see "Forum" project link' do + step 'I should see "Forum" project link' do page.should have_link "Forum" end end diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb index 84a480bd7f..8508b2a809 100644 --- a/features/steps/dashboard/dashboard.rb +++ b/features/steps/dashboard/dashboard.rb @@ -1,33 +1,33 @@ -class Dashboard < Spinach::FeatureSteps +class Spinach::Features::Dashboard < Spinach::FeatureSteps include SharedAuthentication include SharedPaths include SharedProject - Then 'I should see "New Project" link' do + step 'I should see "New Project" link' do page.should have_link "New project" end - Then 'I should see "Shop" project link' do + step 'I should see "Shop" project link' do page.should have_link "Shop" end - Then 'I should see last push widget' do + step 'I should see last push widget' do page.should have_content "You pushed to fix" page.should have_link "Create Merge Request" end - And 'I click "Create Merge Request" link' do + step 'I click "Create Merge Request" link' do click_link "Create Merge Request" end - Then 'I see prefilled new Merge Request page' do - current_path.should == new_project_merge_request_path(@project) + step 'I see prefilled new Merge Request page' do + current_path.should == new_namespace_project_merge_request_path(@project.namespace, @project) find("#merge_request_target_project_id").value.should == @project.id.to_s find("#merge_request_source_branch").value.should == "fix" find("#merge_request_target_branch").value.should == "master" end - Given 'user with name "John Doe" joined project "Shop"' do + step 'user with name "John Doe" joined project "Shop"' do user = create(:user, {name: "John Doe"}) project.team << [user, :master] Event.create( @@ -37,11 +37,11 @@ class Dashboard < Spinach::FeatureSteps ) end - Then 'I should see "John Doe joined project at Shop" event' do - page.should have_content "John Doe joined project at #{project.name_with_namespace}" + step 'I should see "John Doe joined project Shop" event' do + page.should have_content "John Doe joined project #{project.name_with_namespace}" end - And 'user with name "John Doe" left project "Shop"' do + step 'user with name "John Doe" left project "Shop"' do user = User.find_by(name: "John Doe") Event.create( project: project, @@ -50,11 +50,11 @@ class Dashboard < Spinach::FeatureSteps ) end - Then 'I should see "John Doe left project at Shop" event' do - page.should have_content "John Doe left project at #{project.name_with_namespace}" + step 'I should see "John Doe left project Shop" event' do + page.should have_content "John Doe left project #{project.name_with_namespace}" end - And 'I have group with projects' do + step 'I have group with projects' do @group = create(:group) @project = create(:project, namespace: @group) @event = create(:closed_issue_event, project: @project) @@ -62,28 +62,24 @@ class Dashboard < Spinach::FeatureSteps @project.team << [current_user, :master] end - Then 'I should see projects list' do + step 'I should see projects list' do @user.authorized_projects.all.each do |project| page.should have_link project.name_with_namespace end end - Then 'I should see groups list' do + step 'I should see groups list' do Group.all.each do |group| page.should have_link group.name end end - And 'group has a projects that does not belongs to me' do + step 'group has a projects that does not belongs to me' do @forbidden_project1 = create(:project, group: @group) @forbidden_project2 = create(:project, group: @group) end - Then 'I should see 1 project at group list' do - page.find('span.last_activity/span').should have_content('1') - end - - def project - @project ||= Project.find_by(name: "Shop") + step 'I should see 1 project at group list' do + find('span.last_activity/span').should have_content('1') end end diff --git a/features/steps/dashboard/event_filters.rb b/features/steps/dashboard/event_filters.rb index d0fe5c9b64..3da3d62d0c 100644 --- a/features/steps/dashboard/event_filters.rb +++ b/features/steps/dashboard/event_filters.rb @@ -1,35 +1,35 @@ -class EventFilters < Spinach::FeatureSteps +class Spinach::Features::EventFilters < Spinach::FeatureSteps include SharedAuthentication include SharedPaths include SharedProject - Then 'I should see push event' do + step 'I should see push event' do page.should have_selector('span.pushed') end - Then 'I should not see push event' do + step 'I should not see push event' do page.should_not have_selector('span.pushed') end - Then 'I should see new member event' do + step 'I should see new member event' do page.should have_selector('span.joined') end - And 'I should not see new member event' do + step 'I should not see new member event' do page.should_not have_selector('span.joined') end - Then 'I should see merge request event' do + step 'I should see merge request event' do page.should have_selector('span.accepted') end - And 'I should not see merge request event' do + step 'I should not see merge request event' do page.should_not have_selector('span.accepted') end - And 'this project has push event' do + step 'this project has push event' do data = { - before: "0000000000000000000000000000000000000000", + before: Gitlab::Git::BLANK_SHA, after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e", ref: "refs/heads/new_design", user_id: @user.id, @@ -51,7 +51,7 @@ class EventFilters < Spinach::FeatureSteps ) end - And 'this project has new member event' do + step 'this project has new member event' do user = create(:user, {name: "John Doe"}) Event.create( project: @project, @@ -60,7 +60,7 @@ class EventFilters < Spinach::FeatureSteps ) end - And 'this project has merge request event' do + step 'this project has merge request event' do merge_request = create :merge_request, author: @user, source_project: @project, target_project: @project Event.create( project: @project, @@ -82,6 +82,4 @@ class EventFilters < Spinach::FeatureSteps When 'I click "merge" event filter' do click_link("merged_event_filter") end - end - diff --git a/features/steps/profile/group.rb b/features/steps/dashboard/group.rb similarity index 57% rename from features/steps/profile/group.rb rename to features/steps/dashboard/group.rb index 03144104c7..8384df2fb5 100644 --- a/features/steps/profile/group.rb +++ b/features/steps/dashboard/group.rb @@ -1,4 +1,4 @@ -class ProfileGroup < Spinach::FeatureSteps +class Spinach::Features::DashboardGroup < Spinach::FeatureSteps include SharedAuthentication include SharedGroup include SharedPaths @@ -7,22 +7,22 @@ class ProfileGroup < Spinach::FeatureSteps # Leave step 'I click on the "Leave" button for group "Owned"' do - find(:css, 'li', text: "Owner").find(:css, 'i.icon-signout').click + find(:css, 'li', text: "Owner").find(:css, 'i.fa.fa-sign-out').click # poltergeist always confirms popups. end step 'I click on the "Leave" button for group "Guest"' do - find(:css, 'li', text: "Guest").find(:css, 'i.icon-signout').click + find(:css, 'li', text: "Guest").find(:css, 'i.fa.fa-sign-out').click # poltergeist always confirms popups. end step 'I should not see the "Leave" button for group "Owned"' do - find(:css, 'li', text: "Owner").should_not have_selector(:css, 'i.icon-signout') + find(:css, 'li', text: "Owner").should_not have_selector(:css, 'i.fa.fa-sign-out') # poltergeist always confirms popups. end step 'I should not see the "Leave" button for groupr "Guest"' do - find(:css, 'li', text: "Guest").should_not have_selector(:css, 'i.icon-signout') + find(:css, 'li', text: "Guest").should_not have_selector(:css, 'i.fa.fa-sign-out') # poltergeist always confirms popups. end @@ -41,4 +41,23 @@ class ProfileGroup < Spinach::FeatureSteps step 'I should not see group "Guest" in group list' do page.should_not have_content("Guest") end + + step 'I click new group link' do + click_link "New Group" + end + + step 'submit form with new group "Samurai" info' do + fill_in 'group_path', with: 'Samurai' + fill_in 'group_description', with: 'Tokugawa Shogunate' + click_button "Create group" + end + + step 'I should be redirected to group "Samurai" page' do + current_path.should == group_path(Group.find_by(name: 'Samurai')) + end + + step 'I should see newly created group "Samurai"' do + page.should have_content "Samurai" + page.should have_content "Tokugawa Shogunate" + end end diff --git a/features/steps/help.rb b/features/steps/dashboard/help.rb similarity index 90% rename from features/steps/help.rb rename to features/steps/dashboard/help.rb index 0d1c9c0037..ef433c57c6 100644 --- a/features/steps/help.rb +++ b/features/steps/dashboard/help.rb @@ -1,4 +1,4 @@ -class Spinach::Features::Help < Spinach::FeatureSteps +class Spinach::Features::DashboardHelp < Spinach::FeatureSteps include SharedAuthentication include SharedPaths include SharedMarkdown diff --git a/features/steps/dashboard/issues.rb b/features/steps/dashboard/issues.rb index 1344edfa80..60da36e86d 100644 --- a/features/steps/dashboard/issues.rb +++ b/features/steps/dashboard/issues.rb @@ -1,6 +1,7 @@ -class DashboardIssues < Spinach::FeatureSteps +class Spinach::Features::DashboardIssues < Spinach::FeatureSteps include SharedAuthentication include SharedPaths + include Select2Helper step 'I should see issues assigned to me' do should_see(assigned_issue) @@ -10,6 +11,7 @@ class DashboardIssues < Spinach::FeatureSteps step 'I should see issues authored by me' do should_see(authored_issue) + should_see(authored_issue_on_public_project) should_not_see(assigned_issue) should_not_see(other_issue) end @@ -22,6 +24,7 @@ class DashboardIssues < Spinach::FeatureSteps step 'I have authored issues' do authored_issue + authored_issue_on_public_project end step 'I have assigned issues' do @@ -33,15 +36,13 @@ class DashboardIssues < Spinach::FeatureSteps end step 'I click "Authored by me" link' do - within ".scope-filter" do - click_link 'Created by me' - end + select2(current_user.id, from: "#author_id") + select2(nil, from: "#assignee_id") end step 'I click "All" link' do - within ".scope-filter" do - click_link "Everyone's" - end + select2(nil, from: "#author_id") + select2(nil, from: "#assignee_id") end def should_see(issue) @@ -64,6 +65,10 @@ class DashboardIssues < Spinach::FeatureSteps @other_issue ||= create :issue, project: project end + def authored_issue_on_public_project + @authored_issue_on_public_project ||= create :issue, author: current_user, project: public_project + end + def project @project ||= begin project =create :project @@ -71,4 +76,8 @@ class DashboardIssues < Spinach::FeatureSteps project end end + + def public_project + @public_project ||= create :project, :public + end end diff --git a/features/steps/dashboard/merge_requests.rb b/features/steps/dashboard/merge_requests.rb index e198bc0cf9..9d92082bb8 100644 --- a/features/steps/dashboard/merge_requests.rb +++ b/features/steps/dashboard/merge_requests.rb @@ -1,16 +1,21 @@ -class DashboardMergeRequests < Spinach::FeatureSteps +class Spinach::Features::DashboardMergeRequests < Spinach::FeatureSteps include SharedAuthentication include SharedPaths + include Select2Helper step 'I should see merge requests assigned to me' do should_see(assigned_merge_request) + should_see(assigned_merge_request_from_fork) should_not_see(authored_merge_request) + should_not_see(authored_merge_request_from_fork) should_not_see(other_merge_request) end step 'I should see merge requests authored by me' do should_see(authored_merge_request) + should_see(authored_merge_request_from_fork) should_not_see(assigned_merge_request) + should_not_see(assigned_merge_request_from_fork) should_not_see(other_merge_request) end @@ -22,10 +27,12 @@ class DashboardMergeRequests < Spinach::FeatureSteps step 'I have authored merge requests' do authored_merge_request + authored_merge_request_from_fork end step 'I have assigned merge requests' do assigned_merge_request + assigned_merge_request_from_fork end step 'I have other merge requests' do @@ -33,15 +40,13 @@ class DashboardMergeRequests < Spinach::FeatureSteps end step 'I click "Authored by me" link' do - within ".scope-filter" do - click_link 'Created by me' - end + select2(current_user.id, from: "#author_id") + select2(nil, from: "#assignee_id") end step 'I click "All" link' do - within ".scope-filter" do - click_link "Everyone's" - end + select2(nil, from: "#author_id") + select2(nil, from: "#assignee_id") end def should_see(merge_request) @@ -53,15 +58,41 @@ class DashboardMergeRequests < Spinach::FeatureSteps end def assigned_merge_request - @assigned_merge_request ||= create :merge_request, assignee: current_user, target_project: project, source_project: project + @assigned_merge_request ||= create :merge_request, + assignee: current_user, + target_project: project, + source_project: project end def authored_merge_request - @authored_merge_request ||= create :merge_request, source_branch: 'simple_merge_request', author: current_user, target_project: project, source_project: project + @authored_merge_request ||= create :merge_request, + source_branch: 'simple_merge_request', + author: current_user, + target_project: project, + source_project: project end def other_merge_request - @other_merge_request ||= create :merge_request, source_branch: '2_3_notes_fix', target_project: project, source_project: project + @other_merge_request ||= create :merge_request, + source_branch: '2_3_notes_fix', + target_project: project, + source_project: project + end + + def authored_merge_request_from_fork + @authored_merge_request_from_fork ||= create :merge_request, + source_branch: 'basic_page', + author: current_user, + target_project: public_project, + source_project: forked_project + end + + def assigned_merge_request_from_fork + @assigned_merge_request_from_fork ||= create :merge_request, + source_branch: 'basic_page_fix', + assignee: current_user, + target_project: public_project, + source_project: forked_project end def project @@ -71,4 +102,12 @@ class DashboardMergeRequests < Spinach::FeatureSteps project end end + + def public_project + @public_project ||= create :project, :public + end + + def forked_project + @forked_project ||= Projects::ForkService.new(public_project, current_user).execute + end end diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb new file mode 100644 index 0000000000..5e588ceb78 --- /dev/null +++ b/features/steps/dashboard/new_project.rb @@ -0,0 +1,27 @@ +class Spinach::Features::NewProject < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedProject + + step 'I click "New project" link' do + click_link "New project" + end + + step 'I see "New project" page' do + page.should have_content("Project path") + end + + step 'I click on "Import project from GitHub"' do + first('.how_to_import_link').click + end + + step 'I see instructions on how to import from GitHub' do + github_modal = first('.modal-body') + github_modal.should be_visible + github_modal.should have_content "To enable importing projects from GitHub" + + all('.modal-body').each do |element| + element.should_not be_visible unless element == github_modal + end + end +end diff --git a/features/steps/dashboard/projects.rb b/features/steps/dashboard/projects.rb deleted file mode 100644 index 8525156544..0000000000 --- a/features/steps/dashboard/projects.rb +++ /dev/null @@ -1,11 +0,0 @@ -class DashboardProjects < Spinach::FeatureSteps - include SharedAuthentication - include SharedPaths - include SharedProject - - Then 'I should see projects list' do - @user.authorized_projects.all.each do |project| - page.should have_link project.name_with_namespace - end - end -end diff --git a/features/steps/dashboard/search.rb b/features/steps/dashboard/search.rb deleted file mode 100644 index 32966a8617..0000000000 --- a/features/steps/dashboard/search.rb +++ /dev/null @@ -1,19 +0,0 @@ -class DashboardSearch < Spinach::FeatureSteps - include SharedAuthentication - include SharedPaths - include SharedProject - - Given 'I search for "Sho"' do - fill_in "dashboard_search", with: "Sho" - click_button "Search" - end - - Then 'I should see "Shop" project link' do - page.should have_link "Shop" - end - - Given 'I search for "Contibuting"' do - fill_in "dashboard_search", with: "Contibuting" - click_button "Search" - end -end diff --git a/features/steps/dashboard/shortcuts.rb b/features/steps/dashboard/shortcuts.rb new file mode 100644 index 0000000000..a9083850b5 --- /dev/null +++ b/features/steps/dashboard/shortcuts.rb @@ -0,0 +1,6 @@ +class Spinach::Features::DashboardShortcuts < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedProject + include SharedActiveTab +end diff --git a/features/steps/dashboard/starred_projects.rb b/features/steps/dashboard/starred_projects.rb new file mode 100644 index 0000000000..b9ad2f13e2 --- /dev/null +++ b/features/steps/dashboard/starred_projects.rb @@ -0,0 +1,15 @@ +class Spinach::Features::DashboardStarredProjects < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedProject + + step 'I starred project "Community"' do + current_user.toggle_star(Project.find_by(name: 'Community')) + end + + step 'I should not see project "Shop"' do + within 'aside' do + page.should_not have_content('Shop') + end + end +end diff --git a/features/steps/explore/groups_feature.rb b/features/steps/explore/groups.rb similarity index 92% rename from features/steps/explore/groups_feature.rb rename to features/steps/explore/groups.rb index b529c5f845..0c2127d4c4 100644 --- a/features/steps/explore/groups_feature.rb +++ b/features/steps/explore/groups.rb @@ -1,4 +1,4 @@ -class Spinach::Features::ExploreGroupsFeature < Spinach::FeatureSteps +class Spinach::Features::ExploreGroups < Spinach::FeatureSteps include SharedAuthentication include SharedPaths include SharedGroup @@ -35,7 +35,7 @@ class Spinach::Features::ExploreGroupsFeature < Spinach::FeatureSteps end step 'I visit group "TestGroup" members page' do - visit members_group_path(Group.find_by(name: "TestGroup")) + visit group_group_members_path(Group.find_by(name: "TestGroup")) end step 'I should not see project "Enterprise" items' do @@ -63,7 +63,7 @@ class Spinach::Features::ExploreGroupsFeature < Spinach::FeatureSteps end step 'I should not see member roles' do - page.body.should_not match(%r{owner|developer|reporter|guest}i) + body.should_not match(%r{owner|developer|reporter|guest}i) end protected diff --git a/features/steps/explore/projects.rb b/features/steps/explore/projects.rb index dfd5106073..26b71406bd 100644 --- a/features/steps/explore/projects.rb +++ b/features/steps/explore/projects.rb @@ -1,4 +1,4 @@ -class Spinach::Features::ExploreProjectsFeature < Spinach::FeatureSteps +class Spinach::Features::ExploreProjects < Spinach::FeatureSteps include SharedAuthentication include SharedPaths include SharedProject @@ -22,26 +22,26 @@ class Spinach::Features::ExploreProjectsFeature < Spinach::FeatureSteps step 'I should see empty public project details with http clone info' do project = Project.find_by(name: 'Empty Public Project') - page.all(:css, '.git-empty .clone').each do |element| + all(:css, '.git-empty .clone').each do |element| element.text.should include(project.http_url_to_repo) end end step 'I should see empty public project details with ssh clone info' do project = Project.find_by(name: 'Empty Public Project') - page.all(:css, '.git-empty .clone').each do |element| + all(:css, '.git-empty .clone').each do |element| element.text.should include(project.url_to_repo) end end step 'I should see project "Community" home page' do - within '.project-home-title' do + within '.navbar-gitlab .title' do page.should have_content 'Community' end end step 'I should see project "Internal" home page' do - within '.project-home-title' do + within '.navbar-gitlab .title' do page.should have_content 'Internal' end end @@ -65,7 +65,7 @@ class Spinach::Features::ExploreProjectsFeature < Spinach::FeatureSteps title: "New feature", project: public_project ) - visit project_issues_path(public_project) + visit namespace_project_issues_path(public_project.namespace, public_project) end @@ -84,7 +84,7 @@ class Spinach::Features::ExploreProjectsFeature < Spinach::FeatureSteps title: "New internal feature", project: internal_project ) - visit project_issues_path(internal_project) + visit namespace_project_issues_path(internal_project.namespace, internal_project) end @@ -95,7 +95,7 @@ class Spinach::Features::ExploreProjectsFeature < Spinach::FeatureSteps end step 'I visit "Community" merge requests page' do - visit project_merge_requests_path(public_project) + visit namespace_project_merge_requests_path(public_project.namespace, public_project) end step 'project "Community" has "Bug fix" open merge request' do @@ -112,7 +112,7 @@ class Spinach::Features::ExploreProjectsFeature < Spinach::FeatureSteps end step 'I visit "Internal" merge requests page' do - visit project_merge_requests_path(internal_project) + visit namespace_project_merge_requests_path(internal_project.namespace, internal_project) end step 'project "Internal" has "Feature implemented" open merge request' do diff --git a/features/steps/group/group.rb b/features/steps/groups.rb similarity index 75% rename from features/steps/group/group.rb rename to features/steps/groups.rb index c3ee42f112..228b83e5fd 100644 --- a/features/steps/group/group.rb +++ b/features/steps/groups.rb @@ -1,70 +1,113 @@ -class Groups < Spinach::FeatureSteps +class Spinach::Features::Groups < Spinach::FeatureSteps include SharedAuthentication include SharedPaths include SharedGroup include SharedUser include Select2Helper - Then 'I should see group "Owned" projects list' do + step 'gitlab user "Mike"' do + create(:user, name: "Mike") + end + + step 'I click link "Add members"' do + find(:css, 'button.btn-new').click + end + + step 'I select "Mike" as "Reporter"' do + user = User.find_by(name: "Mike") + + within ".users-group-form" do + select2(user.id, from: "#user_ids", multiple: true) + select "Reporter", from: "access_level" + end + + click_button "Add users to group" + end + + step 'I should see "Mike" in team list as "Reporter"' do + within '.well-list' do + page.should have_content('Mike') + page.should have_content('Reporter') + end + end + + step 'I select "sjobs@apple.com" as "Reporter"' do + within ".users-group-form" do + select2("sjobs@apple.com", from: "#user_ids", multiple: true) + select "Reporter", from: "access_level" + end + + click_button "Add users to group" + end + + step 'I should see "sjobs@apple.com" in team list as invited "Reporter"' do + within '.well-list' do + page.should have_content('sjobs@apple.com') + page.should have_content('invited') + page.should have_content('Reporter') + end + end + + step 'I should see group "Owned" projects list' do Group.find_by(name: "Owned").projects.each do |project| page.should have_link project.name end end - And 'I should see projects activity feed' do + step 'I should see projects activity feed' do page.should have_content 'closed issue' end - Then 'I should see issues from group "Owned" assigned to me' do + step 'I should see issues from group "Owned" assigned to me' do assigned_to_me(:issues).each do |issue| page.should have_content issue.title end end - Then 'I should see merge requests from group "Owned" assigned to me' do + step 'I should see merge requests from group "Owned" assigned to me' do assigned_to_me(:merge_requests).each do |issue| page.should have_content issue.title[0..80] end end - And 'I select user "Mary Jane" from list with role "Reporter"' do + step 'I select user "Mary Jane" from list with role "Reporter"' do user = User.find_by(name: "Mary Jane") || create(:user, name: "Mary Jane") - click_link 'Add members' + click_button 'Add members' within ".users-group-form" do select2(user.id, from: "#user_ids", multiple: true) - select "Reporter", from: "group_access" + select "Reporter", from: "access_level" end - click_button "Add users into group" + click_button "Add users to group" end - Then 'I should see user "John Doe" in team list' do + step 'I should see user "John Doe" in team list' do projects_with_access = find(".panel .well-list") projects_with_access.should have_content("John Doe") end - Then 'I should not see user "John Doe" in team list' do + step 'I should not see user "John Doe" in team list' do projects_with_access = find(".panel .well-list") projects_with_access.should_not have_content("John Doe") end - Then 'I should see user "Mary Jane" in team list' do + step 'I should see user "Mary Jane" in team list' do projects_with_access = find(".panel .well-list") projects_with_access.should have_content("Mary Jane") end - Then 'I should not see user "Mary Jane" in team list' do + step 'I should not see user "Mary Jane" in team list' do projects_with_access = find(".panel .well-list") projects_with_access.should_not have_content("Mary Jane") end - Given 'project from group "Owned" has issues assigned to me' do + step 'project from group "Owned" has issues assigned to me' do create :issue, project: project, assignee: current_user, author: current_user end - Given 'project from group "Owned" has merge requests assigned to me' do + step 'project from group "Owned" has merge requests assigned to me' do create :merge_request, source_project: project, target_project: project, @@ -72,34 +115,15 @@ class Groups < Spinach::FeatureSteps author: current_user end - When 'I click new group link' do - click_link "New group" - end - - And 'submit form with new group "Samurai" info' do - fill_in 'group_name', with: 'Samurai' - fill_in 'group_description', with: 'Tokugawa Shogunate' - click_button "Create group" - end - - Then 'I should be redirected to group "Samurai" page' do - current_path.should == group_path(Group.last) - end - - Then 'I should see newly created group "Samurai"' do - page.should have_content "Samurai" - page.should have_content "Tokugawa Shogunate" - page.should have_content "Currently you are only seeing events from the" - end - - And 'I change group "Owned" name to "new-name"' do + step 'I change group "Owned" name to "new-name"' do fill_in 'group_name', with: 'new-name' + fill_in 'group_path', with: 'new-name' click_button "Save group" end - Then 'I should see new group "Owned" name' do + step 'I should see new group "Owned" name' do within ".navbar-gitlab" do - page.should have_content "group: new-name" + page.should have_content "new-name" end end @@ -110,7 +134,7 @@ class Groups < Spinach::FeatureSteps end step 'I should see new group "Owned" avatar' do - Group.find_by(name: "Owned").avatar.should be_instance_of AttachmentUploader + Group.find_by(name: "Owned").avatar.should be_instance_of AvatarUploader Group.find_by(name: "Owned").avatar.url.should == "/uploads/group/avatar/#{ Group.find_by(name:"Owned").id }/gitlab_logo.png" end @@ -188,20 +212,19 @@ class Groups < Spinach::FeatureSteps end step 'I should see group milestone with descriptions and expiry date' do - page.should have_content('Lorem Ipsum is simply dummy text of the printing and typesetting industry') page.should have_content('expires at Aug 20, 2114') end step 'I should see group milestone with all issues and MRs assigned to that milestone' do page.should have_content('Milestone GL-113') page.should have_content('Progress: 0 closed – 4 open') - page.should have_link(@issue1.title, href: project_issue_path(@project1, @issue1)) - page.should have_link(@mr3.title, href: project_merge_request_path(@project3, @mr3)) + page.should have_link(@issue1.title, href: namespace_project_issue_path(@project1.namespace, @project1, @issue1)) + page.should have_link(@mr3.title, href: namespace_project_merge_request_path(@project3.namespace, @project3, @mr3)) end protected - def assigned_to_me key + def assigned_to_me(key) project.send(key).where(assignee_id: current_user.id) end diff --git a/features/steps/invites.rb b/features/steps/invites.rb new file mode 100644 index 0000000000..d051cc3edc --- /dev/null +++ b/features/steps/invites.rb @@ -0,0 +1,80 @@ +class Spinach::Features::Invites < Spinach::FeatureSteps + include SharedAuthentication + include SharedUser + include SharedGroup + + step '"John Doe" has invited "user@example.com" to group "Owned"' do + user = User.find_by(name: "John Doe") + group = Group.find_by(name: "Owned") + group.add_user("user@example.com", Gitlab::Access::DEVELOPER, user) + end + + step 'I visit the invitation page' do + group = Group.find_by(name: "Owned") + invite = group.group_members.invite.last + invite.generate_invite_token! + @raw_invite_token = invite.raw_invite_token + visit invite_path(@raw_invite_token) + end + + step 'I should be redirected to the sign in page' do + expect(current_path).to eq(new_user_session_path) + end + + step 'I should see a notice telling me to sign in' do + expect(page).to have_content "To accept this invitation, sign in" + end + + step 'I should be redirected to the invitation page' do + expect(current_path).to eq(invite_path(@raw_invite_token)) + end + + step 'I should see the invitation details' do + expect(page).to have_content("You have been invited by John Doe to join group Owned as Developer.") + end + + step "I should see a message telling me I'm already a member" do + expect(page).to have_content("However, you are already a member of this group.") + end + + step 'I should see an "Accept invitation" button' do + expect(page).to have_link("Accept invitation") + end + + step 'I should see a "Decline" button' do + expect(page).to have_link("Decline") + end + + step 'I click the "Accept invitation" button' do + page.click_link "Accept invitation" + end + + step 'I should be redirected to the group page' do + group = Group.find_by(name: "Owned") + expect(current_path).to eq(group_path(group)) + end + + step 'I should see a notice telling me I have access' do + expect(page).to have_content("You have been granted Developer access to group Owned.") + end + + step 'I click the "Decline" button' do + page.click_link "Decline" + end + + step 'I should be redirected to the dashboard' do + expect(current_path).to eq(dashboard_path) + end + + step 'I should see a notice telling me I have declined' do + expect(page).to have_content("You have declined the invitation to join group Owned.") + end + + step "I visit the invitation's decline page" do + group = Group.find_by(name: "Owned") + invite = group.group_members.invite.last + invite.generate_invite_token! + @raw_invite_token = invite.raw_invite_token + visit decline_invite_path(@raw_invite_token) + end +end diff --git a/features/steps/profile/active_tab.rb b/features/steps/profile/active_tab.rb index 1924a6fa78..8595ee876a 100644 --- a/features/steps/profile/active_tab.rb +++ b/features/steps/profile/active_tab.rb @@ -1,25 +1,25 @@ -class ProfileActiveTab < Spinach::FeatureSteps +class Spinach::Features::ProfileActiveTab < Spinach::FeatureSteps include SharedAuthentication include SharedPaths include SharedActiveTab - Then 'the active main tab should be Home' do + step 'the active main tab should be Home' do ensure_active_main_tab('Profile') end - Then 'the active main tab should be Account' do + step 'the active main tab should be Account' do ensure_active_main_tab('Account') end - Then 'the active main tab should be SSH Keys' do + step 'the active main tab should be SSH Keys' do ensure_active_main_tab('SSH Keys') end - Then 'the active main tab should be Design' do + step 'the active main tab should be Design' do ensure_active_main_tab('Design') end - Then 'the active main tab should be History' do + step 'the active main tab should be History' do ensure_active_main_tab('History') end end diff --git a/features/steps/profile/emails.rb b/features/steps/profile/emails.rb index 99588c8599..2b6ac37d86 100644 --- a/features/steps/profile/emails.rb +++ b/features/steps/profile/emails.rb @@ -1,47 +1,47 @@ -class ProfileEmails < Spinach::FeatureSteps +class Spinach::Features::ProfileEmails < Spinach::FeatureSteps include SharedAuthentication - Then 'I visit profile emails page' do + step 'I visit profile emails page' do visit profile_emails_path end - Then 'I should see my emails' do + step 'I should see my emails' do page.should have_content(@user.email) @user.emails.each do |email| page.should have_content(email.email) end end - And 'I submit new email "my@email.com"' do + step 'I submit new email "my@email.com"' do fill_in "email_email", with: "my@email.com" click_button "Add" end - Then 'I should see new email "my@email.com"' do + step 'I should see new email "my@email.com"' do email = @user.emails.find_by(email: "my@email.com") email.should_not be_nil page.should have_content("my@email.com") end - Then 'I should not see email "my@email.com"' do + step 'I should not see email "my@email.com"' do email = @user.emails.find_by(email: "my@email.com") email.should be_nil page.should_not have_content("my@email.com") end - Then 'I click link "Remove" for "my@email.com"' do + step 'I click link "Remove" for "my@email.com"' do # there should only be one remove button at this time click_link "Remove" # force these to reload as they have been cached @user.emails.reload end - And 'I submit duplicate email @user.email' do + step 'I submit duplicate email @user.email' do fill_in "email_email", with: @user.email click_button "Add" end - Then 'I should not have @user.email added' do + step 'I should not have @user.email added' do email = @user.emails.find_by(email: @user.email) email.should be_nil end diff --git a/features/steps/profile/notifications.rb b/features/steps/profile/notifications.rb index e884df3098..13e93618eb 100644 --- a/features/steps/profile/notifications.rb +++ b/features/steps/profile/notifications.rb @@ -1,4 +1,4 @@ -class ProfileNotifications < Spinach::FeatureSteps +class Spinach::Features::ProfileNotifications < Spinach::FeatureSteps include SharedAuthentication include SharedProject @@ -7,6 +7,6 @@ class ProfileNotifications < Spinach::FeatureSteps end step 'I should see global notifications settings' do - page.should have_content "Notifications settings" + page.should have_content "Notifications Settings" end end diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb index 5a7ac20731..791982d16c 100644 --- a/features/steps/profile/profile.rb +++ b/features/steps/profile/profile.rb @@ -1,9 +1,9 @@ -class Profile < Spinach::FeatureSteps +class Spinach::Features::Profile < Spinach::FeatureSteps include SharedAuthentication include SharedPaths step 'I should see my profile info' do - page.should have_content "Profile settings" + page.should have_content "Profile Settings" end step 'I change my profile info' do @@ -11,6 +11,7 @@ class Profile < Spinach::FeatureSteps fill_in "user_linkedin", with: "testlinkedin" fill_in "user_twitter", with: "testtwitter" fill_in "user_website_url", with: "testurl" + fill_in "user_location", with: "Ukraine" click_button "Save changes" @user.reload end @@ -20,6 +21,7 @@ class Profile < Spinach::FeatureSteps @user.linkedin.should == 'testlinkedin' @user.twitter.should == 'testtwitter' @user.website_url.should == 'testurl' + find("#user_location").value.should == "Ukraine" end step 'I change my avatar' do @@ -29,7 +31,7 @@ class Profile < Spinach::FeatureSteps end step 'I should see new avatar' do - @user.avatar.should be_instance_of AttachmentUploader + @user.avatar.should be_instance_of AvatarUploader @user.avatar.url.should == "/uploads/user/avatar/#{ @user.id }/gitlab_logo.png" end @@ -136,7 +138,7 @@ class Profile < Spinach::FeatureSteps end step "I am not an ldap user" do - current_user.update_attributes(extern_uid: nil, provider: '') + current_user.identities.delete current_user.ldap_user?.should be_false end @@ -187,4 +189,54 @@ class Profile < Spinach::FeatureSteps step 'I should see groups I belong to' do page.should have_css('.profile-groups-avatars', visible: true) end + + step 'I click on new application button' do + click_on 'New Application' + end + + step 'I should see application form' do + page.should have_content "New application" + end + + step 'I fill application form out and submit' do + fill_in :doorkeeper_application_name, with: 'test' + fill_in :doorkeeper_application_redirect_uri, with: 'https://test.com' + click_on "Submit" + end + + step 'I see application' do + page.should have_content "Application: test" + page.should have_content "Application Id" + page.should have_content "Secret" + end + + step 'I click edit' do + click_on "Edit" + end + + step 'I see edit application form' do + page.should have_content "Edit application" + end + + step 'I change name of application and submit' do + page.should have_content "Edit application" + fill_in :doorkeeper_application_name, with: 'test_changed' + click_on "Submit" + end + + step 'I see that application was changed' do + page.should have_content "test_changed" + page.should have_content "Application Id" + page.should have_content "Secret" + end + + step 'I click to remove application' do + within '.oauth-applications' do + click_on "Destroy" + end + end + + step "I see that application is removed" do + page.find(".oauth-applications").should_not have_content "test_changed" + end end diff --git a/features/steps/profile/ssh_keys.rb b/features/steps/profile/ssh_keys.rb index 65ca824bb5..ea912e5b4d 100644 --- a/features/steps/profile/ssh_keys.rb +++ b/features/steps/profile/ssh_keys.rb @@ -1,48 +1,46 @@ -class ProfileSshKeys < Spinach::FeatureSteps +class Spinach::Features::ProfileSshKeys < Spinach::FeatureSteps include SharedAuthentication - Then 'I should see my ssh keys' do + step 'I should see my ssh keys' do @user.keys.each do |key| page.should have_content(key.title) end end - Given 'I click link "Add new"' do + step 'I click link "Add new"' do click_link "Add SSH Key" end - And 'I submit new ssh key "Laptop"' do + step 'I submit new ssh key "Laptop"' do fill_in "key_title", with: "Laptop" fill_in "key_key", with: "ssh-rsa AAAAB3NzaC1yc2EAAAABIwAAAQEAzrEJUIR6Y03TCE9rIJ+GqTBvgb8t1jI9h5UBzCLuK4VawOmkLornPqLDrGbm6tcwM/wBrrLvVOqi2HwmkKEIecVO0a64A4rIYScVsXIniHRS6w5twyn1MD3sIbN+socBDcaldECQa2u1dI3tnNVcs8wi77fiRe7RSxePsJceGoheRQgC8AZ510UdIlO+9rjIHUdVN7LLyz512auAfYsgx1OfablkQ/XJcdEwDNgi9imI6nAXhmoKUm1IPLT2yKajTIC64AjLOnE0YyCh6+7RFMpiMyu1qiOCpdjYwTgBRiciNRZCH8xIedyCoAmiUgkUT40XYHwLuwiPJICpkAzp7Q== user@laptop" click_button "Add key" end - Then 'I should see new ssh key "Laptop"' do + step 'I should see new ssh key "Laptop"' do key = Key.find_by(title: "Laptop") page.should have_content(key.title) page.should have_content(key.key) current_path.should == profile_key_path(key) end - Given 'I click link "Work"' do + step 'I click link "Work"' do click_link "Work" end - And 'I click link "Remove"' do + step 'I click link "Remove"' do click_link "Remove" end - Then 'I visit profile keys page' do + step 'I visit profile keys page' do visit profile_keys_path end - And 'I should not see "Work" ssh key' do - within "#keys-table" do - page.should_not have_content "Work" - end + step 'I should not see "Work" ssh key' do + page.should_not have_content "Work" end - And 'I have ssh key "ssh-rsa Work"' do + step 'I have ssh key "ssh-rsa Work"' do create(:key, user: @user, title: "ssh-rsa Work", key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC+L3TbFegm3k8QjejSwemk4HhlRh+DuN679Pc5ckqE/MPhVtE/+kZQDYCTB284GiT2aIoGzmZ8ee9TkaoejAsBwlA+Wz2Q3vhz65X6sMgalRwpdJx8kSEUYV8ZPV3MZvPo8KdNg993o4jL6G36GDW4BPIyO6FPZhfsawdf6liVD0Xo5kibIK7B9VoE178cdLQtLpS2YolRwf5yy6XR6hbbBGQR+6xrGOdP16eGZDb1CE2bMvvJijjloFqPscGktWOqW+nfh5txwFfBzlfARDTBsS8WZtg3Yoj1kn33kPsWRlgHfNutFRAIynDuDdQzQq8tTtVwm+Yi75RfcPHW8y3P Work") end end diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb index dfafbc6fc0..dd3215adb1 100644 --- a/features/steps/project/active_tab.rb +++ b/features/steps/project/active_tab.rb @@ -1,140 +1,103 @@ -class ProjectActiveTab < Spinach::FeatureSteps +class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps include SharedAuthentication include SharedPaths include SharedProject include SharedActiveTab - - # Main Tabs - - Then 'the active main tab should be Home' do - ensure_active_main_tab('Activity') - end - - Then 'the active main tab should be Settings' do - ensure_active_main_tab('Settings') - end - - Then 'the active main tab should be Files' do - ensure_active_main_tab('Files') - end - - Then 'the active main tab should be Commits' do - ensure_active_main_tab('Commits') - end - - Then 'the active main tab should be Network' do - ensure_active_main_tab('Network') - end - - Then 'the active main tab should be Issues' do - ensure_active_main_tab('Issues') - end - - Then 'the active main tab should be Merge Requests' do - ensure_active_main_tab('Merge Requests') - end - - Then 'the active main tab should be Wall' do - ensure_active_main_tab('Wall') - end - - Then 'the active main tab should be Wiki' do - ensure_active_main_tab('Wiki') - end + include SharedProjectTab # Sub Tabs: Home - Given 'I click the "Team" tab' do + step 'I click the "Team" tab' do click_link('Members') end - Given 'I click the "Attachments" tab' do + step 'I click the "Attachments" tab' do click_link('Attachments') end - Given 'I click the "Snippets" tab' do + step 'I click the "Snippets" tab' do click_link('Snippets') end - Given 'I click the "Edit" tab' do + step 'I click the "Edit" tab' do within '.project-settings-nav' do click_link('Project') end end - Given 'I click the "Hooks" tab' do + step 'I click the "Hooks" tab' do click_link('Web Hooks') end - Given 'I click the "Deploy Keys" tab' do + step 'I click the "Deploy Keys" tab' do click_link('Deploy Keys') end - Then 'the active sub nav should be Team' do + step 'the active sub nav should be Team' do ensure_active_sub_nav('Members') end - Then 'the active sub nav should be Edit' do + step 'the active sub nav should be Edit' do ensure_active_sub_nav('Project') end - Then 'the active sub nav should be Hooks' do + step 'the active sub nav should be Hooks' do ensure_active_sub_nav('Web Hooks') end - Then 'the active sub nav should be Deploy Keys' do + step 'the active sub nav should be Deploy Keys' do ensure_active_sub_nav('Deploy Keys') end # Sub Tabs: Commits - Given 'I click the "Compare" tab' do + step 'I click the "Compare" tab' do click_link('Compare') end - Given 'I click the "Branches" tab' do + step 'I click the "Branches" tab' do click_link('Branches') end - Given 'I click the "Tags" tab' do + step 'I click the "Tags" tab' do click_link('Tags') end - Then 'the active sub tab should be Commits' do + step 'the active sub tab should be Commits' do ensure_active_sub_tab('Commits') end - Then 'the active sub tab should be Compare' do + step 'the active sub tab should be Compare' do ensure_active_sub_tab('Compare') end - Then 'the active sub tab should be Branches' do + step 'the active sub tab should be Branches' do ensure_active_sub_tab('Branches') end - Then 'the active sub tab should be Tags' do + step 'the active sub tab should be Tags' do ensure_active_sub_tab('Tags') end # Sub Tabs: Issues - Given 'I click the "Milestones" tab' do + step 'I click the "Milestones" tab' do click_link('Milestones') end - Given 'I click the "Labels" tab' do + step 'I click the "Labels" tab' do click_link('Labels') end - Then 'the active sub tab should be Browse Issues' do - ensure_active_sub_tab('Browse Issues') + step 'the active sub tab should be Issues' do + ensure_active_sub_tab('Issues') end - Then 'the active sub tab should be Milestones' do - ensure_active_sub_tab('Milestones') + step 'the active main tab should be Milestones' do + ensure_active_main_tab('Milestones') end - Then 'the active sub tab should be Labels' do - ensure_active_sub_tab('Labels') + step 'the active main tab should be Labels' do + ensure_active_main_tab('Labels') end end diff --git a/features/steps/project/archived.rb b/features/steps/project/archived.rb index 8b490d2ffc..37ad0c7765 100644 --- a/features/steps/project/archived.rb +++ b/features/steps/project/archived.rb @@ -1,4 +1,4 @@ -class ProjectArchived < Spinach::FeatureSteps +class Spinach::Features::ProjectArchived < Spinach::FeatureSteps include SharedAuthentication include SharedProject include SharedPaths @@ -15,14 +15,14 @@ class ProjectArchived < Spinach::FeatureSteps When 'I visit project "Forum" page' do project = Project.find_by(name: "Forum") - visit project_path(project) + visit namespace_project_path(project.namespace, project) end - Then 'I should not see "Archived"' do + step 'I should not see "Archived"' do page.should_not have_content "Archived" end - Then 'I should see "Archived"' do + step 'I should see "Archived"' do page.should have_content "Archived" end diff --git a/features/steps/project/browse_branches.rb b/features/steps/project/browse_branches.rb deleted file mode 100644 index 7a0625952d..0000000000 --- a/features/steps/project/browse_branches.rb +++ /dev/null @@ -1,46 +0,0 @@ -class ProjectBrowseBranches < Spinach::FeatureSteps - include SharedAuthentication - include SharedProject - include SharedPaths - - step 'I click link "All"' do - click_link "All" - end - - step 'I should see "Shop" all branches list' do - page.should have_content "Branches" - page.should have_content "master" - end - - step 'I click link "Protected"' do - click_link "Protected" - end - - step 'I should see "Shop" protected branches list' do - within ".protected-branches-list" do - page.should have_content "stable" - page.should_not have_content "master" - end - end - - step 'project "Shop" has protected branches' do - project = Project.find_by(name: "Shop") - project.protected_branches.create(name: "stable") - end - - step 'I click new branch link' do - click_link "New branch" - end - - step 'I submit new branch form' do - fill_in 'branch_name', with: 'deploy_keys' - fill_in 'ref', with: 'master' - click_button 'Create branch' - end - - step 'I should see new branch created' do - within '.tree-ref-holder' do - page.should have_content 'deploy_keys' - end - end -end diff --git a/features/steps/project/browse_commits.rb b/features/steps/project/browse_commits.rb deleted file mode 100644 index 37207aafeb..0000000000 --- a/features/steps/project/browse_commits.rb +++ /dev/null @@ -1,91 +0,0 @@ -class ProjectBrowseCommits < Spinach::FeatureSteps - include SharedAuthentication - include SharedProject - include SharedPaths - include RepoHelpers - - Then 'I see project commits' do - commit = @project.repository.commit - page.should have_content(@project.name) - page.should have_content(commit.message[0..20]) - page.should have_content(commit.id.to_s[0..5]) - end - - Given 'I click atom feed link' do - click_link "Feed" - end - - Then 'I see commits atom feed' do - commit = @project.repository.commit - page.response_headers['Content-Type'].should have_content("application/atom+xml") - page.body.should have_selector("title", text: "Recent commits to #{@project.name}") - page.body.should have_selector("author email", text: commit.author_email) - page.body.should have_selector("entry summary", text: commit.description[0..10]) - end - - Given 'I click on commit link' do - visit project_commit_path(@project, sample_commit.id) - end - - Then 'I see commit info' do - page.should have_content sample_commit.message - page.should have_content "Showing #{sample_commit.files_changed_count} changed files" - end - - And 'I fill compare fields with refs' do - fill_in "from", with: sample_commit.parent_id - fill_in "to", with: sample_commit.id - click_button "Compare" - end - - Then 'I see compared refs' do - page.should have_content "Compare View" - page.should have_content "Commits (1)" - page.should have_content "Showing 2 changed files" - end - - Then 'I see breadcrumb links' do - page.should have_selector('ul.breadcrumb') - page.should have_selector('ul.breadcrumb a', count: 4) - end - - Then 'I see commits stats' do - page.should have_content 'Top 50 Committers' - page.should have_content 'Committers' - page.should have_content 'Total commits' - page.should have_content 'Authors' - end - - Given 'I visit big commit page' do - Commit::DIFF_SAFE_FILES = 20 - visit project_commit_path(@project, sample_big_commit.id) - end - - Then 'I see big commit warning' do - page.should have_content sample_big_commit.message - page.should have_content "Too many changes" - Commit::DIFF_SAFE_FILES = 100 - end - - Given 'I visit a commit with an image that changed' do - visit project_commit_path(@project, sample_image_commit.id) - end - - Then 'The diff links to both the previous and current image' do - links = page.all('.two-up span div a') - links[0]['href'].should =~ %r{blob/#{sample_image_commit.old_blob_id}} - links[1]['href'].should =~ %r{blob/#{sample_image_commit.new_blob_id}} - end - - Given 'I click side-by-side diff button' do - click_link "Side-by-side Diff" - end - - Then 'I see side-by-side diff button' do - page.should have_content "Side-by-side Diff" - end - - Then 'I see inline diff button' do - page.should have_content "Inline Diff" - end -end diff --git a/features/steps/project/browse_files.rb b/features/steps/project/browse_files.rb deleted file mode 100644 index 6fd0c2c2de..0000000000 --- a/features/steps/project/browse_files.rb +++ /dev/null @@ -1,93 +0,0 @@ -class ProjectBrowseFiles < Spinach::FeatureSteps - include SharedAuthentication - include SharedProject - include SharedPaths - include RepoHelpers - - step 'I should see files from repository' do - page.should have_content "VERSION" - page.should have_content ".gitignore" - page.should have_content "LICENSE" - end - - step 'I should see files from repository for "6d39438"' do - current_path.should == project_tree_path(@project, "6d39438") - page.should have_content ".gitignore" - page.should have_content "LICENSE" - end - - step 'I click on ".gitignore" file in repo' do - click_link ".gitignore" - end - - step 'I should see it content' do - page.should have_content "*.rbc" - end - - step 'I click link "raw"' do - click_link "raw" - end - - step 'I should see raw file content' do - page.source.should == sample_blob.data - end - - step 'I click button "edit"' do - click_link 'edit' - end - - step 'I can edit code' do - page.execute_script('editor.setValue("GitlabFileEditor")') - page.evaluate_script('editor.getValue()').should == "GitlabFileEditor" - end - - step 'I edit code' do - page.execute_script('editor.setValue("GitlabFileEditor")') - end - - step 'I click link "Diff"' do - click_link 'Diff' - end - - step 'I see diff' do - page.should have_css '.line_holder.new' - end - - step 'I click on "new file" link in repo' do - click_link 'new-file-link' - end - - step 'I can see new file page' do - page.should have_content "New file" - page.should have_content "File name" - page.should have_content "Commit message" - end - - step 'I click on files directory' do - click_link 'files' - end - - step 'I click on history link' do - click_link 'history' - end - - step 'I see Browse dir link' do - page.should have_link 'Browse Dir »' - page.should_not have_link 'Browse Code »' - end - - step 'I click on readme file' do - click_link 'README.md' - end - - step 'I see Browse file link' do - page.should have_link 'Browse File »' - page.should_not have_link 'Browse Code »' - end - - step 'I see Browse code link' do - page.should have_link 'Browse Code »' - page.should_not have_link 'Browse File »' - page.should_not have_link 'Browse Dir »' - end -end diff --git a/features/steps/project/browse_tags.rb b/features/steps/project/browse_tags.rb deleted file mode 100644 index 7c679911e0..0000000000 --- a/features/steps/project/browse_tags.rb +++ /dev/null @@ -1,10 +0,0 @@ -class ProjectBrowseTags < Spinach::FeatureSteps - include SharedAuthentication - include SharedProject - include SharedPaths - - Then 'I should see "Shop" all tags list' do - page.should have_content "Tags" - page.should have_content "v1.0.0" - end -end diff --git a/features/steps/project/commits/branches.rb b/features/steps/project/commits/branches.rb new file mode 100644 index 0000000000..07f7e5796a --- /dev/null +++ b/features/steps/project/commits/branches.rb @@ -0,0 +1,85 @@ +class Spinach::Features::ProjectCommitsBranches < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + step 'I click link "All"' do + click_link "All" + end + + step 'I should see "Shop" all branches list' do + page.should have_content "Branches" + page.should have_content "master" + end + + step 'I click link "Protected"' do + click_link "Protected" + end + + step 'I should see "Shop" protected branches list' do + within ".protected-branches-list" do + page.should have_content "stable" + page.should_not have_content "master" + end + end + + step 'project "Shop" has protected branches' do + project = Project.find_by(name: "Shop") + project.protected_branches.create(name: "stable") + end + + step 'I click new branch link' do + click_link "New branch" + end + + step 'I submit new branch form' do + fill_in 'branch_name', with: 'deploy_keys' + fill_in 'ref', with: 'master' + click_button 'Create branch' + end + + step 'I submit new branch form with invalid name' do + fill_in 'branch_name', with: '1.0 stable' + fill_in 'ref', with: 'master' + click_button 'Create branch' + end + + step 'I submit new branch form with invalid reference' do + fill_in 'branch_name', with: 'foo' + fill_in 'ref', with: 'foo' + click_button 'Create branch' + end + + step 'I submit new branch form with branch that already exists' do + fill_in 'branch_name', with: 'master' + fill_in 'ref', with: 'master' + click_button 'Create branch' + end + + step 'I should see new branch created' do + page.should have_content 'deploy_keys' + end + + step 'I should see new an error that branch is invalid' do + page.should have_content 'Branch name invalid' + end + + step 'I should see new an error that ref is invalid' do + page.should have_content 'Invalid reference name' + end + + step 'I should see new an error that branch already exists' do + page.should have_content 'Branch already exists' + end + + step "I click branch 'improve/awesome' delete link" do + within '.js-branch-improve\/awesome' do + find('.btn-remove').click + sleep 0.05 + end + end + + step "I should not see branch 'improve/awesome'" do + all(visible: true).should_not have_content 'improve/awesome' + end +end diff --git a/features/steps/project/comments_on_commits.rb b/features/steps/project/commits/comments.rb similarity index 58% rename from features/steps/project/comments_on_commits.rb rename to features/steps/project/commits/comments.rb index 56bb12a820..3d4d8ad636 100644 --- a/features/steps/project/comments_on_commits.rb +++ b/features/steps/project/commits/comments.rb @@ -1,4 +1,4 @@ -class CommentsOnCommits < Spinach::FeatureSteps +class Spinach::Features::ProjectCommitsComments < Spinach::FeatureSteps include SharedAuthentication include SharedNote include SharedPaths diff --git a/features/steps/project/commits/commits.rb b/features/steps/project/commits/commits.rb new file mode 100644 index 0000000000..57b727f837 --- /dev/null +++ b/features/steps/project/commits/commits.rb @@ -0,0 +1,103 @@ +class Spinach::Features::ProjectCommits < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + include RepoHelpers + + step 'I see project commits' do + commit = @project.repository.commit + page.should have_content(@project.name) + page.should have_content(commit.message[0..20]) + page.should have_content(commit.short_id) + end + + step 'I click atom feed link' do + click_link "Feed" + end + + step 'I see commits atom feed' do + commit = @project.repository.commit + response_headers['Content-Type'].should have_content("application/atom+xml") + body.should have_selector("title", text: "Recent commits to #{@project.name}") + body.should have_selector("author email", text: commit.author_email) + body.should have_selector("entry summary", text: commit.description[0..10]) + end + + step 'I click on commit link' do + visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id) + end + + step 'I see commit info' do + page.should have_content sample_commit.message + page.should have_content "Showing #{sample_commit.files_changed_count} changed files" + end + + step 'I fill compare fields with refs' do + fill_in "from", with: sample_commit.parent_id + fill_in "to", with: sample_commit.id + click_button "Compare" + end + + step 'I unfold diff' do + @diff = first('.js-unfold') + @diff.click + sleep 2 + end + + step 'I should see additional file lines' do + within @diff.parent do + first('.new_line').text.should_not have_content "..." + end + end + + step 'I see compared refs' do + page.should have_content "Compare View" + page.should have_content "Commits (1)" + page.should have_content "Showing 2 changed files" + end + + step 'I see breadcrumb links' do + page.should have_selector('ul.breadcrumb') + page.should have_selector('ul.breadcrumb a', count: 4) + end + + step 'I see commits stats' do + page.should have_content 'Top 50 Committers' + page.should have_content 'Committers' + page.should have_content 'Total commits' + page.should have_content 'Authors' + end + + step 'I visit big commit page' do + Commit::DIFF_SAFE_FILES = 20 + visit namespace_project_commit_path(@project.namespace, @project, sample_big_commit.id) + end + + step 'I see big commit warning' do + page.should have_content sample_big_commit.message + page.should have_content "Too many changes" + Commit::DIFF_SAFE_FILES = 100 + end + + step 'I visit a commit with an image that changed' do + visit namespace_project_commit_path(@project.namespace, @project, sample_image_commit.id) + end + + step 'The diff links to both the previous and current image' do + links = all('.two-up span div a') + links[0]['href'].should =~ %r{blob/#{sample_image_commit.old_blob_id}} + links[1]['href'].should =~ %r{blob/#{sample_image_commit.new_blob_id}} + end + + step 'I click side-by-side diff button' do + click_link "Side-by-side" + end + + step 'I see side-by-side diff button' do + page.should have_content "Side-by-side" + end + + step 'I see inline diff button' do + page.should have_content "Inline" + end +end diff --git a/features/steps/project/comments_on_commit_diffs.rb b/features/steps/project/commits/diff_comments.rb similarity index 58% rename from features/steps/project/comments_on_commit_diffs.rb rename to features/steps/project/commits/diff_comments.rb index fc397a4fa9..b9d8cf2c5a 100644 --- a/features/steps/project/comments_on_commit_diffs.rb +++ b/features/steps/project/commits/diff_comments.rb @@ -1,4 +1,4 @@ -class CommentsOnCommitDiffs < Spinach::FeatureSteps +class Spinach::Features::ProjectCommitsDiffComments < Spinach::FeatureSteps include SharedAuthentication include SharedDiffNote include SharedPaths diff --git a/features/steps/project/commits/tags.rb b/features/steps/project/commits/tags.rb new file mode 100644 index 0000000000..3465fcbfd0 --- /dev/null +++ b/features/steps/project/commits/tags.rb @@ -0,0 +1,82 @@ +class Spinach::Features::ProjectCommitsTags < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + + step 'I should see "Shop" all tags list' do + page.should have_content "Tags" + page.should 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 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 + page.should have_content 'v7.0' + end + + step 'I should see new an error that tag is invalid' do + page.should have_content 'Tag name invalid' + end + + step 'I should see new an error that tag ref is invalid' do + page.should have_content 'Invalid reference name' + end + + step 'I should see new an error that tag already exists' do + page.should have_content 'Tag already exists' + end + + step "I delete tag 'v1.1.0'" do + within '.tags' do + first('.btn-remove').click + sleep 0.05 + end + end + + step "I should not see tag 'v1.1.0'" do + within '.tags' do + all(visible: true).should_not have_content 'v1.1.0' + end + end + + step 'I delete all tags' do + within '.tags' do + all('.btn-remove').each do |remove| + remove.click + sleep 0.05 + end + end + end + + step 'I should see tags info message' do + within '.tags' do + page.should have_content 'Repository has no tags yet.' + end + end +end diff --git a/features/steps/project/browse_commits_user_lookup.rb b/features/steps/project/commits/user_lookup.rb similarity index 77% rename from features/steps/project/browse_commits_user_lookup.rb rename to features/steps/project/commits/user_lookup.rb index 198ea29f28..63ff84c82e 100644 --- a/features/steps/project/browse_commits_user_lookup.rb +++ b/features/steps/project/commits/user_lookup.rb @@ -1,14 +1,14 @@ -class ProjectBrowseCommitsUserLookup < Spinach::FeatureSteps +class Spinach::Features::ProjectCommitsUserLookup < Spinach::FeatureSteps include SharedAuthentication include SharedProject include SharedPaths - Given 'I click on commit link' do - visit project_commit_path(@project, sample_commit.id) + step 'I click on commit link' do + visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id) end - Given 'I click on another commit link' do - visit project_commit_path(@project, sample_commit.parent_id) + step 'I click on another commit link' do + visit namespace_project_commit_path(@project.namespace, @project, sample_commit.parent_id) end step 'I have user with primary email' do diff --git a/features/steps/project/create.rb b/features/steps/project/create.rb index b42e5bd362..6b85cf74f5 100644 --- a/features/steps/project/create.rb +++ b/features/steps/project/create.rb @@ -1,42 +1,42 @@ -class CreateProject < Spinach::FeatureSteps +class Spinach::Features::ProjectCreate < Spinach::FeatureSteps include SharedAuthentication include SharedPaths - And 'fill project form with valid data' do - fill_in 'project_name', with: 'Empty' + step 'fill project form with valid data' do + fill_in 'project_path', with: 'Empty' click_button "Create project" end - Then 'I should see project page' do + step 'I should see project page' do page.should have_content "Empty" - current_path.should == project_path(Project.last) + current_path.should == namespace_project_path(Project.last.namespace, Project.last) end - And 'I should see empty project instuctions' do + step 'I should see empty project instuctions' do page.should have_content "git init" page.should have_content "git remote" page.should have_content Project.last.url_to_repo end - Then 'I see empty project instuctions' do + step 'I see empty project instuctions' do page.should have_content "git init" page.should have_content "git remote" page.should have_content Project.last.url_to_repo end - And 'I click on HTTP' do + step 'I click on HTTP' do click_button 'HTTP' end - Then 'Remote url should update to http link' do + step 'Remote url should update to http link' do page.should have_content "git remote add origin #{Project.last.http_url_to_repo}" end - And 'If I click on SSH' do + step 'If I click on SSH' do click_button 'SSH' end - Then 'Remote url should update to ssh link' do + step 'Remote url should update to ssh link' do page.should have_content "git remote add origin #{Project.last.url_to_repo}" end end diff --git a/features/steps/project/deploy_keys.rb b/features/steps/project/deploy_keys.rb index 914da31322..50e14513a7 100644 --- a/features/steps/project/deploy_keys.rb +++ b/features/steps/project/deploy_keys.rb @@ -7,12 +7,24 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps create(:deploy_keys_project, project: @project) end - step 'I should see project deploy keys' do + step 'I should see project deploy key' do within '.enabled-keys' do page.should have_content deploy_key.title end end + step 'I should see other project deploy key' do + within '.available-keys' do + page.should have_content other_deploy_key.title + end + end + + step 'I should see public deploy key' do + within '.available-keys' do + page.should have_content public_deploy_key.title + end + end + step 'I click \'New Deploy Key\'' do click_link 'New Deploy Key' end @@ -24,7 +36,7 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps end step 'I should be on deploy keys page' do - current_path.should == project_deploy_keys_path(@project) + current_path.should == namespace_project_deploy_keys_path(@project.namespace, @project) end step 'I should see newly created deploy key' do @@ -39,6 +51,10 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps create(:deploy_keys_project, project: @second_project) end + step 'public deploy key exists' do + create(:deploy_key, public: true) + end + step 'I click attach deploy key' do within '.available-keys' do click_link 'Enable' @@ -50,4 +66,12 @@ class Spinach::Features::ProjectDeployKeys < Spinach::FeatureSteps def deploy_key @project.deploy_keys.last end + + def other_deploy_key + @second_project.deploy_keys.last + end + + def public_deploy_key + DeployKey.are_public.last + end end diff --git a/features/steps/project/fork.rb b/features/steps/project/fork.rb index 93ceaa0ebb..8e58597db2 100644 --- a/features/steps/project/fork.rb +++ b/features/steps/project/fork.rb @@ -1,4 +1,4 @@ -class ForkProject < Spinach::FeatureSteps +class Spinach::Features::ProjectFork < Spinach::FeatureSteps include SharedAuthentication include SharedPaths include SharedProject @@ -25,4 +25,10 @@ class ForkProject < Spinach::FeatureSteps step 'I should see a "Name has already been taken" warning' do page.should have_content "Name has already been taken" end + + step 'I fork to my namespace' do + within '.fork-namespaces' do + click_link current_user.name + end + end end diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb index 6ec527df13..63ad90e124 100644 --- a/features/steps/project/forked_merge_requests.rb +++ b/features/steps/project/forked_merge_requests.rb @@ -1,4 +1,4 @@ -class ProjectForkedMergeRequests < Spinach::FeatureSteps +class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps include SharedAuthentication include SharedProject include SharedNote @@ -23,7 +23,7 @@ class ProjectForkedMergeRequests < Spinach::FeatureSteps step 'I should see merge request "Merge Request On Forked Project"' do @project.merge_requests.size.should >= 1 @merge_request = @project.merge_requests.last - current_path.should == project_merge_request_path(@project, @merge_request) + current_path.should == namespace_project_merge_request_path(@project.namespace, @project, @merge_request) @merge_request.title.should == "Merge Request On Forked Project" @merge_request.source_project.should == @forked_project @merge_request.source_branch.should == "fix" @@ -64,14 +64,14 @@ class ProjectForkedMergeRequests < Spinach::FeatureSteps end step 'I see prefilled new Merge Request page for the forked project' do - current_path.should == new_project_merge_request_path(@forked_project) + current_path.should == new_namespace_project_merge_request_path(@forked_project.namespace, @forked_project) find("#merge_request_source_project_id").value.should == @forked_project.id.to_s find("#merge_request_target_project_id").value.should == @project.id.to_s find("#merge_request_source_branch").value.should have_content "new_design" find("#merge_request_target_branch").value.should have_content "master" find("#merge_request_title").value.should == "New Design" - verify_commit_link(".mr_target_commit",@project) - verify_commit_link(".mr_source_commit",@forked_project) + verify_commit_link(".mr_target_commit", @project) + verify_commit_link(".mr_source_commit", @forked_project) end step 'I update the merge request title' do @@ -86,7 +86,7 @@ class ProjectForkedMergeRequests < Spinach::FeatureSteps page.should have_content "An Edited Forked Merge Request" @project.merge_requests.size.should >= 1 @merge_request = @project.merge_requests.last - current_path.should == project_merge_request_path(@project, @merge_request) + current_path.should == namespace_project_merge_request_path(@project.namespace, @project, @merge_request) @merge_request.source_project.should == @forked_project @merge_request.source_branch.should == "fix" @merge_request.target_branch.should == "master" @@ -106,7 +106,7 @@ class ProjectForkedMergeRequests < Spinach::FeatureSteps end step 'I see the edit page prefilled for "Merge Request On Forked Project"' do - current_path.should == edit_project_merge_request_path(@project, @merge_request) + current_path.should == edit_namespace_project_merge_request_path(@project.namespace, @project, @merge_request) page.should have_content "Edit merge request ##{@merge_request.id}" find("#merge_request_title").value.should == "Merge Request On Forked Project" end @@ -114,7 +114,7 @@ class ProjectForkedMergeRequests < Spinach::FeatureSteps step 'I fill out an invalid "Merge Request On Forked Project" merge request' do select "Select branch", from: "merge_request_target_branch" find(:select, "merge_request_source_project_id", {}).value.should == @forked_project.id.to_s - find(:select, "merge_request_target_project_id", {}).value.should == project.id.to_s + find(:select, "merge_request_target_project_id", {}).value.should == @project.id.to_s find(:select, "merge_request_source_branch", {}).value.should == "" find(:select, "merge_request_target_branch", {}).value.should == "" click_button "Compare branches" @@ -125,11 +125,7 @@ class ProjectForkedMergeRequests < Spinach::FeatureSteps end step 'the target repository should be the original repository' do - page.should have_select("merge_request_target_project_id", selected: project.path_with_namespace) - end - - def project - @project ||= Project.find_by!(name: "Shop") + page.should have_select("merge_request_target_project_id", selected: @project.path_with_namespace) end # Verify a link is generated against the correct project diff --git a/features/steps/project/graph.rb b/features/steps/project/graph.rb index 89fe5fdead..a2807c340f 100644 --- a/features/steps/project/graph.rb +++ b/features/steps/project/graph.rb @@ -1,13 +1,23 @@ -class ProjectGraph < Spinach::FeatureSteps +class Spinach::Features::ProjectGraph < Spinach::FeatureSteps include SharedAuthentication include SharedProject - Then 'page should have graphs' do + step 'page should have graphs' do page.should have_selector ".stat-graph" end When 'I visit project "Shop" graph page' do project = Project.find_by(name: "Shop") - visit project_graph_path(project, "master") + visit namespace_project_graph_path(project.namespace, project, "master") + end + + step 'I visit project "Shop" commits graph page' do + project = Project.find_by(name: "Shop") + visit commits_namespace_project_graph_path(project.namespace, project, "master") + end + + step 'page should have commits graphs' do + page.should have_content "Commit statistics for master" + page.should have_content "Commits per day of month" end end diff --git a/features/steps/project/hooks.rb b/features/steps/project/hooks.rb index 2bd383676e..4b13520259 100644 --- a/features/steps/project/hooks.rb +++ b/features/steps/project/hooks.rb @@ -1,6 +1,6 @@ require 'webmock' -class ProjectHooks < Spinach::FeatureSteps +class Spinach::Features::ProjectHooks < Spinach::FeatureSteps include SharedAuthentication include SharedProject include SharedPaths @@ -29,7 +29,7 @@ class ProjectHooks < Spinach::FeatureSteps end step 'I should see newly created hook' do - page.current_path.should == project_hooks_path(current_project) + current_path.should == namespace_project_hooks_path(current_project.namespace, current_project) page.should have_content(@url) end @@ -44,7 +44,7 @@ class ProjectHooks < Spinach::FeatureSteps end step 'hook should be triggered' do - page.current_path.should == project_hooks_path(current_project) + current_path.should == namespace_project_hooks_path(current_project.namespace, current_project) page.should have_selector '.flash-notice', text: 'Hook successfully executed.' end diff --git a/features/steps/project/issue_tracker.rb b/features/steps/project/issue_tracker.rb deleted file mode 100644 index c2fd4e15c9..0000000000 --- a/features/steps/project/issue_tracker.rb +++ /dev/null @@ -1,31 +0,0 @@ -class ProjectIssueTracker < Spinach::FeatureSteps - include SharedAuthentication - include SharedProject - include SharedPaths - - step 'project "Shop" has issues enabled' do - @project = Project.find_by(name: "Shop") - @project ||= create(:project, name: "Shop", namespace: @user.namespace) - @project.issues_enabled = true - end - - step 'change the issue tracker to "GitLab"' do - select 'GitLab', from: 'project_issues_tracker' - end - - step 'I the project should have "GitLab" as issue tracker' do - find_field('project_issues_tracker').value.should == 'gitlab' - end - - step 'change the issue tracker to "Redmine"' do - select 'Redmine', from: 'project_issues_tracker' - end - - step 'I the project should have "Redmine" as issue tracker' do - find_field('project_issues_tracker').value.should == 'redmine' - end - - And 'I save project' do - click_button 'Save changes' - end -end diff --git a/features/steps/project/issues.rb b/features/steps/project/issues.rb deleted file mode 100644 index 557ea2fdca..0000000000 --- a/features/steps/project/issues.rb +++ /dev/null @@ -1,190 +0,0 @@ -class ProjectIssues < Spinach::FeatureSteps - include SharedAuthentication - include SharedProject - include SharedNote - include SharedPaths - include SharedMarkdown - - Given 'I should see "Release 0.4" in issues' do - page.should have_content "Release 0.4" - end - - And 'I should not see "Release 0.3" in issues' do - page.should_not have_content "Release 0.3" - end - - And 'I should not see "Tweet control" in issues' do - page.should_not have_content "Tweet control" - end - - Given 'I click link "Closed"' do - click_link "Closed" - end - - Then 'I should see "Release 0.3" in issues' do - page.should have_content "Release 0.3" - end - - And 'I should not see "Release 0.4" in issues' do - page.should_not have_content "Release 0.4" - end - - Given 'I click link "All"' do - click_link "All" - end - - Given 'I click link "Release 0.4"' do - click_link "Release 0.4" - end - - Then 'I should see issue "Release 0.4"' do - page.should have_content "Release 0.4" - end - - Given 'I click link "New Issue"' do - click_link "New Issue" - end - - And 'I submit new issue "500 error on profile"' do - fill_in "issue_title", with: "500 error on profile" - click_button "Submit new issue" - end - - step 'I submit new issue "500 error on profile" with label \'bug\'' do - fill_in "issue_title", with: "500 error on profile" - select 'bug', from: "Labels" - click_button "Submit new issue" - end - - Given 'I click link "500 error on profile"' do - click_link "500 error on profile" - end - - step 'I should see label \'bug\' with issue' do - within '.issue-show-labels' do - page.should have_content 'bug' - end - end - - Then 'I should see issue "500 error on profile"' do - issue = Issue.find_by(title: "500 error on profile") - page.should have_content issue.title - page.should have_content issue.author_name - page.should have_content issue.project.name - end - - Given 'I fill in issue search with "Re"' do - fill_in 'issue_search', with: "Re" - end - - Given 'I fill in issue search with "Bu"' do - fill_in 'issue_search', with: "Bu" - end - - And 'I fill in issue search with ".3"' do - fill_in 'issue_search', with: ".3" - end - - And 'I fill in issue search with "Something"' do - fill_in 'issue_search', with: "Something" - end - - And 'I fill in issue search with ""' do - fill_in 'issue_search', with: "" - end - - Given 'project "Shop" has milestone "v2.2"' do - project = Project.find_by(name: "Shop") - milestone = create(:milestone, title: "v2.2", project: project) - - 3.times { create(:issue, project: project, milestone: milestone) } - end - - And 'project "Shop" has milestone "v3.0"' do - project = Project.find_by(name: "Shop") - milestone = create(:milestone, title: "v3.0", project: project) - - 3.times { create(:issue, project: project, milestone: milestone) } - end - - When 'I select milestone "v3.0"' do - select "v3.0", from: "milestone_id" - end - - Then 'I should see selected milestone with title "v3.0"' do - issues_milestone_selector = "#issue_milestone_id_chzn > a" - page.find(issues_milestone_selector).should have_content("v3.0") - end - - When 'I select first assignee from "Shop" project' do - project = Project.find_by(name: "Shop") - first_assignee = project.users.first - select first_assignee.name, from: "assignee_id" - end - - Then 'I should see first assignee from "Shop" as selected assignee' do - issues_assignee_selector = "#issue_assignee_id_chzn > a" - project = Project.find_by(name: "Shop") - assignee_name = project.users.first.name - page.find(issues_assignee_selector).should have_content(assignee_name) - end - - And 'project "Shop" have "Release 0.4" open issue' do - project = Project.find_by(name: "Shop") - create(:issue, - title: "Release 0.4", - project: project, - author: project.users.first, - description: "# Description header" - ) - end - - And 'project "Shop" have "Tweet control" open issue' do - project = Project.find_by(name: "Shop") - create(:issue, - title: "Tweet control", - project: project, - author: project.users.first) - end - - And 'project "Shop" have "Release 0.3" closed issue' do - project = Project.find_by(name: "Shop") - create(:closed_issue, - title: "Release 0.3", - project: project, - author: project.users.first) - end - - Given 'empty project "Empty Project"' do - create :empty_project, name: 'Empty Project', namespace: @user.namespace - end - - When 'I visit empty project page' do - project = Project.find_by(name: 'Empty Project') - visit project_path(project) - end - - And 'I see empty project details with ssh clone info' do - project = Project.find_by(name: 'Empty Project') - page.all(:css, '.git-empty .clone').each do |element| - element.text.should include(project.url_to_repo) - end - end - - When "I visit empty project's issues page" do - project = Project.find_by(name: 'Empty Project') - visit project_issues_path(project) - end - - step 'I leave a comment with code block' do - within(".js-main-target-form") do - fill_in "note[note]", with: "```\nCommand [1]: /usr/local/bin/git , see [text](doc/text)\n```" - click_button "Add Comment" - sleep 0.05 - end - end - - step 'The code block should be unchanged' do - page.should have_content("```\nCommand [1]: /usr/local/bin/git , see [text](doc/text)\n```") - end -end diff --git a/features/steps/project/filter_labels.rb b/features/steps/project/issues/filter_labels.rb similarity index 75% rename from features/steps/project/filter_labels.rb rename to features/steps/project/issues/filter_labels.rb index 9b31a6d9da..5740bd1283 100644 --- a/features/steps/project/filter_labels.rb +++ b/features/steps/project/issues/filter_labels.rb @@ -1,25 +1,8 @@ -class ProjectFilterLabels < Spinach::FeatureSteps +class Spinach::Features::ProjectIssuesFilterLabels < Spinach::FeatureSteps include SharedAuthentication include SharedProject include SharedPaths - - step 'I should see "bug" in labels filter' do - within ".labels-filter" do - page.should have_content "bug" - end - end - - step 'I should see "feature" in labels filter' do - within ".labels-filter" do - page.should have_content "feature" - end - end - - step 'I should see "enhancement" in labels filter' do - within ".labels-filter" do - page.should have_content "enhancement" - end - end + include Select2Helper step 'I should see "Bugfix1" in issues list' do within ".issues-list" do @@ -46,9 +29,7 @@ class ProjectFilterLabels < Spinach::FeatureSteps end step 'I click link "bug"' do - within ".labels-filter" do - click_link "bug" - end + select2('bug', from: "#label_name") end step 'I click link "feature"' do diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb new file mode 100644 index 0000000000..b8e282b202 --- /dev/null +++ b/features/steps/project/issues/issues.rb @@ -0,0 +1,276 @@ +class Spinach::Features::ProjectIssues < Spinach::FeatureSteps + include SharedAuthentication + include SharedIssuable + include SharedProject + include SharedNote + include SharedPaths + include SharedMarkdown + + step 'I should see "Release 0.4" in issues' do + page.should have_content "Release 0.4" + end + + step 'I should not see "Release 0.3" in issues' do + page.should_not have_content "Release 0.3" + end + + step 'I should not see "Tweet control" in issues' do + page.should_not have_content "Tweet control" + end + + step 'I should see that I am subscribed' do + find(".subscribe-button span").text.should == "Unsubscribe" + end + + step 'I should see that I am unsubscribed' do + sleep 0.2 + find(".subscribe-button span").text.should == "Subscribe" + end + + step 'I click link "Closed"' do + click_link "Closed" + end + + step 'I click button "Unsubscribe"' do + click_on "Unsubscribe" + end + + step 'I should see "Release 0.3" in issues' do + page.should have_content "Release 0.3" + end + + step 'I should not see "Release 0.4" in issues' do + page.should_not have_content "Release 0.4" + end + + step 'I click link "All"' do + click_link "All" + end + + step 'I click link "Release 0.4"' do + click_link "Release 0.4" + end + + step 'I should see issue "Release 0.4"' do + page.should have_content "Release 0.4" + end + + step 'I click link "New Issue"' do + click_link "New Issue" + end + + step 'I click "author" dropdown' do + first('.ajax-users-select').click + end + + step 'I see current user as the first user' do + expect(page).to have_selector('.user-result', visible: true, count: 4) + users = page.all('.user-name') + users[0].text.should == 'Any' + users[1].text.should == 'Unassigned' + users[2].text.should == current_user.name + end + + step 'I submit new issue "500 error on profile"' do + fill_in "issue_title", with: "500 error on profile" + click_button "Submit new issue" + end + + step 'I submit new issue "500 error on profile" with label \'bug\'' do + fill_in "issue_title", with: "500 error on profile" + select 'bug', from: "Labels" + click_button "Submit new issue" + end + + step 'I click link "500 error on profile"' do + click_link "500 error on profile" + end + + step 'I should see label \'bug\' with issue' do + within '.issue-show-labels' do + page.should have_content 'bug' + end + end + + step 'I should see issue "500 error on profile"' do + issue = Issue.find_by(title: "500 error on profile") + page.should have_content issue.title + page.should have_content issue.author_name + page.should have_content issue.project.name + end + + step 'I fill in issue search with "Re"' do + filter_issue "Re" + end + + step 'I fill in issue search with "Bu"' do + filter_issue "Bu" + end + + step 'I fill in issue search with ".3"' do + filter_issue ".3" + end + + step 'I fill in issue search with "Something"' do + filter_issue "Something" + end + + step 'I fill in issue search with ""' do + filter_issue "" + end + + step 'project "Shop" has milestone "v2.2"' do + + milestone = create(:milestone, title: "v2.2", project: project) + + 3.times { create(:issue, project: project, milestone: milestone) } + end + + step 'project "Shop" has milestone "v3.0"' do + + milestone = create(:milestone, title: "v3.0", project: project) + + 3.times { create(:issue, project: project, milestone: milestone) } + end + + When 'I select milestone "v3.0"' do + select "v3.0", from: "milestone_id" + end + + step 'I should see selected milestone with title "v3.0"' do + issues_milestone_selector = "#issue_milestone_id_chzn > a" + find(issues_milestone_selector).should have_content("v3.0") + end + + When 'I select first assignee from "Shop" project' do + + first_assignee = project.users.first + select first_assignee.name, from: "assignee_id" + end + + step 'I should see first assignee from "Shop" as selected assignee' do + issues_assignee_selector = "#issue_assignee_id_chzn > a" + + assignee_name = project.users.first.name + find(issues_assignee_selector).should have_content(assignee_name) + end + + step 'project "Shop" have "Release 0.4" open issue' do + + create(:issue, + title: "Release 0.4", + project: project, + author: project.users.first, + description: "# Description header" + ) + end + + step 'project "Shop" have "Tweet control" open issue' do + create(:issue, + title: "Tweet control", + project: project, + author: project.users.first) + end + + step 'project "Shop" have "Release 0.3" closed issue' do + create(:closed_issue, + title: "Release 0.3", + project: project, + author: project.users.first) + end + + step 'project "Shop" has "Tasks-open" open issue with task markdown' do + create_taskable(:issue, 'Tasks-open') + end + + step 'project "Shop" has "Tasks-closed" closed issue with task markdown' do + create_taskable(:closed_issue, 'Tasks-closed') + end + + step 'empty project "Empty Project"' do + create :empty_project, name: 'Empty Project', namespace: @user.namespace + end + + When 'I visit empty project page' do + project = Project.find_by(name: 'Empty Project') + visit namespace_project_path(project.namespace, project) + end + + step 'I see empty project details with ssh clone info' do + project = Project.find_by(name: 'Empty Project') + all(:css, '.git-empty .clone').each do |element| + element.text.should include(project.url_to_repo) + end + end + + When "I visit empty project's issues page" do + project = Project.find_by(name: 'Empty Project') + visit namespace_project_issues_path(project.namespace, project) + end + + step 'I leave a comment with code block' do + within(".js-main-target-form") do + fill_in "note[note]", with: "```\nCommand [1]: /usr/local/bin/git , see [text](doc/text)\n```" + click_button "Add Comment" + sleep 0.05 + end + end + + step 'I should see an error alert section within the comment form' do + within(".js-main-target-form") do + find(".error-alert") + end + end + + step 'The code block should be unchanged' do + page.should have_content("```\nCommand [1]: /usr/local/bin/git , see [text](doc/text)\n```") + end + + step 'project \'Shop\' has issue \'Bugfix1\' with description: \'Description for issue1\'' do + issue = create(:issue, title: 'Bugfix1', description: 'Description for issue1', project: project) + end + + step 'project \'Shop\' has issue \'Feature1\' with description: \'Feature submitted for issue1\'' do + issue = create(:issue, title: 'Feature1', description: 'Feature submitted for issue1', project: project) + end + + step 'I fill in issue search with \'Description for issue1\'' do + filter_issue 'Description for issue' + end + + step 'I fill in issue search with \'issue1\'' do + filter_issue 'issue1' + end + + step 'I fill in issue search with \'Rock and roll\'' do + filter_issue 'Description for issue' + end + + step 'I should see \'Bugfix1\' in issues' do + page.should have_content 'Bugfix1' + end + + step 'I should see \'Feature1\' in issues' do + page.should have_content 'Feature1' + end + + step 'I should not see \'Bugfix1\' in issues' do + page.should_not have_content 'Bugfix1' + end + + step 'issue \'Release 0.4\' has label \'bug\'' do + label = project.labels.create!(name: 'bug', color: '#990000') + issue = Issue.find_by!(title: 'Release 0.4') + issue.labels << label + end + + step 'I click label \'bug\'' do + within ".issues-list" do + click_link 'bug' + end + end + + def filter_issue(text) + fill_in 'issue_search', with: text + end +end diff --git a/features/steps/project/labels.rb b/features/steps/project/issues/labels.rb similarity index 74% rename from features/steps/project/labels.rb rename to features/steps/project/issues/labels.rb index 8320405e09..6ce34c500c 100644 --- a/features/steps/project/labels.rb +++ b/features/steps/project/issues/labels.rb @@ -1,22 +1,10 @@ -class ProjectLabels < Spinach::FeatureSteps +class Spinach::Features::ProjectIssuesLabels < Spinach::FeatureSteps include SharedAuthentication include SharedProject include SharedPaths - step 'I should see label "bug"' do - within ".manage-labels-list" do - page.should have_content "bug" - end - end - - step 'I should see label "feature"' do - within ".manage-labels-list" do - page.should have_content "feature" - end - end - step 'I visit \'bug\' label edit page' do - visit edit_project_label_path(project, bug_label) + visit edit_namespace_project_label_path(project.namespace, project, bug_label) end step 'I remove label \'bug\'' do @@ -25,6 +13,22 @@ class ProjectLabels < Spinach::FeatureSteps end end + step 'I delete all labels' do + within '.labels' do + all('.btn-remove').each do |remove| + remove.click + sleep 0.05 + end + end + end + + step 'I should see labels help message' do + within '.labels' do + page.should have_content 'Create first label or generate default set of '\ + 'labels' + end + end + step 'I submit new label \'support\'' do fill_in 'Title', with: 'support' fill_in 'Background Color', with: '#F95610' @@ -55,6 +59,12 @@ class ProjectLabels < Spinach::FeatureSteps end end + step 'I should see label \'feature\'' do + within '.manage-labels-list' do + page.should have_content 'feature' + end + end + step 'I should see label \'bug\'' do within '.manage-labels-list' do page.should have_content 'bug' diff --git a/features/steps/project/milestones.rb b/features/steps/project/issues/milestones.rb similarity index 72% rename from features/steps/project/milestones.rb rename to features/steps/project/issues/milestones.rb index 5562b523d1..cce87a6d98 100644 --- a/features/steps/project/milestones.rb +++ b/features/steps/project/issues/milestones.rb @@ -1,37 +1,37 @@ -class ProjectMilestones < Spinach::FeatureSteps +class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps include SharedAuthentication include SharedProject include SharedPaths include SharedMarkdown - Then 'I should see milestone "v2.2"' do + step 'I should see milestone "v2.2"' do milestone = @project.milestones.find_by(title: "v2.2") page.should have_content(milestone.title[0..10]) page.should have_content(milestone.expires_at) - page.should have_content("Browse Issues") + page.should have_content("Issues") end - Given 'I click link "v2.2"' do + step 'I click link "v2.2"' do click_link "v2.2" end - Given 'I click link "New Milestone"' do + step 'I click link "New Milestone"' do click_link "New Milestone" end - And 'I submit new milestone "v2.3"' do + step 'I submit new milestone "v2.3"' do fill_in "milestone_title", with: "v2.3" click_button "Create milestone" end - Then 'I should see milestone "v2.3"' do + step 'I should see milestone "v2.3"' do milestone = @project.milestones.find_by(title: "v2.3") page.should have_content(milestone.title[0..10]) page.should have_content(milestone.expires_at) - page.should have_content("Browse Issues") + page.should have_content("Issues") end - And 'project "Shop" has milestone "v2.2"' do + step 'project "Shop" has milestone "v2.2"' do project = Project.find_by(name: "Shop") milestone = create(:milestone, title: "v2.2", @@ -41,7 +41,7 @@ class ProjectMilestones < Spinach::FeatureSteps 3.times { create(:issue, project: project, milestone: milestone) } end - Given 'the milestone has open and closed issues' do + step 'the milestone has open and closed issues' do project = Project.find_by(name: "Shop") milestone = project.milestones.find_by(title: 'v2.2') @@ -53,7 +53,7 @@ class ProjectMilestones < Spinach::FeatureSteps click_link 'All Issues' end - Then "I should see 3 issues" do + step 'I should see 3 issues' do page.should have_selector('#tab-issues li.issue-row', count: 4) end end diff --git a/features/steps/project/markdown_render.rb b/features/steps/project/markdown_render.rb deleted file mode 100644 index 1885649891..0000000000 --- a/features/steps/project/markdown_render.rb +++ /dev/null @@ -1,277 +0,0 @@ -# If you need to modify the existing seed repository for your tests, -# it is recommended that you make the changes on the `markdown` branch of the seed project repository, -# which should only be used by tests in this file. See `/spec/factories.rb#project` for more info. -class Spinach::Features::ProjectMarkdownRender < Spinach::FeatureSteps - include SharedAuthentication - include SharedPaths - include SharedMarkdown - - And 'I own project "Delta"' do - @project = Project.find_by(name: "Delta") - @project ||= create(:project, name: "Delta", namespace: @user.namespace) - @project.team << [@user, :master] - end - - Then 'I should see files from repository in markdown' do - current_path.should == project_tree_path(@project, "markdown") - page.should have_content "README.md" - page.should have_content "CHANGELOG" - end - - And 'I should see rendered README which contains correct links' do - page.should have_content "Welcome to GitLab GitLab is a free project and repository management application" - page.should have_link "GitLab API doc" - page.should have_link "GitLab API website" - page.should have_link "Rake tasks" - page.should have_link "backup and restore procedure" - page.should have_link "GitLab API doc directory" - page.should have_link "Maintenance" - end - - And 'I click on Gitlab API in README' do - click_link "GitLab API doc" - end - - Then 'I should see correct document rendered' do - current_path.should == project_blob_path(@project, "markdown/doc/api/README.md") - page.should have_content "All API requests require authentication" - end - - And 'I click on Rake tasks in README' do - click_link "Rake tasks" - end - - Then 'I should see correct directory rendered' do - current_path.should == project_tree_path(@project, "markdown/doc/raketasks") - page.should have_content "backup_restore.md" - page.should have_content "maintenance.md" - end - - And 'I click on GitLab API doc directory in README' do - click_link "GitLab API doc directory" - end - - Then 'I should see correct doc/api directory rendered' do - current_path.should == project_tree_path(@project, "markdown/doc/api") - page.should have_content "README.md" - page.should have_content "users.md" - end - - And 'I click on Maintenance in README' do - click_link "Maintenance" - end - - Then 'I should see correct maintenance file rendered' do - current_path.should == project_blob_path(@project, "markdown/doc/raketasks/maintenance.md") - page.should have_content "bundle exec rake gitlab:env:info RAILS_ENV=production" - end - - And 'I click on link "empty" in the README' do - within('.readme-holder') do - click_link "empty" - end - end - - And 'I click on link "id" in the README' do - within('.readme-holder') do - click_link "#id" - end - end - - And 'I navigate to the doc/api/README' do - click_link "doc" - click_link "api" - click_link "README.md" - end - - And 'I see correct file rendered' do - current_path.should == project_blob_path(@project, "markdown/doc/api/README.md") - page.should have_content "Contents" - page.should have_link "Users" - page.should have_link "Rake tasks" - end - - And 'I click on users in doc/api/README' do - click_link "Users" - end - - Then 'I should see the correct document file' do - current_path.should == project_blob_path(@project, "markdown/doc/api/users.md") - page.should have_content "Get a list of users." - end - - And 'I click on raketasks in doc/api/README' do - click_link "Rake tasks" - end - - # Markdown branch - - When 'I visit markdown branch' do - visit project_tree_path(@project, "markdown") - end - - When 'I visit markdown branch "README.md" blob' do - visit project_blob_path(@project, "markdown/README.md") - end - - When 'I visit markdown branch "d" tree' do - visit project_tree_path(@project, "markdown/d") - end - - When 'I visit markdown branch "d/README.md" blob' do - visit project_blob_path(@project, "markdown/d/README.md") - end - - Then 'I should see files from repository in markdown branch' do - current_path.should == project_tree_path(@project, "markdown") - page.should have_content "README.md" - page.should have_content "CHANGELOG" - end - - And 'I see correct file rendered in markdown branch' do - current_path.should == project_blob_path(@project, "markdown/doc/api/README.md") - page.should have_content "Contents" - page.should have_link "Users" - page.should have_link "Rake tasks" - end - - Then 'I should see correct document rendered for markdown branch' do - current_path.should == project_blob_path(@project, "markdown/doc/api/README.md") - page.should have_content "All API requests require authentication" - end - - Then 'I should see correct directory rendered for markdown branch' do - current_path.should == project_tree_path(@project, "markdown/doc/raketasks") - page.should have_content "backup_restore.md" - page.should have_content "maintenance.md" - end - - Then 'I should see the users document file in markdown branch' do - current_path.should == project_blob_path(@project, "markdown/doc/api/users.md") - page.should have_content "Get a list of users." - end - - # Expected link contents - - Then 'The link with text "empty" should have url "tree/markdown"' do - find('a', text: /^empty$/)['href'] == current_host + project_tree_path(@project, "markdown") - end - - Then 'The link with text "empty" should have url "blob/markdown/README.md"' do - find('a', text: /^empty$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") - end - - Then 'The link with text "empty" should have url "tree/markdown/d"' do - find('a', text: /^empty$/)['href'] == current_host + project_tree_path(@project, "markdown/d") - end - - Then 'The link with text "empty" should have url "blob/markdown/d/README.md"' do - find('a', text: /^empty$/)['href'] == current_host + project_blob_path(@project, "markdown/d/README.md") - end - - Then 'The link with text "ID" should have url "tree/markdownID"' do - find('a', text: /^#id$/)['href'] == current_host + project_tree_path(@project, "markdown") + '#id' - end - - Then 'The link with text "/ID" should have url "tree/markdownID"' do - find('a', text: /^\/#id$/)['href'] == current_host + project_tree_path(@project, "markdown") + '#id' - end - - Then 'The link with text "README.mdID" should have url "blob/markdown/README.mdID"' do - find('a', text: /^README.md#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id' - end - - Then 'The link with text "d/README.mdID" should have url "blob/markdown/d/README.mdID"' do - find('a', text: /^d\/README.md#id$/)['href'] == current_host + project_blob_path(@project, "d/markdown/README.md") + '#id' - end - - Then 'The link with text "ID" should have url "blob/markdown/README.mdID"' do - find('a', text: /^#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id' - end - - Then 'The link with text "/ID" should have url "blob/markdown/README.mdID"' do - find('a', text: /^\/#id$/)['href'] == current_host + project_blob_path(@project, "markdown/README.md") + '#id' - end - - # Wiki - - Given 'I go to wiki page' do - click_link "Wiki" - current_path.should == project_wiki_path(@project, "home") - end - - And 'I add various links to the wiki page' do - fill_in "wiki[content]", with: "[test](test)\n[GitLab API doc](api)\n[Rake tasks](raketasks)\n" - fill_in "wiki[message]", with: "Adding links to wiki" - click_button "Create page" - end - - Then 'Wiki page should have added links' do - current_path.should == project_wiki_path(@project, "home") - page.should have_content "test GitLab API doc Rake tasks" - end - - step 'I add a header to the wiki page' do - fill_in "wiki[content]", with: "# Wiki header\n" - fill_in "wiki[message]", with: "Add header to wiki" - click_button "Create page" - end - - step 'Wiki header should have correct id and link' do - header_should_have_correct_id_and_link(1, 'Wiki header', 'wiki-header') - end - - And 'I click on test link' do - click_link "test" - end - - Then 'I see new wiki page named test' do - current_path.should == project_wiki_path(@project, "test") - page.should have_content "Editing" - end - - When 'I go back to wiki page home' do - visit project_wiki_path(@project, "home") - current_path.should == project_wiki_path(@project, "home") - end - - And 'I click on GitLab API doc link' do - click_link "GitLab API" - end - - Then 'I see Gitlab API document' do - current_path.should == project_wiki_path(@project, "api") - page.should have_content "Editing" - end - - And 'I click on Rake tasks link' do - click_link "Rake tasks" - end - - Then 'I see Rake tasks directory' do - current_path.should == project_wiki_path(@project, "raketasks") - page.should have_content "Editing" - end - - Given 'I go directory which contains README file' do - visit project_tree_path(@project, "markdown/doc/api") - current_path.should == project_tree_path(@project, "markdown/doc/api") - end - - And 'I click on a relative link in README' do - click_link "Users" - end - - Then 'I should see the correct markdown' do - current_path.should == project_blob_path(@project, "markdown/doc/api/users.md") - page.should have_content "List users" - end - - step 'Header "Application details" should have correct id and link' do - header_should_have_correct_id_and_link(2, 'Application details', 'application-details') - end - - step 'Header "GitLab API" should have correct id and link' do - header_should_have_correct_id_and_link(1, 'GitLab API', 'gitlab-api') - end -end diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb index 05d3e5067c..bb1f9f129c 100644 --- a/features/steps/project/merge_requests.rb +++ b/features/steps/project/merge_requests.rb @@ -1,9 +1,11 @@ -class ProjectMergeRequests < Spinach::FeatureSteps +class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps include SharedAuthentication + include SharedIssuable include SharedProject include SharedNote include SharedPaths include SharedMarkdown + include SharedDiffNote step 'I click link "New Merge Request"' do click_link "New Merge Request" @@ -54,10 +56,20 @@ class ProjectMergeRequests < Spinach::FeatureSteps page.should_not have_content "Bug NS-04" end + step 'I should see that I am subscribed' do + find(".subscribe-button span").text.should == "Unsubscribe" + end + + step 'I should see that I am unsubscribed' do + find(".subscribe-button span").should have_content("Subscribe") + end + + step 'I click button "Unsubscribe"' do + click_on "Unsubscribe" + end + step 'I click link "Close"' do - within '.page-title' do - click_link "Close" - end + first(:css, '.close-mr-link').click end step 'I submit new merge request "Wiki Feature"' do @@ -96,17 +108,39 @@ class ProjectMergeRequests < Spinach::FeatureSteps author: project.users.first) end + step 'project "Shop" has "MR-task-open" open MR with task markdown' do + create_taskable(:merge_request, 'MR-task-open') + end + step 'I switch to the diff tab' do - visit diffs_project_merge_request_path(project, merge_request) + visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request) + end + + step 'I click on the Changes tab via Javascript' do + find('.diffs-tab').click + sleep 2 + end + + step 'I should see the proper Inline and Side-by-side links' do + buttons = all('#commit-diff-viewtype') + expect(buttons.count).to eq(2) + + buttons.each do |b| + expect(b['href']).should_not have_content('json') + end end step 'I switch to the merge request\'s comments tab' do - visit project_merge_request_path(project, merge_request) + visit namespace_project_merge_request_path(project.namespace, project, merge_request) end step 'I click on the commit in the merge request' do - within '.mr-commits' do - click_link sample_commit.id[0..8] + within '.merge-request-tabs' do + click_link 'Commits' + end + + within '.commits' do + click_link Commit.truncate_sha(sample_commit.id) end end @@ -148,12 +182,12 @@ class ProjectMergeRequests < Spinach::FeatureSteps end step 'merge request is mergeable' do - page.should have_content 'You can accept this request automatically' + page.should have_button 'Accept Merge Request' end step 'I modify merge commit message' do find('.modify-merge-commit-link').click - fill_in 'merge_commit_message', with: "wow such merge" + fill_in 'commit_message', with: 'wow such merge' end step 'merge request "Bug NS-05" is mergeable' do @@ -165,7 +199,9 @@ class ProjectMergeRequests < Spinach::FeatureSteps merge!: true, ) - click_button "Accept Merge Request" + within '.can_be_merged' do + click_button "Accept Merge Request" + end end step 'I should see merged request' do @@ -175,26 +211,24 @@ class ProjectMergeRequests < Spinach::FeatureSteps end step 'I click link "Reopen"' do - within '.page-title' do - click_link "Reopen" - end + first(:css, '.reopen-mr-link').click end step 'I should see reopened merge request "Bug NS-04"' do - within '.state-label' do + within '.issue-box' do page.should have_content "Open" end end step 'I click link "Hide inline discussion" of the second file' do within '.files [id^=diff]:nth-child(2)' do - click_link "Diff comments" + find('.js-toggle-diff-comments').click end end step 'I click link "Show inline discussion" of the second file' do within '.files [id^=diff]:nth-child(2)' do - click_link "Diff comments" + find('.js-toggle-diff-comments').click end end @@ -205,11 +239,23 @@ class ProjectMergeRequests < Spinach::FeatureSteps end step 'I should see a comment like "Line is wrong" in the second file' do - within '.files [id^=diff]:nth-child(2) .note-text' do + within '.files [id^=diff]:nth-child(2) .note-body > .note-text' do page.should have_visible_content "Line is wrong" end end + step 'I should not see a comment like "Line is wrong here" in the second file' do + within '.files [id^=diff]:nth-child(2)' do + page.should_not have_visible_content "Line is wrong here" + end + end + + step 'I should see a comment like "Line is wrong here" in the second file' do + within '.files [id^=diff]:nth-child(2) .note-body > .note-text' do + page.should have_visible_content "Line is wrong here" + end + end + step 'I leave a comment like "Line is correct" on line 12 of the first file' do init_diff_note_first_file @@ -218,7 +264,7 @@ class ProjectMergeRequests < Spinach::FeatureSteps click_button "Add Comment" end - within ".files [id^=diff]:nth-child(1) .note-text" do + within ".files [id^=diff]:nth-child(1) .note-body > .note-text" do page.should have_content "Line is correct" end end @@ -227,17 +273,13 @@ class ProjectMergeRequests < Spinach::FeatureSteps init_diff_note_second_file within(".js-discussion-note-form") do - fill_in "note_note", with: "Line is wrong" + fill_in "note_note", with: "Line is wrong on here" click_button "Add Comment" end - - within ".files [id^=diff]:nth-child(2) .note-text" do - page.should have_content "Line is wrong" - end end step 'I should still see a comment like "Line is correct" in the first file' do - within '.files [id^=diff]:nth-child(1) .note-text' do + within '.files [id^=diff]:nth-child(1) .note-body > .note-text' do page.should have_visible_content "Line is correct" end end @@ -250,8 +292,18 @@ class ProjectMergeRequests < Spinach::FeatureSteps expect(first('.text-file')).to have_content('.bundle') end - def project - @project ||= Project.find_by!(name: "Shop") + step 'I click Side-by-side Diff tab' do + find('a', text: 'Side-by-side').trigger('click') + end + + step 'I should see comments on the side-by-side diff page' do + within '.files [id^=diff]:nth-child(1) .parallel .note-body > .note-text' do + page.should have_visible_content "Line is correct" + end + end + + step 'I fill in merge request search with "Fe"' do + fill_in 'issue_search', with: "Fe" end def merge_request @@ -282,8 +334,4 @@ class ProjectMergeRequests < Spinach::FeatureSteps def have_visible_content (text) have_css("*", text: text, visible: true) end - - def click_diff_line(code) - find("a[data-line-code='#{code}']").click - end end diff --git a/features/steps/project/network_graph.rb b/features/steps/project/network_graph.rb index 9f5da28891..a15688ace6 100644 --- a/features/steps/project/network_graph.rb +++ b/features/steps/project/network_graph.rb @@ -1,9 +1,9 @@ -class ProjectNetworkGraph < Spinach::FeatureSteps +class Spinach::Features::ProjectNetworkGraph < Spinach::FeatureSteps include SharedAuthentication include SharedPaths include SharedProject - Then 'page should have network graph' do + step 'page should have network graph' do page.should have_selector ".network-graph" end @@ -12,30 +12,30 @@ class ProjectNetworkGraph < Spinach::FeatureSteps Network::Graph.stub(max_count: 10) project = Project.find_by(name: "Shop") - visit project_network_path(project, "master") + visit namespace_project_network_path(project.namespace, project, "master") end - And 'page should select "master" in select box' do + step 'page should select "master" in select box' do page.should have_selector '.select2-chosen', text: "master" end - And 'page should select "v1.0.0" in select box' do + step 'page should select "v1.0.0" in select box' do page.should have_selector '.select2-chosen', text: "v1.0.0" end - And 'page should have "master" on graph' do + step 'page should have "master" on graph' do within '.network-graph' do page.should have_content 'master' end end When 'I switch ref to "feature"' do - page.select 'feature', from: 'ref' + select 'feature', from: 'ref' sleep 2 end When 'I switch ref to "v1.0.0"' do - page.select 'v1.0.0', from: 'ref' + select 'v1.0.0', from: 'ref' sleep 2 end @@ -44,27 +44,27 @@ class ProjectNetworkGraph < Spinach::FeatureSteps sleep 2 end - Then 'page should have content not containing "v1.0.0"' do + step 'page should have content not containing "v1.0.0"' do within '.network-graph' do page.should have_content 'Change some files' end end - Then 'page should not have content not containing "v1.0.0"' do + step 'page should not have content not containing "v1.0.0"' do within '.network-graph' do page.should_not have_content 'Change some files' end end - And 'page should select "feature" in select box' do + step 'page should select "feature" in select box' do page.should have_selector '.select2-chosen', text: "feature" end - And 'page should select "v1.0.0" in select box' do + step 'page should select "v1.0.0" in select box' do page.should have_selector '.select2-chosen', text: "v1.0.0" end - And 'page should have "feature" on graph' do + step 'page should have "feature" on graph' do within '.network-graph' do page.should have_content 'feature' end @@ -78,7 +78,7 @@ class ProjectNetworkGraph < Spinach::FeatureSteps sleep 2 end - And 'page should have "v1.0.0" on graph' do + step 'page should have "v1.0.0" on graph' do within '.network-graph' do page.should have_content 'v1.0.0' end diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb index 2ffa1a6297..d39c8e7d2d 100644 --- a/features/steps/project/project.rb +++ b/features/steps/project/project.rb @@ -1,10 +1,10 @@ -class ProjectFeature < Spinach::FeatureSteps +class Spinach::Features::Project < Spinach::FeatureSteps include SharedAuthentication include SharedProject include SharedPaths step 'change project settings' do - fill_in 'project_name', with: 'NewName' + fill_in 'project_name_edit', with: 'NewName' uncheck 'project_issues_enabled' end @@ -17,23 +17,58 @@ class ProjectFeature < Spinach::FeatureSteps end step 'change project path settings' do - fill_in "project_path", with: "new-path" - click_button "Rename" + fill_in 'project_path', with: 'new-path' + click_button 'Rename' end step 'I should see project with new path settings' do - project.path.should == "new-path" + project.path.should == 'new-path' end - step 'I should see project "Shop" README link' do - within '.project-side' do - page.should have_content "README.md" - end + step 'I change the project avatar' do + attach_file( + :project_avatar, + File.join(Rails.root, 'public', 'gitlab_logo.png') + ) + click_button 'Save changes' + @project.reload + end + + step 'I should see new project avatar' do + @project.avatar.should be_instance_of AvatarUploader + url = @project.avatar.url + url.should == "/uploads/project/avatar/#{ @project.id }/gitlab_logo.png" + end + + step 'I should see the "Remove avatar" button' do + page.should have_link('Remove avatar') + end + + step 'I have an project avatar' do + attach_file( + :project_avatar, + File.join(Rails.root, 'public', 'gitlab_logo.png') + ) + click_button 'Save changes' + @project.reload + end + + step 'I remove my project avatar' do + click_link 'Remove avatar' + @project.reload + end + + step 'I should see the default project avatar' do + @project.avatar?.should be_false + end + + step 'I should not see the "Remove avatar" button' do + page.should_not have_link('Remove avatar') end step 'I should see project "Shop" version' do within '.project-side' do - page.should have_content "Version: 6.7.0.pre" + page.should have_content 'Version: 6.7.0.pre' end end @@ -45,4 +80,18 @@ class ProjectFeature < Spinach::FeatureSteps step 'I should see project default branch changed' do find(:css, 'select#project_default_branch').value.should == 'fix' end + + step 'I select project "Forum" README tab' do + click_link 'Readme' + end + + step 'I should see project "Forum" README' do + page.should have_link 'README.md' + page.should have_content 'Sample repo for testing gitlab features' + end + + step 'I should see project "Shop" README' do + page.should have_link 'README.md' + page.should have_content 'testme' + end end diff --git a/features/steps/project/project_shortcuts.rb b/features/steps/project/project_shortcuts.rb new file mode 100644 index 0000000000..a10e7bf78e --- /dev/null +++ b/features/steps/project/project_shortcuts.rb @@ -0,0 +1,36 @@ +class Spinach::Features::ProjectShortcuts < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedProject + include SharedProjectTab + + step 'I press "g" and "f"' do + find('body').native.send_key('g') + find('body').native.send_key('f') + end + + step 'I press "g" and "c"' do + find('body').native.send_key('g') + find('body').native.send_key('c') + end + + step 'I press "g" and "n"' do + find('body').native.send_key('g') + find('body').native.send_key('n') + end + + step 'I press "g" and "g"' do + find('body').native.send_key('g') + find('body').native.send_key('g') + end + + step 'I press "g" and "s"' do + find('body').native.send_key('g') + find('body').native.send_key('s') + end + + step 'I press "g" and "w"' do + find('body').native.send_key('g') + find('body').native.send_key('w') + end +end diff --git a/features/steps/project/redirects.rb b/features/steps/project/redirects.rb index 7e01735af9..57c6e39c80 100644 --- a/features/steps/project/redirects.rb +++ b/features/steps/project/redirects.rb @@ -13,30 +13,28 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps step 'I visit project "Community" page' do project = Project.find_by(name: 'Community') - visit project_path(project) + visit namespace_project_path(project.namespace, project) end step 'I should see project "Community" home page' do - Gitlab.config.gitlab.stub(:host).and_return("www.example.com") - within '.project-home-title' do + Gitlab.config.gitlab.should_receive(:host).and_return("www.example.com") + within '.navbar-gitlab .title' do page.should have_content 'Community' end end step 'I visit project "Enterprise" page' do project = Project.find_by(name: 'Enterprise') - visit project_path(project) + visit namespace_project_path(project.namespace, project) end step 'I visit project "CommunityDoesNotExist" page' do project = Project.find_by(name: 'Community') - visit project_path(project) + 'DoesNotExist' + visit namespace_project_path(project.namespace, project) + 'DoesNotExist' end step 'I click on "Sign In"' do - within '.pull-right' do - click_link "Sign in" - end + first(:link, "Sign in").click end step 'Authenticate' do @@ -50,8 +48,8 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps step 'I should be redirected to "Community" page' do project = Project.find_by(name: 'Community') - page.current_path.should == "/#{project.path_with_namespace}" - page.status_code.should == 200 + current_path.should == "/#{project.path_with_namespace}" + status_code.should == 200 end step 'I get redirected to signin page where I sign in' do @@ -65,7 +63,7 @@ class Spinach::Features::ProjectRedirects < Spinach::FeatureSteps step 'I should be redirected to "Enterprise" page' do project = Project.find_by(name: 'Enterprise') - page.current_path.should == "/#{project.path_with_namespace}" - page.status_code.should == 200 + current_path.should == "/#{project.path_with_namespace}" + status_code.should == 200 end end diff --git a/features/steps/project/services.rb b/features/steps/project/services.rb index 0594a08a5e..4b3d79324a 100644 --- a/features/steps/project/services.rb +++ b/features/steps/project/services.rb @@ -1,18 +1,23 @@ -class ProjectServices < Spinach::FeatureSteps +class Spinach::Features::ProjectServices < Spinach::FeatureSteps include SharedAuthentication include SharedProject include SharedPaths step 'I visit project "Shop" services page' do - visit project_services_path(@project) + visit namespace_project_services_path(@project.namespace, @project) end step 'I should see list of available services' do page.should have_content 'Project services' page.should have_content 'Campfire' - page.should have_content 'Hipchat' + page.should have_content 'HipChat' page.should have_content 'GitLab CI' page.should have_content 'Assembla' + page.should have_content 'Pushover' + page.should have_content 'Atlassian Bamboo' + page.should have_content 'JetBrains TeamCity' + page.should have_content 'Asana' + page.should have_content 'Irker (IRC gateway)' end step 'I click gitlab-ci service link' do @@ -31,7 +36,7 @@ class ProjectServices < Spinach::FeatureSteps end step 'I click hipchat service link' do - click_link 'Hipchat' + click_link 'HipChat' end step 'I fill hipchat settings' do @@ -45,6 +50,17 @@ class ProjectServices < Spinach::FeatureSteps find_field('Room').value.should == 'gitlab' end + step 'I fill hipchat settings with custom server' do + check 'Active' + fill_in 'Room', with: 'gitlab_custom' + fill_in 'Token', with: 'secretCustom' + fill_in 'Server', with: 'https://chat.example.com' + click_button 'Save' + end + + step 'I should see hipchat service settings with custom server saved' do + find_field('Server').value.should == 'https://chat.example.com' + end step 'I click pivotaltracker service link' do click_link 'PivotalTracker' @@ -88,6 +104,22 @@ class ProjectServices < Spinach::FeatureSteps find_field('Token').value.should == 'verySecret' end + step 'I click Asana service link' do + click_link 'Asana' + end + + step 'I fill Asana settings' do + check 'Active' + fill_in 'Api key', with: 'verySecret' + fill_in 'Restrict to branch', with: 'master' + click_button 'Save' + end + + step 'I should see Asana service settings saved' do + find_field('Api key').value.should == 'verySecret' + find_field('Restrict to branch').value.should == 'master' + end + step 'I click email on push service link' do click_link 'Emails on push' end @@ -101,21 +133,93 @@ class ProjectServices < Spinach::FeatureSteps find_field('Recipients').value.should == 'qa@company.name' end + step 'I click Irker service link' do + click_link 'Irker (IRC gateway)' + end + + step 'I fill Irker settings' do + check 'Active' + fill_in 'Recipients', with: 'irc://chat.freenode.net/#commits' + check 'Colorize messages' + click_button 'Save' + end + + step 'I should see Irker service settings saved' do + find_field('Recipients').value.should == 'irc://chat.freenode.net/#commits' + find_field('Colorize messages').value.should == '1' + end + step 'I click Slack service link' do click_link 'Slack' end step 'I fill Slack settings' do check 'Active' - fill_in 'Subdomain', with: 'gitlab' - fill_in 'Room', with: '#gitlab' - fill_in 'Token', with: 'verySecret' + fill_in 'Webhook', with: 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' click_button 'Save' end step 'I should see Slack service settings saved' do - find_field('Subdomain').value.should == 'gitlab' - find_field('Room').value.should == '#gitlab' - find_field('Token').value.should == 'verySecret' + find_field('Webhook').value.should == 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' + end + + step 'I click Pushover service link' do + click_link 'Pushover' + end + + step 'I fill Pushover settings' do + check 'Active' + fill_in 'Api key', with: 'verySecret' + fill_in 'User key', with: 'verySecret' + fill_in 'Device', with: 'myDevice' + select 'High Priority', from: 'Priority' + select 'Bike', from: 'Sound' + click_button 'Save' + end + + step 'I should see Pushover service settings saved' do + find_field('Api key').value.should == 'verySecret' + find_field('User key').value.should == 'verySecret' + find_field('Device').value.should == 'myDevice' + find_field('Priority').find('option[selected]').value.should == '1' + find_field('Sound').find('option[selected]').value.should == 'bike' + end + + step 'I click Atlassian Bamboo CI service link' do + click_link 'Atlassian Bamboo CI' + end + + step 'I fill Atlassian Bamboo CI settings' do + check 'Active' + fill_in 'Bamboo url', with: 'http://bamboo.example.com' + fill_in 'Build key', with: 'KEY' + fill_in 'Username', with: 'user' + fill_in 'Password', with: 'verySecret' + click_button 'Save' + end + + step 'I should see Atlassian Bamboo CI service settings saved' do + find_field('Bamboo url').value.should == 'http://bamboo.example.com' + find_field('Build key').value.should == 'KEY' + find_field('Username').value.should == 'user' + end + + step 'I click JetBrains TeamCity CI service link' do + click_link 'JetBrains TeamCity CI' + end + + step 'I fill JetBrains TeamCity CI settings' do + check 'Active' + fill_in 'Teamcity url', with: 'http://teamcity.example.com' + fill_in 'Build type', with: 'GitlabTest_Build' + fill_in 'Username', with: 'user' + fill_in 'Password', with: 'verySecret' + click_button 'Save' + end + + step 'I should see JetBrains TeamCity CI service settings saved' do + find_field('Teamcity url').value.should == 'http://teamcity.example.com' + find_field('Build type').value.should == 'GitlabTest_Build' + find_field('Username').value.should == 'user' end end diff --git a/features/steps/project/snippets.rb b/features/steps/project/snippets.rb index feae535fbe..343aeb53b1 100644 --- a/features/steps/project/snippets.rb +++ b/features/steps/project/snippets.rb @@ -1,10 +1,10 @@ -class ProjectSnippets < Spinach::FeatureSteps +class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps include SharedAuthentication include SharedProject include SharedNote include SharedPaths - And 'project "Shop" have "Snippet one" snippet' do + step 'project "Shop" have "Snippet one" snippet' do create(:project_snippet, title: "Snippet one", content: "Test content", @@ -13,7 +13,7 @@ class ProjectSnippets < Spinach::FeatureSteps author: project.users.first) end - And 'project "Shop" have no "Snippet two" snippet' do + step 'project "Shop" have no "Snippet two" snippet' do create(:snippet, title: "Snippet two", content: "Test content", @@ -21,37 +21,37 @@ class ProjectSnippets < Spinach::FeatureSteps author: project.users.first) end - Given 'I click link "New Snippet"' do + step 'I click link "New Snippet"' do click_link "Add new snippet" end - Given 'I click link "Snippet one"' do + step 'I click link "Snippet one"' do click_link "Snippet one" end - Then 'I should see "Snippet one" in snippets' do + step 'I should see "Snippet one" in snippets' do page.should have_content "Snippet one" end - And 'I should not see "Snippet two" in snippets' do + step 'I should not see "Snippet two" in snippets' do page.should_not have_content "Snippet two" end - And 'I should not see "Snippet one" in snippets' do + step 'I should not see "Snippet one" in snippets' do page.should_not have_content "Snippet one" end - And 'I click link "Edit"' do + step 'I click link "Edit"' do within ".file-title" do click_link "Edit" end end - And 'I click link "Remove Snippet"' do + step 'I click link "Remove Snippet"' do click_link "remove" end - And 'I submit new snippet "Snippet three"' do + step 'I submit new snippet "Snippet three"' do fill_in "project_snippet_title", :with => "Snippet three" fill_in "project_snippet_file_name", :with => "my_snippet.rb" within('.file-editor') do @@ -60,37 +60,33 @@ class ProjectSnippets < Spinach::FeatureSteps click_button "Create snippet" end - Then 'I should see snippet "Snippet three"' do + step 'I should see snippet "Snippet three"' do page.should have_content "Snippet three" page.should have_content "Content of snippet three" end - And 'I submit new title "Snippet new title"' do + step 'I submit new title "Snippet new title"' do fill_in "project_snippet_title", :with => "Snippet new title" click_button "Save" end - Then 'I should see "Snippet new title"' do + step 'I should see "Snippet new title"' do page.should have_content "Snippet new title" end - And 'I leave a comment like "Good snippet!"' do + step 'I leave a comment like "Good snippet!"' do within('.js-main-target-form') do fill_in "note_note", with: "Good snippet!" click_button "Add Comment" end end - Then 'I should see comment "Good snippet!"' do + step 'I should see comment "Good snippet!"' do page.should have_content "Good snippet!" end - And 'I visit snippet page "Snippet one"' do - visit project_snippet_path(project, project_snippet) - end - - def project - @project ||= Project.find_by!(name: "Shop") + step 'I visit snippet page "Snippet one"' do + visit namespace_project_snippet_path(project.namespace, project, project_snippet) end def project_snippet diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb new file mode 100644 index 0000000000..caf6c73ee0 --- /dev/null +++ b/features/steps/project/source/browse_files.rb @@ -0,0 +1,218 @@ +class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps + include SharedAuthentication + include SharedProject + include SharedPaths + include RepoHelpers + + step 'I should see files from repository' do + page.should have_content "VERSION" + page.should have_content ".gitignore" + page.should have_content "LICENSE" + end + + step 'I should see files from repository for "6d39438"' do + current_path.should == namespace_project_tree_path(@project.namespace, @project, "6d39438") + page.should have_content ".gitignore" + page.should have_content "LICENSE" + end + + step 'I see the ".gitignore"' do + page.should have_content '.gitignore' + end + + step 'I don\'t see the ".gitignore"' do + page.should_not have_content '.gitignore' + end + + step 'I click on ".gitignore" file in repo' do + click_link ".gitignore" + end + + step 'I should see its content' do + page.should have_content old_gitignore_content + end + + step 'I should see its new content' do + page.should have_content new_gitignore_content + end + + step 'I click link "Raw"' do + click_link 'Raw' + end + + step 'I should see raw file content' do + source.should == sample_blob.data + end + + step 'I click button "Edit"' do + click_link 'Edit' + end + + step 'I cannot see the edit button' do + page.should_not have_link 'edit' + end + + step 'The edit button is disabled' do + page.should have_css '.disabled', text: 'Edit' + end + + step 'I can edit code' do + set_new_content + evaluate_script('blob.editor.getValue()').should == new_gitignore_content + end + + step 'I edit code' do + set_new_content + end + + step 'I fill the new file name' do + fill_in :file_name, with: new_file_name + end + + step 'I fill the new branch name' do + fill_in :new_branch, with: 'new_branch_name' + end + + step 'I fill the new file name with an illegal name' do + fill_in :file_name, with: 'Spaces Not Allowed' + end + + step 'I fill the commit message' do + fill_in :commit_message, with: 'Not yet a commit message.' + end + + step 'I click link "Diff"' do + click_link 'Preview changes' + end + + step 'I click on "Commit Changes"' do + click_button 'Commit Changes' + end + + step 'I click on "Remove"' do + click_button 'Remove' + end + + step 'I click on "Remove file"' do + click_button 'Remove file' + end + + step 'I see diff' do + page.should have_css '.line_holder.new' + end + + step 'I click on "new file" link in repo' do + click_link 'new-file-link' + end + + step 'I can see new file page' do + page.should have_content "New file" + page.should have_content "Commit message" + end + + step 'I click on files directory' do + click_link 'files' + end + + step 'I click on History link' do + click_link 'History' + end + + step 'I see Browse dir link' do + page.should have_link 'Browse Dir »' + page.should_not have_link 'Browse Code »' + end + + step 'I click on readme file' do + within '.tree-table' do + click_link 'README.md' + end + end + + step 'I see Browse file link' do + page.should have_link 'Browse File »' + page.should_not have_link 'Browse Code »' + end + + step 'I see Browse code link' do + page.should have_link 'Browse Code »' + page.should_not have_link 'Browse File »' + page.should_not have_link 'Browse Dir »' + end + + step 'I click on Permalink' do + click_link 'Permalink' + end + + step 'I am redirected to the files URL' do + current_path.should == namespace_project_tree_path(@project.namespace, @project, 'master') + end + + step 'I am redirected to the ".gitignore"' do + expect(current_path).to eq(namespace_project_blob_path(@project.namespace, @project, 'master/.gitignore')) + end + + step 'I am redirected to the ".gitignore" on new branch' do + expect(current_path).to eq(namespace_project_blob_path(@project.namespace, @project, 'new_branch_name/.gitignore')) + end + + step 'I am redirected to the permalink URL' do + expect(current_path).to( + eq(namespace_project_blob_path(@project.namespace, @project, + @project.repository.commit.sha + + '/.gitignore')) + ) + end + + step 'I am redirected to the new file' do + expect(current_path).to eq(namespace_project_blob_path( + @project.namespace, @project, 'master/' + new_file_name)) + end + + step 'I am redirected to the new file on new branch' do + expect(current_path).to eq(namespace_project_blob_path( + @project.namespace, @project, 'new_branch_name/' + new_file_name)) + end + + step "I don't see the permalink link" do + expect(page).not_to have_link('permalink') + end + + step 'I see a commit error message' do + expect(page).to have_content('Your changes could not be committed') + end + + step 'I create bare repo' do + click_link 'Create empty bare repository' + end + + step 'I click on "add a file" link' do + click_link 'add a file' + + # Remove pre-receive hook so we can push without auth + FileUtils.rm_f(File.join(@project.repository.path, 'hooks', 'pre-receive')) + end + + private + + def set_new_content + execute_script("blob.editor.setValue('#{new_gitignore_content}')") + end + + # Content of the gitignore file on the seed repository. + def old_gitignore_content + '*.rbc' + end + + # Constant value that differs from the content + # of the gitignore of the seed repository. + def new_gitignore_content + old_gitignore_content + 'a' + end + + # Constant value that is a valid filename and + # not a filename present at root of the seed repository. + def new_file_name + 'not_a_file.md' + end +end diff --git a/features/steps/project/browse_git_repo.rb b/features/steps/project/source/git_blame.rb similarity index 54% rename from features/steps/project/browse_git_repo.rb rename to features/steps/project/source/git_blame.rb index 2c3017dd4e..e29a816c51 100644 --- a/features/steps/project/browse_git_repo.rb +++ b/features/steps/project/source/git_blame.rb @@ -1,17 +1,17 @@ -class ProjectBrowseGitRepo < Spinach::FeatureSteps +class Spinach::Features::ProjectSourceGitBlame < Spinach::FeatureSteps include SharedAuthentication include SharedProject include SharedPaths - Given 'I click on ".gitignore" file in repo' do + step 'I click on ".gitignore" file in repo' do click_link ".gitignore" end - And 'I click blame button' do - click_link "blame" + step 'I click Blame button' do + click_link 'Blame' end - Then 'I should see git file blame' do + step 'I should see git file blame' do page.should have_content "*.rb" page.should have_content "Dmitriy Zaporozhets" page.should have_content "Initial commit" diff --git a/features/steps/project/source/markdown_render.rb b/features/steps/project/source/markdown_render.rb new file mode 100644 index 0000000000..7961fdedad --- /dev/null +++ b/features/steps/project/source/markdown_render.rb @@ -0,0 +1,288 @@ +# If you need to modify the existing seed repository for your tests, +# it is recommended that you make the changes on the `markdown` branch of the seed project repository, +# which should only be used by tests in this file. See `/spec/factories.rb#project` for more info. +class Spinach::Features::ProjectSourceMarkdownRender < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedMarkdown + + step 'I own project "Delta"' do + @project = Project.find_by(name: "Delta") + @project ||= create(:project, name: "Delta", namespace: @user.namespace) + @project.team << [@user, :master] + end + + step 'I should see files from repository in markdown' do + current_path.should == namespace_project_tree_path(@project.namespace, @project, "markdown") + page.should have_content "README.md" + page.should have_content "CHANGELOG" + end + + step 'I should see rendered README which contains correct links' do + page.should have_content "Welcome to GitLab GitLab is a free project and repository management application" + page.should have_link "GitLab API doc" + page.should have_link "GitLab API website" + page.should have_link "Rake tasks" + page.should have_link "backup and restore procedure" + page.should have_link "GitLab API doc directory" + page.should have_link "Maintenance" + end + + step 'I click on Gitlab API in README' do + click_link "GitLab API doc" + end + + step 'I should see correct document rendered' do + current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md") + page.should have_content "All API requests require authentication" + end + + step 'I click on Rake tasks in README' do + click_link "Rake tasks" + end + + step 'I should see correct directory rendered' do + current_path.should == namespace_project_tree_path(@project.namespace, @project, "markdown/doc/raketasks") + page.should have_content "backup_restore.md" + page.should have_content "maintenance.md" + end + + step 'I click on GitLab API doc directory in README' do + click_link "GitLab API doc directory" + end + + step 'I should see correct doc/api directory rendered' do + current_path.should == namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api") + page.should have_content "README.md" + page.should have_content "users.md" + end + + step 'I click on Maintenance in README' do + click_link "Maintenance" + end + + step 'I should see correct maintenance file rendered' do + current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/raketasks/maintenance.md") + page.should have_content "bundle exec rake gitlab:env:info RAILS_ENV=production" + end + + step 'I click on link "empty" in the README' do + within('.readme-holder') do + click_link "empty" + end + end + + step 'I click on link "id" in the README' do + within('.readme-holder') do + click_link "#id" + end + end + + step 'I navigate to the doc/api/README' do + within '.tree-table' do + click_link "doc" + end + + within '.tree-table' do + click_link "api" + end + + within '.tree-table' do + click_link "README.md" + end + end + + step 'I see correct file rendered' do + current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md") + page.should have_content "Contents" + page.should have_link "Users" + page.should have_link "Rake tasks" + end + + step 'I click on users in doc/api/README' do + click_link "Users" + end + + step 'I should see the correct document file' do + current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md") + page.should have_content "Get a list of users." + end + + step 'I click on raketasks in doc/api/README' do + click_link "Rake tasks" + end + + # Markdown branch + + When 'I visit markdown branch' do + visit namespace_project_tree_path(@project.namespace, @project, "markdown") + end + + When 'I visit markdown branch "README.md" blob' do + visit namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + end + + When 'I visit markdown branch "d" tree' do + visit namespace_project_tree_path(@project.namespace, @project, "markdown/d") + end + + When 'I visit markdown branch "d/README.md" blob' do + visit namespace_project_blob_path(@project.namespace, @project, "markdown/d/README.md") + end + + step 'I should see files from repository in markdown branch' do + current_path.should == namespace_project_tree_path(@project.namespace, @project, "markdown") + page.should have_content "README.md" + page.should have_content "CHANGELOG" + end + + step 'I see correct file rendered in markdown branch' do + current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md") + page.should have_content "Contents" + page.should have_link "Users" + page.should have_link "Rake tasks" + end + + step 'I should see correct document rendered for markdown branch' do + current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/README.md") + page.should have_content "All API requests require authentication" + end + + step 'I should see correct directory rendered for markdown branch' do + current_path.should == namespace_project_tree_path(@project.namespace, @project, "markdown/doc/raketasks") + page.should have_content "backup_restore.md" + page.should have_content "maintenance.md" + end + + step 'I should see the users document file in markdown branch' do + current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md") + page.should have_content "Get a list of users." + end + + # Expected link contents + + step 'The link with text "empty" should have url "tree/markdown"' do + find('a', text: /^empty$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown") + end + + step 'The link with text "empty" should have url "blob/markdown/README.md"' do + find('a', text: /^empty$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + end + + step 'The link with text "empty" should have url "tree/markdown/d"' do + find('a', text: /^empty$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown/d") + end + + step 'The link with text "empty" should have '\ + 'url "blob/markdown/d/README.md"' do + find('a', text: /^empty$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/d/README.md") + end + + step 'The link with text "ID" should have url "tree/markdownID"' do + find('a', text: /^#id$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown") + '#id' + end + + step 'The link with text "/ID" should have url "tree/markdownID"' do + find('a', text: /^\/#id$/)['href'] == current_host + namespace_project_tree_path(@project.namespace, @project, "markdown") + '#id' + end + + step 'The link with text "README.mdID" '\ + 'should have url "blob/markdown/README.mdID"' do + find('a', text: /^README.md#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + '#id' + end + + step 'The link with text "d/README.mdID" should have '\ + 'url "blob/markdown/d/README.mdID"' do + find('a', text: /^d\/README.md#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "d/markdown/README.md") + '#id' + end + + step 'The link with text "ID" should have url "blob/markdown/README.mdID"' do + find('a', text: /^#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + '#id' + end + + step 'The link with text "/ID" should have url "blob/markdown/README.mdID"' do + find('a', text: /^\/#id$/)['href'] == current_host + namespace_project_blob_path(@project.namespace, @project, "markdown/README.md") + '#id' + end + + # Wiki + + step 'I go to wiki page' do + click_link "Wiki" + current_path.should == namespace_project_wiki_path(@project.namespace, @project, "home") + end + + step 'I add various links to the wiki page' do + fill_in "wiki[content]", with: "[test](test)\n[GitLab API doc](api)\n[Rake tasks](raketasks)\n" + fill_in "wiki[message]", with: "Adding links to wiki" + click_button "Create page" + end + + step 'Wiki page should have added links' do + current_path.should == namespace_project_wiki_path(@project.namespace, @project, "home") + page.should have_content "test GitLab API doc Rake tasks" + end + + step 'I add a header to the wiki page' do + fill_in "wiki[content]", with: "# Wiki header\n" + fill_in "wiki[message]", with: "Add header to wiki" + click_button "Create page" + end + + step 'Wiki header should have correct id and link' do + header_should_have_correct_id_and_link(1, 'Wiki header', 'wiki-header') + end + + step 'I click on test link' do + click_link "test" + end + + step 'I see new wiki page named test' do + current_path.should == namespace_project_wiki_path(@project.namespace, @project, "test") + page.should have_content "Editing" + end + + When 'I go back to wiki page home' do + visit namespace_project_wiki_path(@project.namespace, @project, "home") + current_path.should == namespace_project_wiki_path(@project.namespace, @project, "home") + end + + step 'I click on GitLab API doc link' do + click_link "GitLab API" + end + + step 'I see Gitlab API document' do + current_path.should == namespace_project_wiki_path(@project.namespace, @project, "api") + page.should have_content "Editing" + end + + step 'I click on Rake tasks link' do + click_link "Rake tasks" + end + + step 'I see Rake tasks directory' do + current_path.should == namespace_project_wiki_path(@project.namespace, @project, "raketasks") + page.should have_content "Editing" + end + + step 'I go directory which contains README file' do + visit namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api") + current_path.should == namespace_project_tree_path(@project.namespace, @project, "markdown/doc/api") + end + + step 'I click on a relative link in README' do + click_link "Users" + end + + step 'I should see the correct markdown' do + current_path.should == namespace_project_blob_path(@project.namespace, @project, "markdown/doc/api/users.md") + page.should have_content "List users" + end + + step 'Header "Application details" should have correct id and link' do + header_should_have_correct_id_and_link(2, 'Application details', 'application-details') + end + + step 'Header "GitLab API" should have correct id and link' do + header_should_have_correct_id_and_link(1, 'GitLab API', 'gitlab-api') + end +end diff --git a/features/steps/project/multiselect_blob.rb b/features/steps/project/source/multiselect_blob.rb similarity index 88% rename from features/steps/project/multiselect_blob.rb rename to features/steps/project/source/multiselect_blob.rb index d4dc118697..b749ba4937 100644 --- a/features/steps/project/multiselect_blob.rb +++ b/features/steps/project/source/multiselect_blob.rb @@ -1,4 +1,4 @@ -class ProjectMultiselectBlob < Spinach::FeatureSteps +class Spinach::Features::ProjectSourceMultiselectBlob < Spinach::FeatureSteps include SharedAuthentication include SharedProject include SharedPaths @@ -12,7 +12,7 @@ class ProjectMultiselectBlob < Spinach::FeatureSteps step "I shift-click line #{line_number} in file" do script = "$('#L#{line_number}').trigger($.Event('click', { shiftKey: true }));" - page.evaluate_script(script) + execute_script(script) end end end @@ -45,11 +45,11 @@ class ProjectMultiselectBlob < Spinach::FeatureSteps check_state_steps *Array(1..5), Array(1..2), Array(1..3), Array(1..4), Array(1..5), Array(3..5) step 'I go back in history' do - page.evaluate_script("window.history.back()") + go_back end step 'I go forward in history' do - page.evaluate_script("window.history.forward()") + go_forward end step 'I click on ".gitignore" file in repo' do diff --git a/features/steps/project/search_code.rb b/features/steps/project/source/search_code.rb similarity index 72% rename from features/steps/project/search_code.rb rename to features/steps/project/source/search_code.rb index affa7d3b43..9c2864cc93 100644 --- a/features/steps/project/search_code.rb +++ b/features/steps/project/source/search_code.rb @@ -1,4 +1,4 @@ -class ProjectSearchCode < Spinach::FeatureSteps +class Spinach::Features::ProjectSourceSearchCode < Spinach::FeatureSteps include SharedAuthentication include SharedProject include SharedPaths @@ -6,7 +6,6 @@ class ProjectSearchCode < Spinach::FeatureSteps step 'I search for term "coffee"' do fill_in "search", with: "coffee" click_button "Go" - click_link 'Repository Code' end step 'I should see files from repository containing "coffee"' do @@ -15,6 +14,6 @@ class ProjectSearchCode < Spinach::FeatureSteps end step 'I should see empty result' do - page.should have_content "We couldn't find any matching code" + page.should have_content "We couldn't find any matching" end end diff --git a/features/steps/project/star.rb b/features/steps/project/star.rb index 562df04e34..50cdfd73c3 100644 --- a/features/steps/project/star.rb +++ b/features/steps/project/star.rb @@ -22,12 +22,16 @@ class Spinach::Features::ProjectStar < Spinach::FeatureSteps # Requires @javascript step "I click on the star toggle button" do - page.find(".star .toggle", visible: true).click + find(".star-btn", visible: true).click + end + + step 'I redirected to sign in page' do + current_path.should == new_user_session_path end protected def has_n_stars(n) - expect(page).to have_css(".star .count", text: /^#{n}$/, visible: true) + expect(page).to have_css(".star-btn .count", text: n, visible: true) end end diff --git a/features/steps/project/team_management.rb b/features/steps/project/team_management.rb index ffc5016529..e95621071c 100644 --- a/features/steps/project/team_management.rb +++ b/features/steps/project/team_management.rb @@ -1,95 +1,115 @@ -class ProjectTeamManagement < Spinach::FeatureSteps +class Spinach::Features::ProjectTeamManagement < Spinach::FeatureSteps include SharedAuthentication include SharedProject include SharedPaths include Select2Helper - Then 'I should be able to see myself in team' do + step 'I should be able to see myself in team' do page.should have_content(@user.name) page.should have_content(@user.username) end - And 'I should see "Sam" in team list' do + step 'I should see "Sam" in team list' do user = User.find_by(name: "Sam") page.should have_content(user.name) page.should have_content(user.username) end - Given 'I click link "New Team Member"' do - click_link "New project member" + step 'I click link "Add members"' do + find(:css, 'button.btn-new').click end - And 'I select "Mike" as "Reporter"' do + step 'I select "Mike" as "Reporter"' do user = User.find_by(name: "Mike") - select2(user.id, from: "#user_ids", multiple: true) - within "#new_team_member" do - select "Reporter", from: "project_access" + within ".users-project-form" do + select2(user.id, from: "#user_ids", multiple: true) + select "Reporter", from: "access_level" end - click_button "Add users" + click_button "Add users to project" end - Then 'I should see "Mike" in team list as "Reporter"' do + step 'I should see "Mike" in team list as "Reporter"' do within ".access-reporter" do page.should have_content('Mike') end end - Given 'I should see "Sam" in team list as "Developer"' do + step 'I select "sjobs@apple.com" as "Reporter"' do + within ".users-project-form" do + select2("sjobs@apple.com", from: "#user_ids", multiple: true) + select "Reporter", from: "access_level" + end + click_button "Add users to project" + end + + step 'I should see "sjobs@apple.com" in team list as invited "Reporter"' do + within ".access-reporter" do + page.should have_content('sjobs@apple.com') + page.should have_content('invited') + page.should have_content('Reporter') + end + end + + step 'I should see "Sam" in team list as "Developer"' do within ".access-developer" do page.should have_content('Sam') end end - And 'I change "Sam" role to "Reporter"' do - user = User.find_by(name: "Sam") - within "#user_#{user.id}" do - select "Reporter", from: "team_member_project_access" + step 'I change "Sam" role to "Reporter"' do + project = Project.find_by(name: "Shop") + user = User.find_by(name: 'Sam') + project_member = project.project_members.find_by(user_id: user.id) + within "#project_member_#{project_member.id}" do + click_button "Edit access level" + select "Reporter", from: "project_member_access_level" + click_button "Save" end end - And 'I should see "Sam" in team list as "Reporter"' do + step 'I should see "Sam" in team list as "Reporter"' do within ".access-reporter" do page.should have_content('Sam') end end - And 'I click link "Remove from team"' do + step 'I click link "Remove from team"' do click_link "Remove from team" end - And 'I should not see "Sam" in team list' do + step 'I should not see "Sam" in team list' do user = User.find_by(name: "Sam") page.should_not have_content(user.name) page.should_not have_content(user.username) end - And 'gitlab user "Mike"' do + step 'gitlab user "Mike"' do create(:user, name: "Mike") end - And 'gitlab user "Sam"' do + step 'gitlab user "Sam"' do create(:user, name: "Sam") end - And '"Sam" is "Shop" developer' do + step '"Sam" is "Shop" developer' do user = User.find_by(name: "Sam") project = Project.find_by(name: "Shop") project.team << [user, :developer] end - Given 'I own project "Website"' do + step 'I own project "Website"' do @project = create(:empty_project, name: "Website", namespace: @user.namespace) @project.team << [@user, :master] end - And '"Mike" is "Website" reporter' do + step '"Mike" is "Website" reporter' do user = User.find_by(name: "Mike") project = Project.find_by(name: "Website") project.team << [user, :reporter] end - And 'I click link "Import team from another project"' do + step 'I click link "Import team from another project"' do click_link "Import members from another project" end @@ -100,7 +120,10 @@ class ProjectTeamManagement < Spinach::FeatureSteps end step 'I click cancel link for "Sam"' do - within "#user_#{User.find_by(name: 'Sam').id}" do + project = Project.find_by(name: "Shop") + user = User.find_by(name: 'Sam') + project_member = project.project_members.find_by(user_id: user.id) + within "#project_member_#{project_member.id}" do click_link('Remove user from team') end end diff --git a/features/steps/project/wiki.rb b/features/steps/project/wiki.rb index 96f2505d24..bb93e582a1 100644 --- a/features/steps/project/wiki.rb +++ b/features/steps/project/wiki.rb @@ -3,24 +3,24 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps include SharedProject include SharedNote include SharedPaths + include WikiHelper - Given 'I click on the Cancel button' do + step 'I click on the Cancel button' do within(:css, ".form-actions") do click_on "Cancel" end end - Then 'I should be redirected back to the Edit Home Wiki page' do - url = URI.parse(current_url) - url.path.should == project_wiki_path(project, :home) + step 'I should be redirected back to the Edit Home Wiki page' do + current_path.should == namespace_project_wiki_path(project.namespace, project, :home) end - Given 'I create the Wiki Home page' do + step 'I create the Wiki Home page' do fill_in "wiki_content", with: '[link test](test)' click_on "Create page" end - Then 'I should see the newly created wiki page' do + step 'I should see the newly created wiki page' do page.should have_content "Home" page.should have_content "link test" @@ -28,74 +28,73 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps page.should have_content "Editing" end - Given 'I have an existing Wiki page' do + step 'I have an existing Wiki page' do wiki.create_page("existing", "content", :markdown, "first commit") @page = wiki.find_page("existing") end - And 'I browse to that Wiki page' do - visit project_wiki_path(project, @page) + step 'I browse to that Wiki page' do + visit namespace_project_wiki_path(project.namespace, project, @page) end - And 'I click on the Edit button' do + step 'I click on the Edit button' do click_on "Edit" end - And 'I change the content' do + step 'I change the content' do fill_in "Content", with: 'Updated Wiki Content' click_on "Save changes" end - Then 'I should see the updated content' do + step 'I should see the updated content' do page.should have_content "Updated Wiki Content" end - Then 'I should be redirected back to that Wiki page' do - url = URI.parse(current_url) - url.path.should == project_wiki_path(project, @page) + step 'I should be redirected back to that Wiki page' do + current_path.should == namespace_project_wiki_path(project.namespace, project, @page) end - And 'That page has two revisions' do + step 'That page has two revisions' do @page.update("new content", :markdown, "second commit") end - And 'I click the History button' do + step 'I click the History button' do click_on "History" end - Then 'I should see both revisions' do + step 'I should see both revisions' do page.should have_content current_user.name page.should have_content "first commit" page.should have_content "second commit" end - And 'I click on the "Delete this page" button' do + step 'I click on the "Delete this page" button' do click_on "Delete this page" end - Then 'The page should be deleted' do + step 'The page should be deleted' do page.should have_content "Page was successfully deleted" end - And 'I click on the "Pages" button' do + step 'I click on the "Pages" button' do click_on "Pages" end - Then 'I should see the existing page in the pages list' do + step 'I should see the existing page in the pages list' do page.should have_content current_user.name page.should have_content @page.title end - Given 'I have an existing Wiki page with images linked on page' do + step 'I have an existing Wiki page with images linked on page' do wiki.create_page("pictures", "Look at this [image](image.jpg)\n\n ![image](image.jpg)", :markdown, "first commit") @wiki_page = wiki.find_page("pictures") end - And 'I browse to wiki page with images' do - visit project_wiki_path(project, @wiki_page) + step 'I browse to wiki page with images' do + visit namespace_project_wiki_path(project.namespace, project, @wiki_page) end - And 'I click on existing image link' do + step 'I click on existing image link' do file = Gollum::File.new(wiki.wiki) Gollum::Wiki.any_instance.stub(:file).with("image.jpg", "master", true).and_return(file) Gollum::File.any_instance.stub(:mime_type).and_return("image/jpeg") @@ -103,30 +102,63 @@ class Spinach::Features::ProjectWiki < Spinach::FeatureSteps click_on "image" end - Then 'I should see the image from wiki repo' do - url = URI.parse(current_url) - url.path.should match("wikis/image.jpg") + step 'I should see the image from wiki repo' do + current_path.should match('wikis/image.jpg') page.should_not have_xpath('/html') # Page should render the image which means there is no html involved Gollum::Wiki.any_instance.unstub(:file) Gollum::File.any_instance.unstub(:mime_type) end - Then 'Image should be shown on the page' do + step 'Image should be shown on the page' do page.should have_xpath("//img[@src=\"image.jpg\"]") end - And 'I click on image link' do + step 'I click on image link' do page.should have_link('image', href: "image.jpg") click_on "image" end - Then 'I should see the new wiki page form' do - url = URI.parse(current_url) - url.path.should match("wikis/image.jpg") + step 'I should see the new wiki page form' do + current_path.should match('wikis/image.jpg') page.should have_content('New Wiki Page') page.should have_content('Editing - image.jpg') end + step 'I create a New page with paths' do + click_on 'New Page' + fill_in 'Page slug', with: 'one/two/three' + click_on 'Build' + fill_in "wiki_content", with: 'wiki content' + click_on "Create page" + current_path.should include 'one/two/three' + end + + step 'I should see non-escaped link in the pages list' do + page.should have_xpath("//a[@href='/#{project.path_with_namespace}/wikis/one/two/three']") + end + + step 'I edit the Wiki page with a path' do + click_on 'three' + click_on 'Edit' + end + + step 'I should see a non-escaped path' do + current_path.should include 'one/two/three' + end + + step 'I should see the Editing page' do + page.should have_content('Editing') + end + + step 'I view the page history of a Wiki page that has a path' do + click_on 'three' + click_on 'Page History' + end + + step 'I should see the page history' do + page.should have_content('History for') + end + def wiki @project_wiki = ProjectWiki.new(project, current_user) end diff --git a/features/steps/search.rb b/features/steps/search.rb new file mode 100644 index 0000000000..6f0e038c4d --- /dev/null +++ b/features/steps/search.rb @@ -0,0 +1,69 @@ +class Spinach::Features::Search < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedProject + + step 'I search for "Sho"' do + fill_in "dashboard_search", with: "Sho" + click_button "Search" + end + + step 'I search for "Foo"' do + fill_in "dashboard_search", with: "Foo" + click_button "Search" + end + + step 'I search for "rspec"' do + fill_in "dashboard_search", with: "rspec" + click_button "Search" + end + + step 'I click "Issues" link' do + within '.search-filter' do + click_link 'Issues' + end + end + + step 'I click project "Shop" link' do + within '.project-filter' do + click_link project.name_with_namespace + end + end + + step 'I click "Merge requests" link' do + within '.search-filter' do + click_link 'Merge requests' + end + end + + step 'I should see "Shop" project link' do + page.should have_link "Shop" + end + + step 'I should see code results for project "Shop"' do + page.should have_content 'Update capybara, rspec-rails, poltergeist to recent versions' + end + + step 'I search for "Contibuting"' do + fill_in "dashboard_search", with: "Contibuting" + click_button "Search" + end + + step 'project has issues' do + create(:issue, title: "Foo", project: project) + create(:issue, title: "Bar", project: project) + end + + step 'project has merge requests' do + create(:merge_request, title: "Foo", source_project: project, target_project: project) + create(:merge_request, :simple, title: "Bar", source_project: project, target_project: project) + end + + step 'I should see "Foo" link in the search results' do + find(:css, '.search-results').should have_link 'Foo' + end + + step 'I should not see "Bar" link in the search results' do + find(:css, '.search-results').should_not have_link 'Bar' + end +end diff --git a/features/steps/shared/active_tab.rb b/features/steps/shared/active_tab.rb index e3cd5fcfe8..9beb688bd1 100644 --- a/features/steps/shared/active_tab.rb +++ b/features/steps/shared/active_tab.rb @@ -2,26 +2,46 @@ module SharedActiveTab include Spinach::DSL def ensure_active_main_tab(content) - page.find('.main-nav li.active').should have_content(content) + find('.nav-sidebar > li.active').should have_content(content) end def ensure_active_sub_tab(content) - page.find('div.content ul.nav-tabs li.active').should have_content(content) + find('div.content ul.nav-tabs li.active').should have_content(content) end def ensure_active_sub_nav(content) - page.find('div.content ul.nav-stacked-menu li.active').should have_content(content) + find('.sidebar-subnav > li.active').should have_content(content) end - And 'no other main tabs should be active' do - page.should have_selector('.main-nav li.active', count: 1) + step 'no other main tabs should be active' do + page.should have_selector('.nav-sidebar > li.active', count: 1) end - And 'no other sub tabs should be active' do + step 'no other sub tabs should be active' do page.should have_selector('div.content ul.nav-tabs li.active', count: 1) end - And 'no other sub navs should be active' do - page.should have_selector('div.content ul.nav-stacked-menu li.active', count: 1) + step 'no other sub navs should be active' do + page.should have_selector('.sidebar-subnav > li.active', count: 1) + end + + step 'the active main tab should be Home' do + ensure_active_main_tab('Your 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/shared/admin.rb b/features/steps/shared/admin.rb index 1b712dc6d0..b607299567 100644 --- a/features/steps/shared/admin.rb +++ b/features/steps/shared/admin.rb @@ -1,11 +1,11 @@ module SharedAdmin include Spinach::DSL - And 'there are projects in system' do + step 'there are projects in system' do 2.times { create(:project) } end - And 'system has users' do + step 'system has users' do 2.times { create(:user) } end end diff --git a/features/steps/shared/authentication.rb b/features/steps/shared/authentication.rb index b48021dc14..ac8a3df6bb 100644 --- a/features/steps/shared/authentication.rb +++ b/features/steps/shared/authentication.rb @@ -4,11 +4,11 @@ module SharedAuthentication include Spinach::DSL include LoginHelpers - Given 'I sign in as a user' do + step 'I sign in as a user' do login_as :user end - Given 'I sign in as an admin' do + step 'I sign in as an admin' do login_as :admin end diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb index b107b08322..510e0f0f93 100644 --- a/features/steps/shared/diff_note.rb +++ b/features/steps/shared/diff_note.rb @@ -2,24 +2,24 @@ module SharedDiffNote include Spinach::DSL include RepoHelpers - Given 'I cancel the diff comment' do + step 'I cancel the diff comment' do within(diff_file_selector) do find(".js-close-discussion-note-form").click end end - Given 'I delete a diff comment' do + step 'I delete a diff comment' do find('.note').hover find(".js-note-delete").click end - Given 'I haven\'t written any diff comment text' do + step 'I haven\'t written any diff comment text' do within(diff_file_selector) do fill_in "note[note]", with: "" end end - Given 'I leave a diff comment like "Typo, please fix"' do + step 'I leave a diff comment like "Typo, please fix"' do click_diff_line(sample_commit.line_code) within("#{diff_file_selector} form[rel$='#{sample_commit.line_code}']") do fill_in "note[note]", with: "Typo, please fix" @@ -28,130 +28,132 @@ module SharedDiffNote end end - Given 'I preview a diff comment text like "Should fix it :smile:"' do + step 'I preview a diff comment text like "Should fix it :smile:"' do click_diff_line(sample_commit.line_code) within("#{diff_file_selector} form[rel$='#{sample_commit.line_code}']") do fill_in "note[note]", with: "Should fix it :smile:" - find(".js-note-preview-button").trigger("click") + find('.js-md-preview-button').click end end - Given 'I preview another diff comment text like "DRY this up"' do + step 'I preview another diff comment text like "DRY this up"' do click_diff_line(sample_commit.del_line_code) within("#{diff_file_selector} form[rel$='#{sample_commit.del_line_code}']") do fill_in "note[note]", with: "DRY this up" - find(".js-note-preview-button").trigger("click") + find('.js-md-preview-button').click end end - Given 'I open a diff comment form' do + step 'I open a diff comment form' do click_diff_line(sample_commit.line_code) end - Given 'I open another diff comment form' do + step 'I open another diff comment form' do click_diff_line(sample_commit.del_line_code) end - Given 'I write a diff comment like ":-1: I don\'t like this"' do + step 'I write a diff comment like ":-1: I don\'t like this"' do within(diff_file_selector) do fill_in "note[note]", with: ":-1: I don\'t like this" end end - Given 'I submit the diff comment' do + step 'I submit the diff comment' do within(diff_file_selector) do click_button("Add Comment") end end - Then 'I should not see the diff comment form' do + step 'I should not see the diff comment form' do within(diff_file_selector) do page.should_not have_css("form.new_note") end end - Then 'I should not see the diff comment preview button' do + step 'The diff comment preview tab should say there is nothing to do' do within(diff_file_selector) do - page.should have_css(".js-note-preview-button", visible: false) + find('.js-md-preview-button').click + expect(find('.js-md-preview')).to have_content('Nothing to preview.') end end - Then 'I should not see the diff comment text field' do + step 'I should not see the diff comment text field' do within(diff_file_selector) do - page.should have_css(".js-note-text", visible: false) + expect(find('.js-note-text')).not_to be_visible end end - Then 'I should only see one diff form' do + step 'I should only see one diff form' do within(diff_file_selector) do page.should have_css("form.new_note", count: 1) end end - Then 'I should see a diff comment form with ":-1: I don\'t like this"' do + step 'I should see a diff comment form with ":-1: I don\'t like this"' do within(diff_file_selector) do page.should have_field("note[note]", with: ":-1: I don\'t like this") end end - Then 'I should see a diff comment saying "Typo, please fix"' do + step 'I should see a diff comment saying "Typo, please fix"' do within("#{diff_file_selector} .note") do page.should have_content("Typo, please fix") end end - Then 'I should see a discussion reply button' do + step 'I should see a discussion reply button' do within(diff_file_selector) do - page.should have_link("Reply") + page.should have_button('Reply') end end - Then 'I should see a temporary diff comment form' do + step 'I should see a temporary diff comment form' do within(diff_file_selector) do page.should have_css(".js-temp-notes-holder form.new_note") end end - Then 'I should see add a diff comment button' do - page.should have_css(".js-add-diff-note-button", visible: false) + step 'I should see add a diff comment button' do + page.should have_css('.js-add-diff-note-button', visible: true) end - Then 'I should see an empty diff comment form' do + step 'I should see an empty diff comment form' do within(diff_file_selector) do page.should have_field("note[note]", with: "") end end - Then 'I should see the cancel comment button' do + step 'I should see the cancel comment button' do within("#{diff_file_selector} form") do page.should have_css(".js-close-discussion-note-form", text: "Cancel") end end - Then 'I should see the diff comment preview' do + step 'I should see the diff comment preview' do within("#{diff_file_selector} form") do - page.should have_css(".js-note-preview", visible: false) + expect(page).to have_css('.js-md-preview', visible: true) end end - Then 'I should see the diff comment edit button' do + step 'I should see the diff comment write tab' do within(diff_file_selector) do - page.should have_css(".js-note-write-button", visible: true) + expect(page).to have_css('.js-md-write-button', visible: true) end end - Then 'I should see the diff comment preview button' do + step 'The diff comment preview tab should display rendered Markdown' do within(diff_file_selector) do - page.should have_css(".js-note-preview-button", visible: true) + find('.js-md-preview-button').click + expect(find('.js-md-preview')).to have_css('img.emoji', visible: true) end end - Then 'I should see two separate previews' do + step 'I should see two separate previews' do within(diff_file_selector) do - page.should have_css(".js-note-preview", visible: true, count: 2) - page.should have_content("Should fix it") - page.should have_content("DRY this up") + expect(page).to have_css('.js-md-preview', visible: true, count: 2) + expect(page).to have_content('Should fix it') + expect(page).to have_content('DRY this up') end end @@ -160,6 +162,6 @@ module SharedDiffNote end def click_diff_line(code) - find("a[data-line-code='#{code}']").click + find("button[data-line-code='#{code}']").click end end diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb new file mode 100644 index 0000000000..41db2612f2 --- /dev/null +++ b/features/steps/shared/issuable.rb @@ -0,0 +1,15 @@ +module SharedIssuable + include Spinach::DSL + + def edit_issuable + find(:css, '.issuable-edit').click + end + + step 'I click link "Edit" for the merge request' do + edit_issuable + end + + step 'I click link "Edit" for the issue' do + edit_issuable + end +end diff --git a/features/steps/shared/markdown.rb b/features/steps/shared/markdown.rb index 782f3f0920..e71700880c 100644 --- a/features/steps/shared/markdown.rb +++ b/features/steps/shared/markdown.rb @@ -2,11 +2,101 @@ module SharedMarkdown include Spinach::DSL def header_should_have_correct_id_and_link(level, text, id, parent = ".wiki") - page.find(:css, "#{parent} h#{level}##{id}").text.should == text - page.find(:css, "#{parent} h#{level}##{id} > :last-child")[:href].should =~ /##{id}$/ + find(:css, "#{parent} h#{level}##{id}").text.should == text + find(:css, "#{parent} h#{level}##{id} > :last-child")[:href].should =~ /##{id}$/ + end + + def create_taskable(type, title) + desc_text = < .note-text") do page.should have_content("Comment with a header") page.should_not have_css("#comment-with-a-header") end end + + step 'I leave a comment with task markdown' do + within('.js-main-target-form') do + fill_in 'note[note]', with: '* [x] Task item' + click_button 'Add Comment' + sleep 0.05 + end + end + + step 'I should not see task checkboxes in the comment' do + expect(page).not_to have_selector( + 'li.note div.timeline-content input[type="checkbox"]' + ) + end + + step 'I edit the last comment with a +1' do + find(".note").hover + find('.js-note-edit').click + + within(".current-note-edit-form") do + fill_in 'note[note]', with: '+1 Awesome!' + click_button 'Save Comment' + sleep 0.05 + end + end + + step 'I should see +1 in the description' do + within(".note") do + page.should have_content("+1 Awesome!") + end + end end diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb index 0d06383509..e3cf1b92cd 100644 --- a/features/steps/shared/paths.rb +++ b/features/steps/shared/paths.rb @@ -1,6 +1,7 @@ module SharedPaths include Spinach::DSL include RepoHelpers + include DashboardHelper step 'I visit new project page' do visit new_project_path @@ -31,7 +32,7 @@ module SharedPaths end step 'I visit group "Owned" members page' do - visit members_group_path(Group.find_by(name:"Owned")) + visit group_group_members_path(Group.find_by(name:"Owned")) end step 'I visit group "Owned" settings page' do @@ -51,7 +52,7 @@ module SharedPaths end step 'I visit group "Guest" members page' do - visit members_group_path(Group.find_by(name:"Guest")) + visit group_group_members_path(Group.find_by(name:"Guest")) end step 'I visit group "Guest" settings page' do @@ -71,11 +72,11 @@ module SharedPaths end step 'I visit dashboard issues page' do - visit issues_dashboard_path + visit assigned_issues_dashboard_path end step 'I visit dashboard merge requests page' do - visit merge_requests_dashboard_path + visit assigned_mrs_dashboard_path end step 'I visit dashboard search page' do @@ -86,6 +87,18 @@ module SharedPaths visit help_path end + step 'I visit dashboard groups page' do + visit dashboard_groups_path + end + + step 'I should be redirected to the dashboard groups page' do + current_path.should == dashboard_groups_path + end + + step 'I visit dashboard starred projects page' do + visit starred_dashboard_projects_path + end + # ---------------------------------------- # Profile # ---------------------------------------- @@ -94,6 +107,10 @@ module SharedPaths visit profile_path end + step 'I visit profile applications page' do + visit applications_profile_path + end + step 'I visit profile password page' do visit edit_profile_password_path end @@ -114,14 +131,6 @@ module SharedPaths visit history_profile_path end - step 'I visit profile groups page' do - visit profile_groups_path - end - - step 'I should be redirected to the profile groups page' do - current_path.should == profile_groups_path - end - # ---------------------------------------- # Admin # ---------------------------------------- @@ -131,7 +140,7 @@ module SharedPaths end step 'I visit admin projects page' do - visit admin_projects_path + visit admin_namespaces_projects_path end step 'I visit admin users page' do @@ -162,59 +171,72 @@ module SharedPaths visit admin_teams_path end + step 'I visit admin settings page' do + visit admin_application_settings_path + end + + step 'I visit applications page' do + visit admin_applications_path + end + # ---------------------------------------- # Generic Project # ---------------------------------------- step "I visit my project's home page" do - visit project_path(@project) + visit namespace_project_path(@project.namespace, @project) end step "I visit my project's settings page" do - visit edit_project_path(@project) + visit edit_namespace_project_path(@project.namespace, @project) end step "I visit my project's files page" do - visit project_tree_path(@project, root_ref) + visit namespace_project_tree_path(@project.namespace, @project, root_ref) + end + + step 'I visit a binary file in the repo' do + visit namespace_project_blob_path(@project.namespace, @project, File.join( + root_ref, 'files/images/logo-black.png')) end step "I visit my project's commits page" do - visit project_commits_path(@project, root_ref, {limit: 5}) + visit namespace_project_commits_path(@project.namespace, @project, root_ref, {limit: 5}) end step "I visit my project's commits page for a specific path" do - visit project_commits_path(@project, root_ref + "/app/models/project.rb", {limit: 5}) + visit namespace_project_commits_path(@project.namespace, @project, root_ref + "/app/models/project.rb", {limit: 5}) end step 'I visit my project\'s commits stats page' do - visit stats_project_repository_path(@project) + visit stats_namespace_project_repository_path(@project.namespace, @project) end step "I visit my project's network page" do # Stub Graph max_size to speed up test (10 commits vs. 650) Network::Graph.stub(max_count: 10) - visit project_network_path(@project, root_ref) + visit namespace_project_network_path(@project.namespace, @project, root_ref) end step "I visit my project's issues page" do - visit project_issues_path(@project) + visit namespace_project_issues_path(@project.namespace, @project) end step "I visit my project's merge requests page" do - visit project_merge_requests_path(@project) + visit namespace_project_merge_requests_path(@project.namespace, @project) end step "I visit my project's wiki page" do - visit project_wiki_path(@project, :home) + visit namespace_project_wiki_path(@project.namespace, @project, :home) end step 'I visit project hooks page' do - visit project_hooks_path(@project) + visit namespace_project_hooks_path(@project.namespace, @project) end step 'I visit project deploy keys page' do - visit project_deploy_keys_path(@project) + visit namespace_project_deploy_keys_path(@project.namespace, @project) end # ---------------------------------------- @@ -222,118 +244,153 @@ module SharedPaths # ---------------------------------------- step 'I visit project "Shop" page' do - visit project_path(project) + visit namespace_project_path(project.namespace, project) end step 'I visit project "Forked Shop" merge requests page' do - visit project_merge_requests_path(@forked_project) + visit namespace_project_merge_requests_path(@forked_project.namespace, @forked_project) end step 'I visit edit project "Shop" page' do - visit edit_project_path(project) + visit edit_namespace_project_path(project.namespace, project) end step 'I visit project branches page' do - visit project_branches_path(@project) + visit namespace_project_branches_path(@project.namespace, @project) end step 'I visit project protected branches page' do - visit project_protected_branches_path(@project) + visit namespace_project_protected_branches_path(@project.namespace, @project) end step 'I visit compare refs page' do - visit project_compare_index_path(@project) + visit namespace_project_compare_index_path(@project.namespace, @project) end step 'I visit project commits page' do - visit project_commits_path(@project, root_ref, {limit: 5}) + visit namespace_project_commits_path(@project.namespace, @project, root_ref, {limit: 5}) end step 'I visit project commits page for stable branch' do - visit project_commits_path(@project, 'stable', {limit: 5}) + visit namespace_project_commits_path(@project.namespace, @project, 'stable', {limit: 5}) end step 'I visit project source page' do - visit project_tree_path(@project, root_ref) + visit namespace_project_tree_path(@project.namespace, @project, root_ref) end step 'I visit blob file from repo' do - visit project_blob_path(@project, File.join(sample_commit.id, sample_blob.path)) + visit namespace_project_blob_path(@project.namespace, @project, File.join(sample_commit.id, sample_blob.path)) end step 'I visit ".gitignore" file in repo' do - visit project_blob_path(@project, File.join(root_ref, '.gitignore')) + visit namespace_project_blob_path(@project.namespace, @project, File.join(root_ref, '.gitignore')) + end + + step 'I am on the new file page' do + current_path.should eq(namespace_project_create_blob_path(@project.namespace, @project, root_ref)) + end + + step 'I am on the ".gitignore" edit file page' do + current_path.should eq(namespace_project_edit_blob_path( + @project.namespace, @project, File.join(root_ref, '.gitignore'))) end step 'I visit project source page for "6d39438"' do - visit project_tree_path(@project, "6d39438") + visit namespace_project_tree_path(@project.namespace, @project, "6d39438") + end + + step 'I visit project source page for' \ + ' "6d394385cf567f80a8fd85055db1ab4c5295806f"' do + visit namespace_project_tree_path(@project.namespace, @project, + '6d394385cf567f80a8fd85055db1ab4c5295806f') end step 'I visit project tags page' do - visit project_tags_path(@project) + visit namespace_project_tags_path(@project.namespace, @project) end step 'I visit project commit page' do - visit project_commit_path(@project, sample_commit.id) + visit namespace_project_commit_path(@project.namespace, @project, sample_commit.id) end step 'I visit project "Shop" issues page' do - visit project_issues_path(project) + visit namespace_project_issues_path(project.namespace, project) end step 'I visit issue page "Release 0.4"' do issue = Issue.find_by(title: "Release 0.4") - visit project_issue_path(issue.project, issue) + visit namespace_project_issue_path(issue.project.namespace, issue.project, issue) + end + + step 'I visit issue page "Tasks-open"' do + issue = Issue.find_by(title: 'Tasks-open') + visit namespace_project_issue_path(issue.project.namespace, issue.project, issue) + end + + step 'I visit issue page "Tasks-closed"' do + issue = Issue.find_by(title: 'Tasks-closed') + visit namespace_project_issue_path(issue.project.namespace, issue.project, issue) end step 'I visit project "Shop" labels page' do project = Project.find_by(name: 'Shop') - visit project_labels_path(project) + visit namespace_project_labels_path(project.namespace, project) end step 'I visit project "Forum" labels page' do project = Project.find_by(name: 'Forum') - visit project_labels_path(project) + visit namespace_project_labels_path(project.namespace, project) end step 'I visit project "Shop" new label page' do project = Project.find_by(name: 'Shop') - visit new_project_label_path(project) + visit new_namespace_project_label_path(project.namespace, project) end step 'I visit project "Forum" new label page' do project = Project.find_by(name: 'Forum') - visit new_project_label_path(project) + visit new_namespace_project_label_path(project.namespace, project) end step 'I visit merge request page "Bug NS-04"' do mr = MergeRequest.find_by(title: "Bug NS-04") - visit project_merge_request_path(mr.target_project, mr) + visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr) end step 'I visit merge request page "Bug NS-05"' do mr = MergeRequest.find_by(title: "Bug NS-05") - visit project_merge_request_path(mr.target_project, mr) + visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr) + end + + step 'I visit merge request page "MR-task-open"' do + mr = MergeRequest.find_by(title: 'MR-task-open') + visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr) + end + + step 'I visit merge request page "MR-task-closed"' do + mr = MergeRequest.find_by(title: 'MR-task-closed') + visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr) end step 'I visit project "Shop" merge requests page' do - visit project_merge_requests_path(project) + visit namespace_project_merge_requests_path(project.namespace, project) end step 'I visit forked project "Shop" merge requests page' do - visit project_merge_requests_path(project) + visit namespace_project_merge_requests_path(project.namespace, project) end step 'I visit project "Shop" milestones page' do - visit project_milestones_path(project) + visit namespace_project_milestones_path(project.namespace, project) end step 'I visit project "Shop" team page' do - visit project_team_index_path(project) + visit namespace_project_project_members_path(project.namespace, project) end step 'I visit project wiki page' do - visit project_wiki_path(@project, :home) + visit namespace_project_wiki_path(@project.namespace, @project, :home) end # ---------------------------------------- @@ -342,17 +399,22 @@ module SharedPaths step 'I visit project "Community" page' do project = Project.find_by(name: "Community") - visit project_path(project) + visit namespace_project_path(project.namespace, project) + end + + step 'I visit project "Community" source page' do + project = Project.find_by(name: 'Community') + visit namespace_project_tree_path(project.namespace, project, root_ref) end step 'I visit project "Internal" page' do project = Project.find_by(name: "Internal") - visit project_path(project) + visit namespace_project_path(project.namespace, project) end step 'I visit project "Enterprise" page' do project = Project.find_by(name: "Enterprise") - visit project_path(project) + visit namespace_project_path(project.namespace, project) end # ---------------------------------------- @@ -361,7 +423,7 @@ module SharedPaths step "I visit empty project page" do project = Project.find_by(name: "Empty Public Project") - visit project_path(project) + visit namespace_project_path(project.namespace, project) end # ---------------------------------------- @@ -388,15 +450,15 @@ module SharedPaths # Snippets # ---------------------------------------- - Given 'I visit project "Shop" snippets page' do - visit project_snippets_path(project) + step 'I visit project "Shop" snippets page' do + visit namespace_project_snippets_path(project.namespace, project) end - Given 'I visit snippets page' do + step 'I visit snippets page' do visit snippets_path end - Given 'I visit new snippet page' do + step 'I visit new snippet page' do visit new_snippet_path end @@ -405,14 +467,14 @@ module SharedPaths end def project - project = Project.find_by!(name: "Shop") + Project.find_by!(name: 'Shop') end # ---------------------------------------- # Errors # ---------------------------------------- - Then 'page status code should be 404' do - page.status_code.should == 404 + step 'page status code should be 404' do + status_code.should == 404 end end diff --git a/features/steps/shared/project.rb b/features/steps/shared/project.rb index c131976614..b60ac5e342 100644 --- a/features/steps/shared/project.rb +++ b/features/steps/shared/project.rb @@ -2,37 +2,49 @@ module SharedProject include Spinach::DSL # Create a project without caring about what it's called - And "I own a project" do + step "I own a project" do @project = create(:project, namespace: @user.namespace) @project.team << [@user, :master] end # Create a specific project called "Shop" - And 'I own project "Shop"' do + step 'I own project "Shop"' do @project = Project.find_by(name: "Shop") @project ||= create(:project, name: "Shop", namespace: @user.namespace, snippets_enabled: true) @project.team << [@user, :master] end + # Add another user to project "Shop" + step 'I add a user to project "Shop"' do + @project = Project.find_by(name: "Shop") + other_user = create(:user, name: 'Alpha') + @project.team << [other_user, :master] + end + # Create another specific project called "Forum" - And 'I own project "Forum"' do + step 'I own project "Forum"' do @project = Project.find_by(name: "Forum") @project ||= create(:project, name: "Forum", namespace: @user.namespace, path: 'forum_project') @project.team << [@user, :master] end # Create an empty project without caring about the name - And 'I own an empty project' do + step 'I own an empty project' do @project = create(:empty_project, name: 'Empty Project', namespace: @user.namespace) @project.team << [@user, :master] end - And 'project "Shop" has push event' do + step 'I visit my empty project page' do + project = Project.find_by(name: 'Empty Project') + visit namespace_project_path(project.namespace, project) + end + + step 'project "Shop" has push event' do @project = Project.find_by(name: "Shop") data = { - before: "0000000000000000000000000000000000000000", + before: Gitlab::Git::BLANK_SHA, after: "6d394385cf567f80a8fd85055db1ab4c5295806f", ref: "refs/heads/fix", user_id: @user.id, @@ -54,13 +66,13 @@ module SharedProject ) end - Then 'I should see project "Shop" activity feed' do + step 'I should see project "Shop" activity feed' do project = Project.find_by(name: "Shop") page.should have_content "#{@user.name} pushed new branch fix at #{project.name_with_namespace}" end - Then 'I should see project settings' do - current_path.should == edit_project_path(@project) + step 'I should see project settings' do + current_path.should == edit_namespace_project_path(@project.namespace, @project) page.should have_content("Project name") page.should have_content("Features:") end @@ -131,7 +143,7 @@ module SharedProject end step 'public empty project "Empty Public Project"' do - create :empty_project, :public, name: "Empty Public Project" + create :project_empty_repo, :public, name: "Empty Public Project" end step 'project "Community" has comments' do diff --git a/features/steps/shared/project_tab.rb b/features/steps/shared/project_tab.rb new file mode 100644 index 0000000000..c5aed19331 --- /dev/null +++ b/features/steps/shared/project_tab.rb @@ -0,0 +1,48 @@ +require_relative 'active_tab' + +module SharedProjectTab + include Spinach::DSL + include SharedActiveTab + + step 'the active main tab should be Home' do + 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') + end + + step 'the active main tab should be Network' do + ensure_active_main_tab('Network') + end + + step 'the active main tab should be Graphs' do + ensure_active_main_tab('Graphs') + 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 Snippets' do + ensure_active_main_tab('Snippets') + end + + step 'the active main tab should be Wiki' do + ensure_active_main_tab('Wiki') + end + + step 'the active main tab should be Settings' do + within '.nav-sidebar' do + page.should have_content('Back to project') + end + end +end diff --git a/features/steps/shared/search.rb b/features/steps/shared/search.rb new file mode 100644 index 0000000000..6c3d601763 --- /dev/null +++ b/features/steps/shared/search.rb @@ -0,0 +1,11 @@ +module SharedSearch + include Spinach::DSL + + def search_snippet_contents(query) + visit "/search?search=#{URI::encode(query)}&snippets=true&scope=snippet_blobs" + end + + def search_snippet_titles(query) + visit "/search?search=#{URI::encode(query)}&snippets=true&scope=snippet_titles" + end +end diff --git a/features/steps/shared/shortcuts.rb b/features/steps/shared/shortcuts.rb new file mode 100644 index 0000000000..bbb7afec0a --- /dev/null +++ b/features/steps/shared/shortcuts.rb @@ -0,0 +1,18 @@ +module SharedActiveTab + include Spinach::DSL + + step 'I press "g" and "p"' do + find('body').native.send_key('g') + find('body').native.send_key('p') + end + + step 'I press "g" and "i"' do + find('body').native.send_key('g') + find('body').native.send_key('i') + end + + step 'I press "g" and "m"' do + find('body').native.send_key('g') + find('body').native.send_key('m') + end +end diff --git a/features/steps/shared/snippet.rb b/features/steps/shared/snippet.rb index 543e43196a..bb596c1620 100644 --- a/features/steps/shared/snippet.rb +++ b/features/steps/shared/snippet.rb @@ -1,21 +1,63 @@ module SharedSnippet include Spinach::DSL - And 'I have public "Personal snippet one" snippet' do + step 'I have public "Personal snippet one" snippet' do create(:personal_snippet, title: "Personal snippet one", content: "Test content", file_name: "snippet.rb", - private: false, + visibility_level: Snippet::PUBLIC, author: current_user) end - And 'I have private "Personal snippet private" snippet' do + step 'I have private "Personal snippet private" snippet' do create(:personal_snippet, title: "Personal snippet private", content: "Provate content", file_name: "private_snippet.rb", - private: true, + visibility_level: Snippet::PRIVATE, author: current_user) end + + step 'I have internal "Personal snippet internal" snippet' do + create(:personal_snippet, + title: "Personal snippet internal", + content: "Provate content", + file_name: "internal_snippet.rb", + visibility_level: Snippet::INTERNAL, + author: current_user) + end + + step 'I have a public many lined snippet' do + create(:personal_snippet, + title: 'Many lined snippet', + content: <<-END.gsub(/^\s+\|/, ''), + |line one + |line two + |line three + |line four + |line five + |line six + |line seven + |line eight + |line nine + |line ten + |line eleven + |line twelve + |line thirteen + |line fourteen + END + file_name: 'many_lined_snippet.rb', + visibility_level: Snippet::PUBLIC, + author: current_user) + end + + step 'There is public "Personal snippet one" snippet' do + create(:personal_snippet, + title: "Personal snippet one", + content: "Test content", + file_name: "snippet.rb", + visibility_level: Snippet::PUBLIC, + author: create(:user)) + end end diff --git a/features/steps/snippet_search.rb b/features/steps/snippet_search.rb new file mode 100644 index 0000000000..669c7186c1 --- /dev/null +++ b/features/steps/snippet_search.rb @@ -0,0 +1,56 @@ +class Spinach::Features::SnippetSearch < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedSnippet + include SharedUser + include SharedSearch + + step 'I search for "snippet" in snippet titles' do + search_snippet_titles 'snippet' + end + + step 'I search for "snippet private" in snippet titles' do + search_snippet_titles 'snippet private' + end + + step 'I search for "line seven" in snippet contents' do + search_snippet_contents 'line seven' + end + + step 'I should see "line seven" in results' do + page.should have_content 'line seven' + end + + step 'I should see "line four" in results' do + page.should have_content 'line four' + end + + step 'I should see "line ten" in results' do + page.should have_content 'line ten' + end + + step 'I should not see "line eleven" in results' do + page.should_not have_content 'line eleven' + end + + step 'I should not see "line three" in results' do + page.should_not have_content 'line three' + end + + step 'I should see "Personal snippet one" in results' do + page.should have_content 'Personal snippet one' + end + + step 'I should see "Personal snippet private" in results' do + page.should have_content 'Personal snippet private' + end + + step 'I should not see "Personal snippet one" in results' do + page.should_not have_content 'Personal snippet one' + end + + step 'I should not see "Personal snippet private" in results' do + page.should_not have_content 'Personal snippet private' + end + +end diff --git a/features/steps/snippets/discover.rb b/features/steps/snippets/discover.rb index 0933793700..2667c1e3d4 100644 --- a/features/steps/snippets/discover.rb +++ b/features/steps/snippets/discover.rb @@ -1,13 +1,17 @@ -class DiscoverSnippets < Spinach::FeatureSteps +class Spinach::Features::SnippetsDiscover < Spinach::FeatureSteps include SharedAuthentication include SharedPaths include SharedSnippet - Then 'I should see "Personal snippet one" in snippets' do + step 'I should see "Personal snippet one" in snippets' do page.should have_content "Personal snippet one" end - And 'I should not see "Personal snippet private" in snippets' do + step 'I should see "Personal snippet internal" in snippets' do + page.should have_content "Personal snippet internal" + end + + step 'I should not see "Personal snippet private" in snippets' do page.should_not have_content "Personal snippet private" end diff --git a/features/steps/snippets/public_snippets.rb b/features/steps/snippets/public_snippets.rb new file mode 100644 index 0000000000..67669dc0a6 --- /dev/null +++ b/features/steps/snippets/public_snippets.rb @@ -0,0 +1,25 @@ +class Spinach::Features::PublicSnippets < Spinach::FeatureSteps + include SharedAuthentication + include SharedPaths + include SharedSnippet + + step 'I should see snippet "Personal snippet one"' do + page.should have_no_xpath("//i[@class='public-snippet']") + end + + step 'I should see raw snippet "Personal snippet one"' do + page.should have_text(snippet.content) + end + + step 'I visit snippet page "Personal snippet one"' do + visit snippet_path(snippet) + end + + step 'I visit snippet raw page "Personal snippet one"' do + visit raw_snippet_path(snippet) + end + + def snippet + @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one") + end +end diff --git a/features/steps/snippets/snippets.rb b/features/steps/snippets/snippets.rb index 040b5390a5..de936db85e 100644 --- a/features/steps/snippets/snippets.rb +++ b/features/steps/snippets/snippets.rb @@ -1,28 +1,28 @@ -class SnippetsFeature < Spinach::FeatureSteps +class Spinach::Features::Snippets < Spinach::FeatureSteps include SharedAuthentication include SharedPaths include SharedProject include SharedSnippet - Given 'I click link "Personal snippet one"' do + step 'I click link "Personal snippet one"' do click_link "Personal snippet one" end - And 'I should not see "Personal snippet one" in snippets' do + step 'I should not see "Personal snippet one" in snippets' do page.should_not have_content "Personal snippet one" end - And 'I click link "Edit"' do + step 'I click link "Edit"' do within ".file-title" do click_link "Edit" end end - And 'I click link "Destroy"' do + step 'I click link "Destroy"' do click_link "remove" end - And 'I submit new snippet "Personal snippet three"' do + step 'I submit new snippet "Personal snippet three"' do fill_in "personal_snippet_title", :with => "Personal snippet three" fill_in "personal_snippet_file_name", :with => "my_snippet.rb" within('.file-editor') do @@ -31,30 +31,30 @@ class SnippetsFeature < Spinach::FeatureSteps click_button "Create snippet" end - Then 'I should see snippet "Personal snippet three"' do + step 'I should see snippet "Personal snippet three"' do page.should have_content "Personal snippet three" page.should have_content "Content of snippet three" end - And 'I submit new title "Personal snippet new title"' do + step 'I submit new title "Personal snippet new title"' do fill_in "personal_snippet_title", :with => "Personal snippet new title" click_button "Save" end - Then 'I should see "Personal snippet new title"' do + step 'I should see "Personal snippet new title"' do page.should have_content "Personal snippet new title" end - And 'I uncheck "Private" checkbox' do - choose "Public" + step 'I uncheck "Private" checkbox' do + choose "Internal" click_button "Save" end - Then 'I should see "Personal snippet one" public' do + step 'I should see "Personal snippet one" public' do page.should have_no_xpath("//i[@class='public-snippet']") end - And 'I visit snippet page "Personal snippet one"' do + step 'I visit snippet page "Personal snippet one"' do visit snippet_path(snippet) end diff --git a/features/steps/snippets/user.rb b/features/steps/snippets/user.rb index 2d7ffc866e..866f637ab6 100644 --- a/features/steps/snippets/user.rb +++ b/features/steps/snippets/user.rb @@ -1,40 +1,54 @@ -class UserSnippets < Spinach::FeatureSteps +class Spinach::Features::SnippetsUser < Spinach::FeatureSteps include SharedAuthentication include SharedPaths include SharedSnippet - Given 'I visit my snippets page' do + step 'I visit my snippets page' do visit user_snippets_path(current_user) end - Then 'I should see "Personal snippet one" in snippets' do + step 'I should see "Personal snippet one" in snippets' do page.should have_content "Personal snippet one" end - And 'I should see "Personal snippet private" in snippets' do + step 'I should see "Personal snippet private" in snippets' do page.should have_content "Personal snippet private" end - Then 'I should not see "Personal snippet one" in snippets' do + step 'I should see "Personal snippet internal" in snippets' do + page.should have_content "Personal snippet internal" + end + + step 'I should not see "Personal snippet one" in snippets' do page.should_not have_content "Personal snippet one" end - And 'I should not see "Personal snippet private" in snippets' do + step 'I should not see "Personal snippet private" in snippets' do page.should_not have_content "Personal snippet private" end - Given 'I click "Public" filter' do + step 'I should not see "Personal snippet internal" in snippets' do + page.should_not have_content "Personal snippet internal" + end + + step 'I click "Internal" filter' do within('.nav-stacked') do - click_link "Public" + click_link "Internal" end end - Given 'I click "Private" filter' do + step 'I click "Private" filter' do within('.nav-stacked') do click_link "Private" end end + step 'I click "Public" filter' do + within('.nav-stacked') do + click_link "Public" + end + end + def snippet @snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one") end diff --git a/features/steps/user.rb b/features/steps/user.rb index 5fb248ffcb..10cae692a8 100644 --- a/features/steps/user.rb +++ b/features/steps/user.rb @@ -5,6 +5,39 @@ class Spinach::Features::User < Spinach::FeatureSteps include SharedProject step 'I should see user "John Doe" page' do - expect(page.title).to match(/^\s*John Doe/) + expect(title).to match(/^\s*John Doe/) + end + + step '"John Doe" has contributions' do + user = User.find_by(name: 'John Doe') + project = contributed_project + + # Issue controbution + issue_params = { title: 'Bug in old browser' } + Issues::CreateService.new(project, user, issue_params).execute + + # Push code contribution + push_params = { + project: project, + action: Event::PUSHED, + author_id: user.id, + data: { commit_count: 3 } + } + + Event.create(push_params) + end + + step 'I should see contributed projects' do + within '.contributed-projects' do + page.should have_content(@contributed_project.name) + end + end + + step 'I should see contributions calendar' do + page.should have_css('.cal-heatmap-container') + end + + def contributed_project + @contributed_project ||= create(:project, :public) end end diff --git a/features/support/env.rb b/features/support/env.rb index 22f28987fe..be17065ccf 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -1,20 +1,20 @@ -require 'simplecov' unless ENV['CI'] +if ENV['SIMPLECOV'] + require 'simplecov' +end -if ENV['TRAVIS'] +if ENV['COVERALLS'] require 'coveralls' - Coveralls.wear! + Coveralls.wear_merged! end ENV['RAILS_ENV'] = 'test' require './config/environment' - require 'rspec' require 'rspec/expectations' require 'database_cleaner' require 'spinach/capybara' require 'sidekiq/testing/inline' - %w(select2_helper test_env repo_helpers).each do |f| require Rails.root.join('spec', 'support', f) end @@ -47,8 +47,8 @@ Spinach.hooks.after_scenario do end Spinach.hooks.before_run do + include RSpec::Mocks::ExampleMethods TestEnv.init(mailer: false) - RSpec::Mocks::setup self include FactoryGirl::Syntax::Methods end diff --git a/features/user.feature b/features/user.feature index a2167935fd..69618e929c 100644 --- a/features/user.feature +++ b/features/user.feature @@ -67,3 +67,12 @@ Feature: User And I should see project "Enterprise" And I should not see project "Internal" And I should not see project "Community" + + @javascript + Scenario: "John Doe" contribution profile + Given I sign in as a user + And "John Doe" has contributions + When I visit user "John Doe" page + Then I should see user "John Doe" page + And I should see contributed projects + And I should see contributions calendar diff --git a/lib/api/api.rb b/lib/api/api.rb index 2c7cd9038c..60858a3940 100644 --- a/lib/api/api.rb +++ b/lib/api/api.rb @@ -2,10 +2,11 @@ Dir["#{Rails.root}/lib/api/*.rb"].each {|file| require file} module API class API < Grape::API + include APIGuard version 'v3', using: :path rescue_from ActiveRecord::RecordNotFound do - rack_response({'message' => '404 Not found'}.to_json, 404) + rack_response({ 'message' => '404 Not found' }.to_json, 404) end rescue_from :all do |exception| @@ -18,7 +19,7 @@ module API message << " " << trace.join("\n ") API.logger.add Logger::FATAL, message - rack_response({'message' => '500 Internal Server Error'}, 500) + rack_response({ 'message' => '500 Internal Server Error' }, 500) end format :json @@ -27,6 +28,7 @@ module API helpers APIHelpers mount Groups + mount GroupMembers mount Users mount Projects mount Repositories diff --git a/lib/api/api_guard.rb b/lib/api/api_guard.rb new file mode 100644 index 0000000000..b9994fcefd --- /dev/null +++ b/lib/api/api_guard.rb @@ -0,0 +1,172 @@ +# Guard API with OAuth 2.0 Access Token + +require 'rack/oauth2' + +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 + + # Must yield access token to store it in the env + request.access_token + end + + helpers HelperMethods + + 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) + 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) + + 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 + + def current_user + @current_user + end + + private + def find_access_token + @access_token ||= Doorkeeper.authenticate(doorkeeper_request, Doorkeeper.configuration.access_token_methods) + end + + def doorkeeper_request + @doorkeeper_request ||= ActionDispatch::Request.new(env) + end + + def validate_access_token(access_token, scopes) + Oauth2::AccessTokenValidationService.validate(access_token, scopes: scopes) + end + 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 + end + end + + private + def install_error_responders(base) + error_classes = [ MissingTokenError, TokenNotFoundError, + ExpiredError, RevokedError, InsufficientScopeError] + + 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 + + # + # Exceptions + # + + class MissingTokenError < StandardError; end + + class TokenNotFoundError < StandardError; end + + class ExpiredError < StandardError; end + + class RevokedError < StandardError; end + + class InsufficientScopeError < StandardError + attr_reader :scopes + def initialize(scopes) + @scopes = scopes + end + end +end diff --git a/lib/api/branches.rb b/lib/api/branches.rb index b32a4aa7bc..592100a704 100644 --- a/lib/api/branches.rb +++ b/lib/api/branches.rb @@ -14,7 +14,8 @@ module API # Example Request: # GET /projects/:id/repository/branches get ":id/repository/branches" do - present user_project.repo.heads.sort_by(&:name), with: Entities::RepoObject, project: user_project + branches = user_project.repository.branches.sort_by(&:name) + present branches, with: Entities::RepoObject, project: user_project end # Get a single branch @@ -25,8 +26,8 @@ module API # Example Request: # GET /projects/:id/repository/branches/:branch get ':id/repository/branches/:branch', requirements: { branch: /.*/ } do - @branch = user_project.repo.heads.find { |item| item.name == params[:branch] } - not_found!("Branch does not exist") if @branch.nil? + @branch = user_project.repository.branches.find { |item| item.name == params[:branch] } + not_found!("Branch") unless @branch present @branch, with: Entities::RepoObject, project: user_project end @@ -43,7 +44,7 @@ module API authorize_admin_project @branch = user_project.repository.find_branch(params[:branch]) - not_found! unless @branch + not_found!("Branch") unless @branch protected_branch = user_project.protected_branches.find_by(name: @branch.name) user_project.protected_branches.create(name: @branch.name) unless protected_branch @@ -63,7 +64,7 @@ module API authorize_admin_project @branch = user_project.repository.find_branch(params[:branch]) - not_found! unless @branch + not_found!("Branch does not exist") unless @branch protected_branch = user_project.protected_branches.find_by(name: @branch.name) protected_branch.destroy if protected_branch @@ -80,9 +81,16 @@ module API # POST /projects/:id/repository/branches post ":id/repository/branches" do authorize_push_project - @branch = CreateBranchService.new.execute(user_project, params[:branch_name], params[:ref], current_user) + result = CreateBranchService.new(user_project, current_user). + execute(params[:branch_name], params[:ref]) - present @branch, with: Entities::RepoObject, project: user_project + if result[:status] == :success + present result[:branch], + with: Entities::RepoObject, + project: user_project + else + render_api_error!(result[:message], 400) + end end # Delete branch @@ -92,14 +100,18 @@ module API # branch (required) - The name of the branch # Example Request: # DELETE /projects/:id/repository/branches/:branch - delete ":id/repository/branches/:branch" do + delete ":id/repository/branches/:branch", + requirements: { branch: /.*/ } do authorize_push_project - result = DeleteBranchService.new.execute(user_project, params[:branch], current_user) + result = DeleteBranchService.new(user_project, current_user). + execute(params[:branch]) - if result[:state] == :success - true + if result[:status] == :success + { + branch_name: params[:branch] + } else - render_api_error!(result[:message], 405) + render_api_error!(result[:message], result[:return_code]) end end end diff --git a/lib/api/commits.rb b/lib/api/commits.rb index 4a67313430..0de4e720ff 100644 --- a/lib/api/commits.rb +++ b/lib/api/commits.rb @@ -50,6 +50,67 @@ module API not_found! "Commit" unless commit commit.diffs end + + # Get a commit's comments + # + # Parameters: + # id (required) - The ID of a project + # sha (required) - The commit hash + # Examples: + # GET /projects/:id/repository/commits/:sha/comments + get ':id/repository/commits/:sha/comments' do + sha = params[:sha] + commit = user_project.repository.commit(sha) + not_found! 'Commit' unless commit + notes = Note.where(commit_id: commit.id) + present paginate(notes), with: Entities::CommitNote + end + + # Post comment to commit + # + # Parameters: + # id (required) - The ID of a project + # sha (required) - The commit hash + # note (required) - Text of comment + # path (optional) - The file path + # line (optional) - The line number + # line_type (optional) - The type of line (new or old) + # Examples: + # POST /projects/:id/repository/commits/:sha/comments + post ':id/repository/commits/:sha/comments' do + required_attributes! [:note] + + sha = params[:sha] + commit = user_project.repository.commit(sha) + not_found! 'Commit' unless commit + opts = { + note: params[:note], + noteable_type: 'Commit', + commit_id: commit.id + } + + if params[:path] && params[:line] && params[:line_type] + commit.diffs.each do |diff| + next unless diff.new_path == params[:path] + lines = Gitlab::Diff::Parser.new.parse(diff.diff.lines.to_a) + + lines.each do |line| + next unless line.new_pos == params[:line].to_i && line.type == params[:line_type] + break opts[:line_code] = Gitlab::Diff::LineCode.generate(diff.new_path, line.new_pos, line.old_pos) + end + + break if opts[:line_code] + end + end + + note = ::Notes::CreateService.new(user_project, current_user, opts).execute + + if note.save + present note, with: Entities::CommitNote + else + render_api_error!("Failed to save note #{note.errors.messages}", 400) + end + end end end end diff --git a/lib/api/deploy_keys.rb b/lib/api/deploy_keys.rb index 7f5a125038..06eb775684 100644 --- a/lib/api/deploy_keys.rb +++ b/lib/api/deploy_keys.rb @@ -58,7 +58,7 @@ module API if key.valid? && user_project.deploy_keys << key present key, with: Entities::SSHKey else - not_found! + render_validation_error!(key) end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 74fdef9354..36332bc651 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -14,9 +14,14 @@ module API expose :bio, :skype, :linkedin, :twitter, :website_url end + class Identity < Grape::Entity + expose :provider, :extern_uid + end + class UserFull < User expose :email - expose :theme_id, :color_scheme_id, :extern_uid, :provider + expose :theme_id, :color_scheme_id, :projects_limit + expose :identities, using: Entities::Identity expose :can_create_group?, as: :can_create_group expose :can_create_project?, as: :can_create_project end @@ -30,7 +35,8 @@ module API end class ProjectHook < Hook - expose :project_id, :push_events, :issues_events, :merge_requests_events + expose :project_id, :push_events + expose :issues_events, :merge_requests_events, :tag_push_events end class ForkedFromProject < Grape::Entity @@ -40,7 +46,7 @@ module API end class Project < Grape::Entity - expose :id, :description, :default_branch + expose :id, :description, :default_branch, :tag_list expose :public?, as: :public expose :archived?, as: :archived expose :visibility_level, :ssh_url_to_repo, :http_url_to_repo, :web_url @@ -48,18 +54,20 @@ module API expose :name, :name_with_namespace expose :path, :path_with_namespace expose :issues_enabled, :merge_requests_enabled, :wiki_enabled, :snippets_enabled, :created_at, :last_activity_at + expose :creator_id expose :namespace - expose :forked_from_project, using: Entities::ForkedFromProject, :if => lambda{ | project, options | project.forked? } + expose :forked_from_project, using: Entities::ForkedFromProject, if: lambda{ | project, options | project.forked? } + expose :avatar_url end class ProjectMember < UserBasic - expose :project_access, as: :access_level do |user, options| - options[:project].users_projects.find_by(user_id: user.id).project_access + expose :access_level do |user, options| + options[:project].project_members.find_by(user_id: user.id).access_level end end class Group < Grape::Entity - expose :id, :name, :path, :owner_id + expose :id, :name, :path, :description end class GroupDetail < Group @@ -67,8 +75,27 @@ module API end class GroupMember < UserBasic - expose :group_access, as: :access_level do |user, options| - options[:group].users_groups.find_by(user_id: user.id).group_access + expose :access_level do |user, options| + options[:group].group_members.find_by(user_id: user.id).access_level + end + end + + class RepoTag < Grape::Entity + expose :name + expose :message do |repo_obj, _options| + if repo_obj.respond_to?(:message) + repo_obj.message + else + nil + end + end + + expose :commit do |repo_obj, options| + if repo_obj.respond_to?(:commit) + repo_obj.commit + elsif options[:project] + options[:project].repository.commit(repo_obj.target) + end end end @@ -117,11 +144,16 @@ module API class ProjectEntity < Grape::Entity expose :id, :iid - expose (:project_id) { |entity| entity.project.id } + expose(:project_id) { |entity| entity.project.id } expose :title, :description expose :state, :created_at, :updated_at end + class RepoDiff < Grape::Entity + expose :old_path, :new_path, :a_mode, :b_mode, :diff + expose :new_file, :renamed_file, :deleted_file + end + class Milestone < ProjectEntity expose :due_date end @@ -141,6 +173,12 @@ module API expose :milestone, using: Entities::Milestone end + class MergeRequestChanges < MergeRequest + expose :diffs, as: :changes, using: Entities::RepoDiff do |compare, _| + compare.diffs + end + end + class SSHKey < Grape::Entity expose :id, :title, :key, :created_at end @@ -158,11 +196,25 @@ module API expose :author, using: Entities::UserBasic end + 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 :author, using: Entities::UserBasic + end + class Event < Grape::Entity expose :title, :project_id, :action_name expose :target_id, :target_type, :author_id expose :data, :target_title expose :created_at + + expose :author_username do |event, options| + if event.author + event.author.username + end + end end class Namespace < Grape::Entity @@ -170,24 +222,24 @@ module API end class ProjectAccess < Grape::Entity - expose :project_access, as: :access_level + expose :access_level expose :notification_level end class GroupAccess < Grape::Entity - expose :group_access, as: :access_level + expose :access_level expose :notification_level end class ProjectWithAccess < Project expose :permissions do expose :project_access, using: Entities::ProjectAccess do |project, options| - project.users_projects.find_by(user_id: options[:user].id) + project.project_members.find_by(user_id: options[:user].id) end expose :group_access, using: Entities::GroupAccess do |project, options| if project.group - project.group.users_groups.find_by(user_id: options[:user].id) + project.group.group_members.find_by(user_id: options[:user].id) end end end @@ -197,11 +249,6 @@ module API expose :name, :color end - class RepoDiff < Grape::Entity - expose :old_path, :new_path, :a_mode, :b_mode, :diff - expose :new_file, :renamed_file, :deleted_file - end - class Compare < Grape::Entity expose :commit, using: Entities::RepoCommit do |compare, options| Commit.decorate(compare.commits).last @@ -225,5 +272,9 @@ module API class Contributor < Grape::Entity expose :name, :email, :commits, :additions, :deletions end + + class BroadcastMessage < Grape::Entity + expose :message, :starts_at, :ends_at, :color, :font + end end end diff --git a/lib/api/files.rb b/lib/api/files.rb index e63e635a4d..3176ef0e25 100644 --- a/lib/api/files.rb +++ b/lib/api/files.rb @@ -35,7 +35,7 @@ module API file_path = attrs.delete(:file_path) commit = user_project.repository.commit(ref) - not_found! "Commit" unless commit + not_found! 'Commit' unless commit blob = user_project.repository.blob_at(commit.sha, file_path) @@ -53,7 +53,7 @@ module API commit_id: commit.id, } else - render_api_error!('File not found', 404) + not_found! 'File' end end @@ -85,7 +85,7 @@ module API branch_name: branch_name } else - render_api_error!(result[:error], 400) + render_api_error!(result[:message], 400) end end @@ -117,7 +117,8 @@ module API branch_name: branch_name } else - render_api_error!(result[:error], 400) + http_status = result[:http_status] || 400 + render_api_error!(result[:message], http_status) end end @@ -149,7 +150,7 @@ module API branch_name: branch_name } else - render_api_error!(result[:error], 400) + render_api_error!(result[:message], 400) end end end diff --git a/lib/api/group_members.rb b/lib/api/group_members.rb new file mode 100644 index 0000000000..ab9b7c602b --- /dev/null +++ b/lib/api/group_members.rb @@ -0,0 +1,87 @@ +module API + class GroupMembers < Grape::API + before { authenticate! } + + resource :groups do + # Get a list of group members viewable by the authenticated user. + # + # Example Request: + # GET /groups/:id/members + get ":id/members" do + group = find_group(params[:id]) + users = group.users + present users, with: Entities::GroupMember, group: group + end + + # Add a user to the list of group members + # + # Parameters: + # id (required) - group id + # user_id (required) - the users id + # access_level (required) - Project access level + # Example Request: + # POST /groups/:id/members + post ":id/members" do + group = find_group(params[:id]) + authorize! :admin_group, group + required_attributes! [:user_id, :access_level] + + unless validate_access_level?(params[:access_level]) + render_api_error!("Wrong access level", 422) + end + + if group.group_members.find_by(user_id: params[:user_id]) + render_api_error!("Already exists", 409) + end + + group.add_users([params[:user_id]], params[:access_level], current_user) + member = group.group_members.find_by(user_id: params[:user_id]) + present member.user, with: Entities::GroupMember, group: group + end + + # Update group member + # + # Parameters: + # id (required) - The ID of a group + # user_id (required) - The ID of a group member + # access_level (required) - Project access level + # Example Request: + # PUT /groups/:id/members/:user_id + put ':id/members/:user_id' do + group = find_group(params[:id]) + authorize! :admin_group, group + required_attributes! [:access_level] + + group_member = group.group_members.find_by(user_id: params[:user_id]) + not_found!('User can not be found') if group_member.nil? + + if group_member.update_attributes(access_level: params[:access_level]) + @member = group_member.user + present @member, with: Entities::GroupMember, group: group + else + handle_member_errors group_member.errors + end + end + + # Remove member. + # + # Parameters: + # id (required) - group id + # user_id (required) - the users id + # + # Example Request: + # DELETE /groups/:id/members/:user_id + delete ":id/members/:user_id" do + group = find_group(params[:id]) + authorize! :admin_group, group + member = group.group_members.find_by(user_id: params[:user_id]) + + if member.nil? + render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404) + else + member.destroy + end + end + end + end +end diff --git a/lib/api/groups.rb b/lib/api/groups.rb index caa2ca97a3..8cb9f92097 100644 --- a/lib/api/groups.rb +++ b/lib/api/groups.rb @@ -4,32 +4,19 @@ module API before { authenticate! } resource :groups do - helpers do - def find_group(id) - group = Group.find(id) - - if can?(current_user, :read_group, group) - group - else - render_api_error!("403 Forbidden - #{current_user.username} lacks sufficient access to #{group.name}", 403) - end - end - - def validate_access_level?(level) - Gitlab::Access.options_with_owner.values.include? level.to_i - end - end - # Get a groups list # # Example Request: # GET /groups get do - if current_user.admin - @groups = paginate Group - else - @groups = paginate current_user.groups - end + @groups = if current_user.admin + Group.all + else + current_user.groups + end + + @groups = @groups.search(params[:search]) if params[:search].present? + @groups = paginate @groups present @groups, with: Entities::Group end @@ -44,14 +31,14 @@ module API authenticated_as_admin! required_attributes! [:name, :path] - attrs = attributes_for_keys [:name, :path] + attrs = attributes_for_keys [:name, :path, :description] @group = Group.new(attrs) - @group.owner = current_user if @group.save + @group.add_owner(current_user) present @group, with: Entities::Group else - not_found! + render_api_error!("Failed to save group #{@group.errors.messages}", 400) end end @@ -74,7 +61,7 @@ module API # DELETE /groups/:id delete ":id" do group = find_group(params[:id]) - authorize! :manage_group, group + authorize! :admin_group, group group.destroy end @@ -94,58 +81,7 @@ module API if result present group else - not_found! - end - end - - # Get a list of group members viewable by the authenticated user. - # - # Example Request: - # GET /groups/:id/members - get ":id/members" do - group = find_group(params[:id]) - members = group.users_groups - users = (paginate members).collect(&:user) - present users, with: Entities::GroupMember, group: group - end - - # Add a user to the list of group members - # - # Parameters: - # id (required) - group id - # user_id (required) - the users id - # access_level (required) - Project access level - # Example Request: - # POST /groups/:id/members - post ":id/members" do - required_attributes! [:user_id, :access_level] - unless validate_access_level?(params[:access_level]) - render_api_error!("Wrong access level", 422) - end - group = find_group(params[:id]) - if group.users_groups.find_by(user_id: params[:user_id]) - render_api_error!("Already exists", 409) - end - group.add_users([params[:user_id]], params[:access_level]) - member = group.users_groups.find_by(user_id: params[:user_id]) - present member.user, with: Entities::GroupMember, group: group - end - - # Remove member. - # - # Parameters: - # id (required) - group id - # user_id (required) - the users id - # - # Example Request: - # DELETE /groups/:id/members/:user_id - delete ":id/members/:user_id" do - group = find_group(params[:id]) - member = group.users_groups.find_by(user_id: params[:user_id]) - if member.nil? - render_api_error!("404 Not Found - user_id:#{params[:user_id]} not a member of group #{group.name}",404) - else - member.destroy + render_api_error!("Failed to transfer project #{project.errors.messages}", 400) end end end diff --git a/lib/api/helpers.rb b/lib/api/helpers.rb index 6af0f6d1b2..be133a2920 100644 --- a/lib/api/helpers.rb +++ b/lib/api/helpers.rb @@ -11,7 +11,7 @@ module API def current_user private_token = (params[PRIVATE_TOKEN_PARAM] || env[PRIVATE_TOKEN_HEADER]).to_s - @current_user ||= User.find_by(authentication_token: private_token) + @current_user ||= (User.find_by(authentication_token: private_token) || doorkeeper_guard) unless @current_user && Gitlab::UserAccess.allowed?(@current_user) return nil @@ -20,7 +20,7 @@ module API identifier = sudo_identifier() # If the sudo is the current user do nothing - if (identifier && !(@current_user.id == identifier || @current_user.username == identifier)) + if identifier && !(@current_user.id == identifier || @current_user.username == identifier) render_api_error!('403 Forbidden: Must be admin to use sudo', 403) unless @current_user.is_admin? @current_user = User.by_username_or_id(identifier) not_found!("No user id or username for: #{identifier}") if @current_user.nil? @@ -33,7 +33,7 @@ module API identifier ||= params[SUDO_PARAM] ||= env[SUDO_HEADER] # Regex for integers - if (!!(identifier =~ /^[0-9]+$/)) + if !!(identifier =~ /^[0-9]+$/) identifier.to_i else identifier @@ -42,7 +42,7 @@ module API def user_project @project ||= find_project(params[:id]) - @project || not_found! + @project || not_found!("Project") end def find_project(id) @@ -55,6 +55,21 @@ module API end end + def find_group(id) + begin + group = Group.find(id) + rescue ActiveRecord::RecordNotFound + group = Group.find_by!(path: id) + end + + if can?(current_user, :read_group, group) + group + else + forbidden!("#{current_user.username} lacks sufficient "\ + "access to #{group.name}") + end + end + def paginate(relation) per_page = params[:per_page].to_i paginated = relation.page(params[:page]).per(per_page) @@ -67,11 +82,18 @@ module API unauthorized! unless current_user end + def authenticate_by_gitlab_shell_token! + input = params['secret_token'].try(:chomp) + unless Devise.secure_compare(secret_token, input) + unauthorized! + end + end + def authenticated_as_admin! forbidden! unless current_user.is_admin? end - def authorize! action, subject + def authorize!(action, subject) unless abilities.allowed?(current_user, action, subject) forbidden! end @@ -131,10 +153,32 @@ module API errors end + def validate_access_level?(level) + Gitlab::Access.options_with_owner.values.include? level.to_i + end + + def issuable_order_by + if params["order_by"] == 'updated_at' + 'updated_at' + else + 'created_at' + end + end + + def issuable_sort + if params["sort"] == 'asc' + :asc + else + :desc + end + end + # error helpers - def forbidden! - render_api_error!('403 Forbidden', 403) + def forbidden!(reason = nil) + message = ['403 Forbidden'] + message << " - #{reason}" if reason + render_api_error!(message.join(' '), 403) end def bad_request!(attribute) @@ -155,11 +199,21 @@ module API end def not_allowed! - render_api_error!('Method Not Allowed', 405) + render_api_error!('405 Method Not Allowed', 405) + end + + def conflict!(message = nil) + render_api_error!(message || '409 Conflict', 409) + end + + def render_validation_error!(model) + if model.errors.any? + render_api_error!(model.errors.messages || '400 Bad Request', 400) + end end def render_api_error!(message, status) - error!({'message' => message}, status) + error!({ 'message' => message }, status) end private @@ -183,5 +237,14 @@ module API abilities end end + + def secret_token + File.read(Rails.root.join('.gitlab_shell_secret')).chomp + end + + def handle_member_errors(errors) + error!(errors[:access_level], 422) if errors[:access_level].any? + not_found!(errors) + end end end diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 5850892df0..f98a17773e 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -1,6 +1,8 @@ module API # Internal access API class Internal < Grape::API + before { authenticate_by_gitlab_shell_token! } + namespace 'internal' do # Check if git command is allowed to project # @@ -12,33 +14,47 @@ module API # ref - branch name # forced_push - forced_push # - get "/allowed" do + post "/allowed" do + status 200 + + actor = + if params[:key_id] + Key.find_by(id: params[:key_id]) + elsif params[:user_id] + User.find_by(id: params[:user_id]) + end + + unless actor + return Gitlab::GitAccessStatus.new(false, 'No such user or key') + end + + project_path = params[:project] + # Check for *.wiki repositories. # Strip out the .wiki from the pathname before finding the # project. This applies the correct project permissions to # the wiki repository as well. - project_path = params[:project] - project_path.gsub!(/\.wiki/,'') if project_path =~ /\.wiki/ + wiki = project_path.end_with?('.wiki') + project_path.chomp!('.wiki') if wiki + project = Project.find_with_namespace(project_path) - return false unless project - actor = if params[:key_id] - Key.find(params[:key_id]) - elsif params[:user_id] - User.find(params[:user_id]) - end + if project + access = + if wiki + Gitlab::GitAccessWiki.new(actor, project) + else + Gitlab::GitAccess.new(actor, project) + end - return false unless actor + status = access.check(params[:action], params[:changes]) + end - Gitlab::GitAccess.new.allowed?( - actor, - params[:action], - project, - params[:ref], - params[:oldrev], - params[:newrev], - params[:forced_push] - ) + if project && access.can_read_project? + status + else + Gitlab::GitAccessStatus.new(false, 'No such project') + end end # @@ -56,6 +72,14 @@ module API gitlab_rev: Gitlab::REVISION, } end + + get "/broadcast_message" do + if message = BroadcastMessage.current + present message, with: Entities::BroadcastMessage + else + {} + end + end end end end diff --git a/lib/api/issues.rb b/lib/api/issues.rb index eb6a74cd2b..ff062be604 100644 --- a/lib/api/issues.rb +++ b/lib/api/issues.rb @@ -3,13 +3,46 @@ module API class Issues < Grape::API before { authenticate! } + helpers do + def filter_issues_state(issues, state) + case state + when 'opened' then issues.opened + when 'closed' then issues.closed + else issues + end + end + + def filter_issues_labels(issues, labels) + issues.includes(:labels).where('labels.title' => labels.split(',')) + end + + def filter_issues_milestone(issues, milestone) + issues.includes(:milestone).where('milestones.title' => milestone) + end + end + resource :issues do # Get currently authenticated user's issues # - # Example Request: + # Parameters: + # state (optional) - Return "opened" or "closed" issues + # labels (optional) - Comma-separated list of label names + # order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` + # sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` + # + # Example Requests: # GET /issues + # GET /issues?state=opened + # GET /issues?state=closed + # GET /issues?labels=foo + # GET /issues?labels=foo,bar + # GET /issues?labels=foo,bar&state=opened get do - present paginate(current_user.issues), with: Entities::Issue + issues = current_user.issues + issues = filter_issues_state(issues, params[:state]) unless params[:state].nil? + issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil? + issues.reorder(issuable_order_by => issuable_sort) + present paginate(issues), with: Entities::Issue end end @@ -18,10 +51,32 @@ module API # # Parameters: # id (required) - The ID of a project - # Example Request: + # state (optional) - Return "opened" or "closed" issues + # labels (optional) - Comma-separated list of label names + # milestone (optional) - Milestone title + # order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` + # sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` + # + # Example Requests: # GET /projects/:id/issues + # GET /projects/:id/issues?state=opened + # GET /projects/:id/issues?state=closed + # GET /projects/:id/issues?labels=foo + # GET /projects/:id/issues?labels=foo,bar + # GET /projects/:id/issues?labels=foo,bar&state=opened + # GET /projects/:id/issues?milestone=1.0.0 + # GET /projects/:id/issues?milestone=1.0.0&state=closed get ":id/issues" do - present paginate(user_project.issues), with: Entities::Issue + issues = user_project.issues + issues = filter_issues_state(issues, params[:state]) unless params[:state].nil? + issues = filter_issues_labels(issues, params[:labels]) unless params[:labels].nil? + + unless params[:milestone].nil? + issues = filter_issues_milestone(issues, params[:milestone]) + end + + issues.reorder(issuable_order_by => issuable_sort) + present paginate(issues), with: Entities::Issue end # Get a single project issue @@ -67,7 +122,7 @@ module API present issue, with: Entities::Issue else - not_found! + render_validation_error!(issue) end end @@ -107,7 +162,7 @@ module API present issue, with: Entities::Issue else - not_found! + render_validation_error!(issue) end end diff --git a/lib/api/labels.rb b/lib/api/labels.rb index 2fdf53ffec..78ca58ad0d 100644 --- a/lib/api/labels.rb +++ b/lib/api/labels.rb @@ -30,16 +30,14 @@ module API attrs = attributes_for_keys [:name, :color] label = user_project.find_label(attrs[:name]) - if label - return render_api_error!('Label already exists', 409) - end + conflict!('Label already exists') if label label = user_project.labels.create(attrs) if label.valid? present label, with: Entities::Label else - render_api_error!(label.errors.full_messages.join(', '), 400) + render_validation_error!(label) end end @@ -56,9 +54,7 @@ module API required_attributes! [:name] label = user_project.find_label(params[:name]) - if !label - return render_api_error!('Label not found', 404) - end + not_found!('Label') unless label label.destroy end @@ -66,10 +62,11 @@ module API # Updates an existing label. At least one optional parameter is required. # # Parameters: - # id (required) - The ID of a project - # name (optional) - The name of the label to be deleted - # color (optional) - Color of the label given in 6-digit hex - # notation with leading '#' sign (e.g. #FFAABB) + # id (required) - The ID of a project + # name (required) - The name of the label to be deleted + # new_name (optional) - The new name of the label + # color (optional) - Color of the label given in 6-digit hex + # notation with leading '#' sign (e.g. #FFAABB) # Example Request: # PUT /projects/:id/labels put ':id/labels' do @@ -77,16 +74,14 @@ module API required_attributes! [:name] label = user_project.find_label(params[:name]) - if !label - return render_api_error!('Label not found', 404) - end + not_found!('Label not found') unless label attrs = attributes_for_keys [:new_name, :color] if attrs.empty? - return render_api_error!('Required parameters "name" or "color" ' \ - 'missing', - 400) + render_api_error!('Required parameters "new_name" or "color" ' \ + 'missing', + 400) end # Rename new name to the actual label attribute name @@ -95,7 +90,7 @@ module API if label.update(attrs) present label, with: Entities::Label else - render_api_error!(label.errors.full_messages.join(', '), 400) + render_validation_error!(label) end end end diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb index 8726379bf3..f3765f5ab0 100644 --- a/lib/api/merge_requests.rb +++ b/lib/api/merge_requests.rb @@ -10,8 +10,13 @@ module API error!(errors[:project_access], 422) elsif errors[:branch_conflict].any? error!(errors[:branch_conflict], 422) + elsif errors[:validate_fork].any? + error!(errors[:validate_fork], 422) + elsif errors[:validate_branches].any? + conflict!(errors[:validate_branches]) end - not_found! + + render_api_error!(errors, 400) end end @@ -20,23 +25,32 @@ module API # Parameters: # id (required) - The ID of a project # state (optional) - Return requests "merged", "opened" or "closed" + # order_by (optional) - Return requests ordered by `created_at` or `updated_at` fields. Default is `created_at` + # sort (optional) - Return requests sorted in `asc` or `desc` order. Default is `desc` # # Example: # GET /projects/:id/merge_requests # GET /projects/:id/merge_requests?state=opened # GET /projects/:id/merge_requests?state=closed + # GET /projects/:id/merge_requests?order_by=created_at + # GET /projects/:id/merge_requests?order_by=updated_at + # GET /projects/:id/merge_requests?sort=desc + # GET /projects/:id/merge_requests?sort=asc # get ":id/merge_requests" do authorize! :read_merge_request, user_project + merge_requests = user_project.merge_requests - mrs = case params["state"] - when "opened" then user_project.merge_requests.opened - when "closed" then user_project.merge_requests.closed - when "merged" then user_project.merge_requests.merged - else user_project.merge_requests - end + merge_requests = + case params["state"] + when "opened" then merge_requests.opened + when "closed" then merge_requests.closed + when "merged" then merge_requests.merged + else merge_requests + end - present paginate(mrs), with: Entities::MergeRequest + merge_requests.reorder(issuable_order_by => issuable_sort) + present paginate(merge_requests), with: Entities::MergeRequest end # Show MR @@ -56,6 +70,22 @@ module API present merge_request, with: Entities::MergeRequest end + # Show MR changes + # + # Parameters: + # id (required) - The ID of a project + # merge_request_id (required) - The ID of MR + # + # Example: + # GET /projects/:id/merge_request/:merge_request_id/changes + # + get ':id/merge_request/:merge_request_id/changes' do + merge_request = user_project.merge_requests. + find(params[:merge_request_id]) + authorize! :read_merge_request, merge_request + present merge_request, with: Entities::MergeRequestChanges + end + # Create MR # # Parameters: @@ -148,13 +178,10 @@ module API put ":id/merge_request/:merge_request_id/merge" do merge_request = user_project.merge_requests.find(params[:merge_request_id]) - action = if user_project.protected_branch?(merge_request.target_branch) - :push_code_to_protected_branches - else - :push_code - end + allowed = ::Gitlab::GitAccess.new(current_user, user_project). + can_push_to_branch?(merge_request.target_branch) - if can?(current_user, action, user_project) + if allowed if merge_request.unchecked? merge_request.check_if_can_be_merged end @@ -214,7 +241,7 @@ module API if note.save present note, with: Entities::MRNote else - not_found! + render_api_error!("Failed to save note #{note.errors.messages}", 400) end end end diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb index a4fdb752d6..c5cd73943f 100644 --- a/lib/api/milestones.rb +++ b/lib/api/milestones.rb @@ -48,7 +48,7 @@ module API if milestone.valid? present milestone, with: Entities::Milestone else - not_found! + render_api_error!("Failed to create milestone #{milestone.errors.messages}", 400) end end @@ -72,9 +72,24 @@ module API if milestone.valid? present milestone, with: Entities::Milestone else - not_found! + render_api_error!("Failed to update milestone #{milestone.errors.messages}", 400) end end + + # Get all issues for a single project milestone + # + # Parameters: + # id (required) - The ID of a project + # milestone_id (required) - The ID of a project milestone + # Example Request: + # GET /projects/:id/milestones/:milestone_id/issues + get ":id/milestones/:milestone_id/issues" do + authorize! :read_milestone, user_project + + @milestone = user_project.milestones.find(params[:milestone_id]) + present paginate(@milestone.issues), with: Entities::Issue + end + end end end diff --git a/lib/api/namespaces.rb b/lib/api/namespaces.rb index f9f2ed90cc..b90ed6af5f 100644 --- a/lib/api/namespaces.rb +++ b/lib/api/namespaces.rb @@ -1,10 +1,10 @@ module API # namespaces API class Namespaces < Grape::API - before { + before do authenticate! authenticated_as_admin! - } + end resource :namespaces do # Get a namespaces list diff --git a/lib/api/notes.rb b/lib/api/notes.rb index 0ef9a3c4be..3726be7c53 100644 --- a/lib/api/notes.rb +++ b/lib/api/notes.rb @@ -61,9 +61,42 @@ module API if @note.valid? present @note, with: Entities::Note else - not_found! + not_found!("Note #{@note.errors.messages}") end end + + # Modify existing +noteable+ note + # + # Parameters: + # id (required) - The ID of a project + # noteable_id (required) - The ID of an issue or snippet + # node_id (required) - The ID of a note + # body (required) - New content of a note + # Example Request: + # PUT /projects/:id/issues/:noteable_id/notes/:note_id + # PUT /projects/:id/snippets/:noteable_id/notes/:node_id + put ":id/#{noteables_str}/:#{noteable_id_str}/notes/:note_id" do + required_attributes! [:body] + + authorize! :admin_note, user_project.notes.find(params[:note_id]) + + opts = { + note: params[:body], + note_id: params[:note_id], + noteable_type: noteables_str.classify, + noteable_id: params[noteable_id_str] + } + + @note = ::Notes::UpdateService.new(user_project, current_user, + opts).execute + + if @note.valid? + present @note, with: Entities::Note + else + render_api_error!("Failed to save note #{note.errors.messages}", 400) + end + end + end end end diff --git a/lib/api/project_hooks.rb b/lib/api/project_hooks.rb index 79c3d122d3..be9850367b 100644 --- a/lib/api/project_hooks.rb +++ b/lib/api/project_hooks.rb @@ -38,7 +38,13 @@ module API # POST /projects/:id/hooks post ":id/hooks" do required_attributes! [:url] - attrs = attributes_for_keys [:url, :push_events, :issues_events, :merge_requests_events] + attrs = attributes_for_keys [ + :url, + :push_events, + :issues_events, + :merge_requests_events, + :tag_push_events + ] @hook = user_project.hooks.new(attrs) if @hook.save @@ -47,7 +53,7 @@ module API if @hook.errors[:url].present? error!("Invalid url given", 422) end - not_found! + not_found!("Project hook #{@hook.errors.messages}") end end @@ -62,7 +68,13 @@ module API put ":id/hooks/:hook_id" do @hook = user_project.hooks.find(params[:hook_id]) required_attributes! [:url] - attrs = attributes_for_keys [:url, :push_events, :issues_events, :merge_requests_events] + attrs = attributes_for_keys [ + :url, + :push_events, + :issues_events, + :merge_requests_events, + :tag_push_events + ] if @hook.update_attributes attrs present @hook, with: Entities::ProjectHook @@ -70,7 +82,7 @@ module API if @hook.errors[:url].present? error!("Invalid url given", 422) end - not_found! + not_found!("Project hook #{@hook.errors.messages}") end end diff --git a/lib/api/project_members.rb b/lib/api/project_members.rb index 47c4ddce16..c756bb479f 100644 --- a/lib/api/project_members.rb +++ b/lib/api/project_members.rb @@ -4,14 +4,6 @@ module API before { authenticate! } resource :projects do - helpers do - def handle_project_member_errors(errors) - if errors[:project_access].any? - error!(errors[:project_access], 422) - end - not_found! - end - end # Get a project team members # @@ -54,19 +46,19 @@ module API required_attributes! [:user_id, :access_level] # either the user is already a team member or a new one - team_member = user_project.team_member_by_id(params[:user_id]) - if team_member.nil? - team_member = user_project.users_projects.new( + project_member = user_project.project_member_by_id(params[:user_id]) + if project_member.nil? + project_member = user_project.project_members.new( user_id: params[:user_id], - project_access: params[:access_level] + access_level: params[:access_level] ) end - if team_member.save - @member = team_member.user + if project_member.save + @member = project_member.user present @member, with: Entities::ProjectMember, project: user_project else - handle_project_member_errors team_member.errors + handle_member_errors project_member.errors end end @@ -82,14 +74,14 @@ module API authorize! :admin_project, user_project required_attributes! [:access_level] - team_member = user_project.users_projects.find_by(user_id: params[:user_id]) - not_found!("User can not be found") if team_member.nil? + project_member = user_project.project_members.find_by(user_id: params[:user_id]) + not_found!("User can not be found") if project_member.nil? - if team_member.update_attributes(project_access: params[:access_level]) - @member = team_member.user + if project_member.update_attributes(access_level: params[:access_level]) + @member = project_member.user present @member, with: Entities::ProjectMember, project: user_project else - handle_project_member_errors team_member.errors + handle_member_errors project_member.errors end end @@ -102,11 +94,11 @@ module API # DELETE /projects/:id/members/:user_id delete ":id/members/:user_id" do authorize! :admin_project, user_project - team_member = user_project.users_projects.find_by(user_id: params[:user_id]) - unless team_member.nil? - team_member.destroy + project_member = user_project.project_members.find_by(user_id: params[:user_id]) + unless project_member.nil? + project_member.destroy else - {message: "Access revoked", id: params[:user_id].to_i} + { message: "Access revoked", id: params[:user_id].to_i } end end end diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb index 8e09fff684..54f2555903 100644 --- a/lib/api/project_snippets.rb +++ b/lib/api/project_snippets.rb @@ -42,21 +42,22 @@ module API # title (required) - The title of a snippet # file_name (required) - The name of a snippet file # code (required) - The content of a snippet + # visibility_level (required) - The snippet's visibility # Example Request: # POST /projects/:id/snippets post ":id/snippets" do authorize! :write_project_snippet, user_project - required_attributes! [:title, :file_name, :code] + required_attributes! [:title, :file_name, :code, :visibility_level] - attrs = attributes_for_keys [:title, :file_name] + attrs = attributes_for_keys [:title, :file_name, :visibility_level] attrs[:content] = params[:code] if params[:code].present? - @snippet = user_project.snippets.new attrs - @snippet.author = current_user + @snippet = CreateSnippetService.new(user_project, current_user, + attrs).execute - if @snippet.save - present @snippet, with: Entities::ProjectSnippet + if @snippet.errors.any? + render_validation_error!(@snippet) else - not_found! + present @snippet, with: Entities::ProjectSnippet end end @@ -68,19 +69,22 @@ module API # title (optional) - The title of a snippet # file_name (optional) - The name of a snippet file # code (optional) - The content of a snippet + # visibility_level (optional) - The snippet's visibility # Example Request: # PUT /projects/:id/snippets/:snippet_id put ":id/snippets/:snippet_id" do @snippet = user_project.snippets.find(params[:snippet_id]) authorize! :modify_project_snippet, @snippet - attrs = attributes_for_keys [:title, :file_name] + attrs = attributes_for_keys [:title, :file_name, :visibility_level] attrs[:content] = params[:code] if params[:code].present? - if @snippet.update_attributes attrs - present @snippet, with: Entities::ProjectSnippet + UpdateSnippetService.new(user_project, current_user, @snippet, + attrs).execute + if @snippet.errors.any? + render_validation_error!(@snippet) else - not_found! + present @snippet, with: Entities::ProjectSnippet end end @@ -97,6 +101,7 @@ module API authorize! :modify_project_snippet, @snippet @snippet.destroy rescue + not_found!('Snippet') end end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index 55f7975bbf..e3fff79d68 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -11,23 +11,46 @@ module API attrs[:visibility_level] = Gitlab::VisibilityLevel::PUBLIC if !attrs[:visibility_level].present? && publik == true attrs end + + def filter_projects(projects) + # If the archived parameter is passed, limit results accordingly + if params[:archived].present? + projects = projects.where(archived: parse_boolean(params[:archived])) + end + + if params[:search].present? + projects = projects.search(params[:search]) + end + + projects.reorder(project_order_by => project_sort) + end + + def project_order_by + order_fields = %w(id name path created_at updated_at last_activity_at) + + if order_fields.include?(params['order_by']) + params['order_by'] + else + 'created_at' + end + end + + def project_sort + if params["sort"] == 'asc' + :asc + else + :desc + end + end end # Get a projects list for authenticated user # - # Parameters: - # archived (optional) - if passed, limit by archived status - # # Example Request: # GET /projects get do @projects = current_user.authorized_projects - - # If the archived parameter is passed, limit results accordingly - if params[:archived].present? - @projects = @projects.where(archived: parse_boolean(params[:archived])) - end - + @projects = filter_projects(@projects) @projects = paginate @projects present @projects, with: Entities::Project end @@ -37,7 +60,9 @@ module API # Example Request: # GET /projects/owned get '/owned' do - @projects = paginate current_user.owned_projects + @projects = current_user.owned_projects + @projects = filter_projects(@projects) + @projects = paginate @projects present @projects, with: Entities::Project end @@ -47,7 +72,9 @@ module API # GET /projects/all get '/all' do authenticated_as_admin! - @projects = paginate Project + @projects = Project.all + @projects = filter_projects(@projects) + @projects = paginate @projects present @projects, with: Entities::Project end @@ -61,17 +88,14 @@ module API present user_project, with: Entities::ProjectWithAccess, user: current_user end - # Get a single project events + # Get events for a single project # # Parameters: # id (required) - The ID of a project # Example Request: - # GET /projects/:id + # GET /projects/:id/events get ":id/events" do - limit = (params[:per_page] || 20).to_i - offset = (params[:page] || 0).to_i * limit - events = user_project.events.recent.limit(limit).offset(offset) - + events = paginate user_project.events.recent present events, with: Entities::Event end @@ -111,7 +135,7 @@ module API if @project.errors[:limit_reached].present? error!(@project.errors[:limit_reached], 403) end - not_found! + render_validation_error!(@project) end end @@ -149,7 +173,67 @@ module API if @project.saved? present @project, with: Entities::Project else - not_found! + render_validation_error!(@project) + end + end + + # Fork new project for the current user. + # + # Parameters: + # id (required) - The ID of a project + # Example Request + # POST /projects/fork/:id + post 'fork/:id' do + @forked_project = + ::Projects::ForkService.new(user_project, + current_user).execute + if @forked_project.errors.any? + conflict!(@forked_project.errors.messages) + else + present @forked_project, with: Entities::Project + end + end + + # Update an existing project + # + # Parameters: + # id (required) - the id of a project + # name (optional) - name of a project + # path (optional) - path of a project + # description (optional) - short project description + # issues_enabled (optional) + # merge_requests_enabled (optional) + # wiki_enabled (optional) + # snippets_enabled (optional) + # public (optional) - if true same as setting visibility_level = 20 + # visibility_level (optional) - visibility level of a project + # Example Request + # PUT /projects/:id + put ':id' do + attrs = attributes_for_keys [:name, + :path, + :description, + :default_branch, + :issues_enabled, + :merge_requests_enabled, + :wiki_enabled, + :snippets_enabled, + :public, + :visibility_level] + attrs = map_public_to_visibility_level(attrs) + authorize_admin_project + authorize! :rename_project, user_project if attrs[:name].present? + if attrs[:visibility_level].present? + authorize! :change_visibility_level, user_project + end + + ::Projects::UpdateService.new(user_project, + current_user, attrs).execute + + if user_project.errors.any? + render_validation_error!(user_project) + else + present user_project, with: Entities::Project end end @@ -161,7 +245,7 @@ module API # DELETE /projects/:id delete ":id" do authorize! :remove_project, user_project - user_project.destroy + ::Projects::DestroyService.new(user_project, current_user, {}).execute end # Mark this project as forked from another @@ -181,7 +265,7 @@ module API render_api_error!("Project already forked", 409) end else - not_found! + not_found!("Source Project") end end @@ -210,6 +294,16 @@ module API ids = current_user.authorized_projects.map(&:id) visibility_levels = [ Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC ] projects = Project.where("(id in (?) OR visibility_level in (?)) AND (name LIKE (?))", ids, visibility_levels, "%#{params[:query]}%") + sort = params[:sort] == 'desc' ? 'desc' : 'asc' + + projects = case params["order_by"] + when 'id' then projects.order("id #{sort}") + when 'name' then projects.order("name #{sort}") + when 'created_at' then projects.order("created_at #{sort}") + when 'last_activity_at' then projects.order("last_activity_at #{sort}") + else projects + end + present paginate(projects), with: Entities::Project end diff --git a/lib/api/repositories.rb b/lib/api/repositories.rb index 42068bb343..1fbf3dca3c 100644 --- a/lib/api/repositories.rb +++ b/lib/api/repositories.rb @@ -23,7 +23,8 @@ module API # Example Request: # GET /projects/:id/repository/tags get ":id/repository/tags" do - present user_project.repo.tags.sort_by(&:name).reverse, with: Entities::RepoObject, project: user_project + present user_project.repo.tags.sort_by(&:name).reverse, + with: Entities::RepoTag, project: user_project end # Create tag @@ -32,14 +33,22 @@ module API # id (required) - The ID of a project # tag_name (required) - The name of the tag # ref (required) - Create tag from commit sha or branch + # message (optional) - Specifying a message creates an annotated tag. # Example Request: # POST /projects/:id/repository/tags post ':id/repository/tags' do authorize_push_project - @tag = CreateTagService.new.execute(user_project, params[:tag_name], - params[:ref], current_user) + message = params[:message] || nil + result = CreateTagService.new(user_project, current_user). + execute(params[:tag_name], params[:ref], message) - present @tag, with: Entities::RepoObject, project: user_project + if result[:status] == :success + present result[:tag], + with: Entities::RepoTag, + project: user_project + else + render_api_error!(result[:message], 400) + end end # Get a project repository tree @@ -49,11 +58,13 @@ module API # ref_name (optional) - The name of a repository branch or tag, if not given the default branch is used # Example Request: # GET /projects/:id/repository/tree - get ":id/repository/tree" do + get ':id/repository/tree' do ref = params[:ref_name] || user_project.try(:default_branch) || 'master' path = params[:path] || nil commit = user_project.repository.commit(ref) + not_found!('Tree') unless commit + tree = user_project.repository.tree(commit.id, path) present tree.sorted_entries, with: Entities::RepoTreeObject @@ -91,14 +102,18 @@ module API # sha (required) - The blob's sha # Example Request: # GET /projects/:id/repository/raw_blobs/:sha - get ":id/repository/raw_blobs/:sha" do + get ':id/repository/raw_blobs/:sha' do ref = params[:sha] repo = user_project.repository - blob = Gitlab::Git::Blob.raw(repo, ref) + begin + blob = Gitlab::Git::Blob.raw(repo, ref) + rescue + not_found! 'Blob' + end - not_found! "Blob" unless blob + not_found! 'Blob' unless blob env['api.format'] = :txt @@ -113,18 +128,29 @@ module API # sha (optional) - the commit sha to download defaults to the tip of the default branch # Example Request: # GET /projects/:id/repository/archive - get ":id/repository/archive", requirements: { format: Gitlab::Regex.archive_formats_regex } do + get ':id/repository/archive', + requirements: { format: Gitlab::Regex.archive_formats_regex } do authorize! :download_code, user_project - file_path = ArchiveRepositoryService.new.execute(user_project, params[:sha], params[:format]) + + begin + file_path = ArchiveRepositoryService.new( + user_project, + params[:sha], + params[:format] + ).execute + rescue + not_found!('File') + end if file_path && File.exists?(file_path) data = File.open(file_path, 'rb').read - header["Content-Disposition"] = "attachment; filename=\"#{File.basename(file_path)}\"" + basename = File.basename(file_path) + header['Content-Disposition'] = "attachment; filename=\"#{basename}\"" content_type MIME::Types.type_for(file_path).first.content_type env['api.format'] = :binary present data else - not_found! + redirect request.fullpath end end @@ -152,7 +178,12 @@ module API get ':id/repository/contributors' do authorize! :download_code, user_project - present user_project.repository.contributors, with: Entities::Contributor + begin + present user_project.repository.contributors, + with: Entities::Contributor + rescue + not_found! + end end end end diff --git a/lib/api/services.rb b/lib/api/services.rb index bde502e32e..3ad59cf3ad 100644 --- a/lib/api/services.rb +++ b/lib/api/services.rb @@ -28,7 +28,7 @@ module API # Delete GitLab CI service settings # # Example Request: - # DELETE /projects/:id/keys/:id + # DELETE /projects/:id/services/gitlab-ci delete ":id/services/gitlab-ci" do if user_project.gitlab_ci_service user_project.gitlab_ci_service.update_attributes( @@ -38,7 +38,41 @@ module API ) end end + + # Set Hipchat service for project + # + # Parameters: + # token (required) - Hipchat token + # room (required) - Hipchat room name + # + # Example Request: + # PUT /projects/:id/services/hipchat + put ':id/services/hipchat' do + required_attributes! [:token, :room] + attrs = attributes_for_keys [:token, :room] + user_project.build_missing_services + + if user_project.hipchat_service.update_attributes( + attrs.merge(active: true)) + true + else + not_found! + end + end + + # Delete Hipchat service settings + # + # Example Request: + # DELETE /projects/:id/services/hipchat + delete ':id/services/hipchat' do + if user_project.hipchat_service + user_project.hipchat_service.update_attributes( + active: false, + token: nil, + room: nil + ) + end + end end end end - diff --git a/lib/api/system_hooks.rb b/lib/api/system_hooks.rb index 3e239c5afe..518964db50 100644 --- a/lib/api/system_hooks.rb +++ b/lib/api/system_hooks.rb @@ -1,10 +1,10 @@ module API # Hooks API class SystemHooks < Grape::API - before { + before do authenticate! authenticated_as_admin! - } + end resource :hooks do # Get the list of system hooks diff --git a/lib/api/users.rb b/lib/api/users.rb index 69553f1639..032a5d76e4 100644 --- a/lib/api/users.rb +++ b/lib/api/users.rb @@ -42,7 +42,8 @@ module API # Parameters: # email (required) - Email # password (required) - Password - # name - Name + # name (required) - Name + # username (required) - Name # skype - Skype ID # linkedin - Linkedin # twitter - Twitter account @@ -53,19 +54,36 @@ module API # bio - Bio # admin - User is admin - true or false (default) # can_create_group - User can create groups - true or false + # confirm - Require user confirmation - true (default) or false # Example Request: # POST /users post do authenticated_as_admin! required_attributes! [:email, :password, :name, :username] - attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin] - user = User.build_user(attrs) + attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :projects_limit, :username, :bio, :can_create_group, :admin, :confirm] admin = attrs.delete(:admin) + 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 + + identity_attrs = attributes_for_keys [:provider, :extern_uid] + if identity_attrs.any? + user.identities.build(identity_attrs) + end + if user.save present user, with: Entities::UserFull else - not_found! + conflict!('Email has already been taken') if User. + where(email: user.email). + count > 0 + + conflict!('Username has already been taken') if User. + where(username: user.username). + count > 0 + + render_validation_error!(user) end end @@ -80,8 +98,6 @@ module API # twitter - Twitter account # website_url - Website url # projects_limit - Limit projects each user can create - # extern_uid - External authentication provider UID - # provider - External provider # bio - Bio # admin - User is admin - true or false (default) # can_create_group - User can create groups - true or false @@ -90,16 +106,25 @@ module API put ":id" do authenticated_as_admin! - attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :extern_uid, :provider, :bio, :can_create_group, :admin] + attrs = attributes_for_keys [:email, :name, :password, :skype, :linkedin, :twitter, :website_url, :projects_limit, :username, :bio, :can_create_group, :admin] user = User.find(params[:id]) - not_found!("User not found") unless user + not_found!('User') unless user admin = attrs.delete(:admin) user.admin = admin unless admin.nil? + + conflict!('Email has already been taken') if attrs[:email] && + User.where(email: attrs[:email]). + where.not(id: user.id).count > 0 + + conflict!('Username has already been taken') if attrs[:username] && + User.where(username: attrs[:username]). + where.not(id: user.id).count > 0 + if user.update_attributes(attrs) present user, with: Entities::UserFull else - not_found! + render_validation_error!(user) end end @@ -113,13 +138,15 @@ module API # POST /users/:id/keys post ":id/keys" do authenticated_as_admin! + required_attributes! [:title, :key] + user = User.find(params[:id]) attrs = attributes_for_keys [:title, :key] key = user.keys.new attrs if key.save present key, with: Entities::SSHKey else - not_found! + render_validation_error!(key) end end @@ -132,11 +159,9 @@ module API get ':uid/keys' do authenticated_as_admin! user = User.find_by(id: params[:uid]) - if user - present user.keys, with: Entities::SSHKey - else - not_found! - end + not_found!('User') unless user + + present user.keys, with: Entities::SSHKey end # Delete existing ssh key of a specified user. Only available to admin @@ -150,15 +175,13 @@ module API delete ':uid/keys/:id' do authenticated_as_admin! user = User.find_by(id: params[:uid]) - if user - begin - key = user.keys.find params[:id] - key.destroy - rescue ActiveRecord::RecordNotFound - not_found! - end - else - not_found! + not_found!('User') unless user + + begin + key = user.keys.find params[:id] + key.destroy + rescue ActiveRecord::RecordNotFound + not_found!('Key') end end @@ -173,7 +196,7 @@ module API if user user.destroy else - not_found! + not_found!('User') end end end @@ -219,7 +242,7 @@ module API if key.save present key, with: Entities::SSHKey else - not_found! + render_validation_error!(key) end end diff --git a/lib/backup/database.rb b/lib/backup/database.rb index 7b6908ccad..9ab6aca276 100644 --- a/lib/backup/database.rb +++ b/lib/backup/database.rb @@ -13,30 +13,33 @@ module Backup def dump success = case config["adapter"] when /^mysql/ then - print "Dumping MySQL database #{config['database']} ... " + $progress.print "Dumping MySQL database #{config['database']} ... " system('mysqldump', *mysql_args, config['database'], out: db_file_name) when "postgresql" then - print "Dumping PostgreSQL database #{config['database']} ... " + $progress.print "Dumping PostgreSQL database #{config['database']} ... " pg_env system('pg_dump', config['database'], out: db_file_name) end report_success(success) + abort 'Backup failed' unless success end def restore success = case config["adapter"] when /^mysql/ then - print "Restoring MySQL database #{config['database']} ... " + $progress.print "Restoring MySQL database #{config['database']} ... " system('mysql', *mysql_args, config['database'], in: db_file_name) when "postgresql" then - print "Restoring PostgreSQL database #{config['database']} ... " + $progress.print "Restoring PostgreSQL database #{config['database']} ... " # Drop all tables because PostgreSQL DB dumps do not contain DROP TABLE # statements like MySQL. Rake::Task["gitlab:db:drop_all_tables"].invoke + Rake::Task["gitlab:db:drop_all_postgres_sequences"].invoke pg_env system('psql', config['database'], '-f', db_file_name) end report_success(success) + abort 'Restore failed' unless success end protected @@ -66,9 +69,9 @@ module Backup def report_success(success) if success - puts '[DONE]'.green + $progress.puts '[DONE]'.green else - puts '[FAILED]'.red + $progress.puts '[FAILED]'.red end end end diff --git a/lib/backup/manager.rb b/lib/backup/manager.rb index 28e323fe30..b69aebf9fe 100644 --- a/lib/backup/manager.rb +++ b/lib/backup/manager.rb @@ -1,7 +1,5 @@ module Backup class Manager - BACKUP_CONTENTS = %w{repositories/ db/ uploads/ backup_information.yml} - def pack # saving additional informations s = {} @@ -9,51 +7,91 @@ module Backup s[:backup_created_at] = Time.now s[:gitlab_version] = Gitlab::VERSION s[:tar_version] = tar_version + s[:skipped] = ENV["SKIP"] + tar_file = "#{s[:backup_created_at].to_i}_gitlab_backup.tar" - Dir.chdir(Gitlab.config.backup.path) + Dir.chdir(Gitlab.config.backup.path) do + File.open("#{Gitlab.config.backup.path}/backup_information.yml", + "w+") do |file| + file << s.to_yaml.gsub(/^---\n/,'') + end - File.open("#{Gitlab.config.backup.path}/backup_information.yml", "w+") do |file| - file << s.to_yaml.gsub(/^---\n/,'') + FileUtils.chmod(0700, folders_to_backup) + + # create archive + $progress.print "Creating backup archive: #{tar_file} ... " + orig_umask = File.umask(0077) + if Kernel.system('tar', '-cf', tar_file, *backup_contents) + $progress.puts "done".green + else + puts "creating archive #{tar_file} failed".red + abort 'Backup failed' + end + File.umask(orig_umask) + + upload(tar_file) + end + end + + def upload(tar_file) + remote_directory = Gitlab.config.backup.upload.remote_directory + $progress.print "Uploading backup archive to remote storage #{remote_directory} ... " + + connection_settings = Gitlab.config.backup.upload.connection + if connection_settings.blank? + $progress.puts "skipped".yellow + return end - # create archive - print "Creating backup archive: #{s[:backup_created_at].to_i}_gitlab_backup.tar ... " - if Kernel.system('tar', '-cf', "#{s[:backup_created_at].to_i}_gitlab_backup.tar", *BACKUP_CONTENTS) - puts "done".green + connection = ::Fog::Storage.new(connection_settings) + directory = connection.directories.get(remote_directory) + + if directory.files.create(key: tar_file, body: File.open(tar_file), public: false) + $progress.puts "done".green else - puts "failed".red + puts "uploading backup to #{remote_directory} failed".red + abort 'Backup failed' end end def cleanup - print "Deleting tmp directories ... " - if Kernel.system('rm', '-rf', *BACKUP_CONTENTS) - puts "done".green - else - puts "failed".red + $progress.print "Deleting tmp directories ... " + + backup_contents.each do |dir| + next unless File.exist?(File.join(Gitlab.config.backup.path, dir)) + + if FileUtils.rm_rf(File.join(Gitlab.config.backup.path, dir)) + $progress.puts "done".green + else + puts "deleting tmp directory '#{dir}' failed".red + abort 'Backup failed' + end end end def remove_old # delete backups - print "Deleting old backups ... " + $progress.print "Deleting old backups ... " keep_time = Gitlab.config.backup.keep_time.to_i - path = Gitlab.config.backup.path if keep_time > 0 removed = 0 - file_list = Dir.glob(Rails.root.join(path, "*_gitlab_backup.tar")) - file_list.map! { |f| $1.to_i if f =~ /(\d+)_gitlab_backup.tar/ } - file_list.sort.each do |timestamp| - if Time.at(timestamp) < (Time.now - keep_time) - if Kernel.system(*%W(rm #{timestamp}_gitlab_backup.tar)) - removed += 1 + + Dir.chdir(Gitlab.config.backup.path) do + file_list = Dir.glob('*_gitlab_backup.tar') + file_list.map! { |f| $1.to_i if f =~ /(\d+)_gitlab_backup.tar/ } + file_list.sort.each do |timestamp| + if Time.at(timestamp) < (Time.now - keep_time) + if Kernel.system(*%W(rm #{timestamp}_gitlab_backup.tar)) + removed += 1 + end end end end - puts "done. (#{removed} removed)".green + + $progress.puts "done. (#{removed} removed)".green else - puts "skipping".yellow + $progress.puts "skipping".yellow end end @@ -63,6 +101,7 @@ module Backup # check for existing backups in the backup dir file_list = Dir.glob("*_gitlab_backup.tar").each.map { |f| f.split(/_/).first.to_i } puts "no backups found" if file_list.count == 0 + if file_list.count > 1 && ENV["BACKUP"].nil? puts "Found more than one backup, please specify which one you want to restore:" puts "rake gitlab:backup:restore BACKUP=timestamp_of_backup" @@ -76,15 +115,15 @@ module Backup exit 1 end - print "Unpacking backup ... " + $progress.print "Unpacking backup ... " + unless Kernel.system(*%W(tar -xf #{tar_file})) - puts "failed".red + puts "unpacking backup failed".red exit 1 else - puts "done".green + $progress.puts "done".green end - settings = YAML.load_file("backup_information.yml") ENV["VERSION"] = "#{settings[:db_version]}" if settings[:db_version].to_i > 0 # restoring mismatching backups can lead to unexpected problems @@ -103,5 +142,29 @@ module Backup tar_version, _ = Gitlab::Popen.popen(%W(tar --version)) tar_version.force_encoding('locale').split("\n").first end + + def skipped?(item) + settings[:skipped] && settings[:skipped].include?(item) + end + + private + + def backup_contents + folders_to_backup + ["backup_information.yml"] + end + + def folders_to_backup + folders = %w{repositories db uploads} + + if ENV["SKIP"] + return folders.reject{ |folder| ENV["SKIP"].include?(folder) } + end + + folders + end + + def settings + @settings ||= YAML.load_file("backup_information.yml") + end end end diff --git a/lib/backup/repository.rb b/lib/backup/repository.rb index 6f7c4f7c90..dfb2da9f84 100644 --- a/lib/backup/repository.rb +++ b/lib/backup/repository.rb @@ -8,29 +8,42 @@ module Backup prepare Project.find_each(batch_size: 1000) do |project| - print " * #{project.path_with_namespace} ... " + $progress.print " * #{project.path_with_namespace} ... " # Create namespace dir if missing FileUtils.mkdir_p(File.join(backup_repos_path, project.namespace.path)) if project.namespace if project.empty_repo? - puts "[SKIPPED]".cyan - elsif system(*%W(git --git-dir=#{path_to_repo(project)} bundle create #{path_to_bundle(project)} --all), silent) - puts "[DONE]".green + $progress.puts "[SKIPPED]".cyan else - puts "[FAILED]".red + cmd = %W(tar -cf #{path_to_bundle(project)} -C #{path_to_repo(project)} .) + output, status = Gitlab::Popen.popen(cmd) + if status.zero? + $progress.puts "[DONE]".green + else + puts "[FAILED]".red + puts "failed: #{cmd.join(' ')}" + puts output + abort 'Backup failed' + end end wiki = ProjectWiki.new(project) if File.exists?(path_to_repo(wiki)) - print " * #{wiki.path_with_namespace} ... " - if wiki.empty? - puts " [SKIPPED]".cyan - elsif system(*%W(git --git-dir=#{path_to_repo(wiki)} bundle create #{path_to_bundle(wiki)} --all), silent) - puts " [DONE]".green + $progress.print " * #{wiki.path_with_namespace} ... " + if wiki.repository.empty? + $progress.puts " [SKIPPED]".cyan else - puts " [FAILED]".red + cmd = %W(git --git-dir=#{path_to_repo(wiki)} bundle create #{path_to_bundle(wiki)} --all) + output, status = Gitlab::Popen.popen(cmd) + if status.zero? + $progress.puts " [DONE]".green + else + puts " [FAILED]".red + puts "failed: #{cmd.join(' ')}" + abort 'Backup failed' + end end end end @@ -46,33 +59,53 @@ module Backup FileUtils.mkdir_p(repos_path) Project.find_each(batch_size: 1000) do |project| - print "#{project.path_with_namespace} ... " + $progress.print " * #{project.path_with_namespace} ... " project.namespace.ensure_dir_exist if project.namespace - if system(*%W(git clone --bare #{path_to_bundle(project)} #{path_to_repo(project)}), silent) - puts "[DONE]".green + if File.exists?(path_to_bundle(project)) + FileUtils.mkdir_p(path_to_repo(project)) + cmd = %W(tar -xf #{path_to_bundle(project)} -C #{path_to_repo(project)}) + else + cmd = %W(git init --bare #{path_to_repo(project)}) + end + + if system(*cmd, silent) + $progress.puts "[DONE]".green else puts "[FAILED]".red + puts "failed: #{cmd.join(' ')}" + abort 'Restore failed' end wiki = ProjectWiki.new(project) if File.exists?(path_to_bundle(wiki)) - print " * #{wiki.path_with_namespace} ... " - if system(*%W(git clone --bare #{path_to_bundle(wiki)} #{path_to_repo(wiki)}), silent) - puts " [DONE]".green + $progress.print " * #{wiki.path_with_namespace} ... " + + # If a wiki bundle exists, first remove the empty repo + # that was initialized with ProjectWiki.new() and then + # try to restore with 'git clone --bare'. + FileUtils.rm_rf(path_to_repo(wiki)) + cmd = %W(git clone --bare #{path_to_bundle(wiki)} #{path_to_repo(wiki)}) + + if system(*cmd, silent) + $progress.puts " [DONE]".green else puts " [FAILED]".red + puts "failed: #{cmd.join(' ')}" + abort 'Restore failed' end end end - print 'Put GitLab hooks in repositories dirs'.yellow - if system("#{Gitlab.config.gitlab_shell.path}/support/rewrite-hooks.sh", Gitlab.config.gitlab_shell.repos_path) - puts " [DONE]".green + $progress.print 'Put GitLab hooks in repositories dirs'.yellow + cmd = "#{Gitlab.config.gitlab_shell.path}/bin/create-hooks" + if system(cmd) + $progress.puts " [DONE]".green else puts " [FAILED]".red + puts "failed: #{cmd}" end end @@ -80,7 +113,7 @@ module Backup protected def path_to_repo(project) - File.join(repos_path, project.path_with_namespace + '.git') + project.repository.path_to_repo end def path_to_bundle(project) diff --git a/lib/disable_email_interceptor.rb b/lib/disable_email_interceptor.rb new file mode 100644 index 0000000000..1b80be112a --- /dev/null +++ b/lib/disable_email_interceptor.rb @@ -0,0 +1,8 @@ +# Read about interceptors in http://guides.rubyonrails.org/action_mailer_basics.html#intercepting-emails +class DisableEmailInterceptor + + def self.delivering_email(message) + message.perform_deliveries = false + Rails.logger.info "Emails disabled! Interceptor prevented sending mail #{message.subject}" + end +end diff --git a/lib/email_validator.rb b/lib/email_validator.rb index 0a67ebcd79..f509f0a584 100644 --- a/lib/email_validator.rb +++ b/lib/email_validator.rb @@ -1,5 +1,5 @@ # Based on https://github.com/balexand/email_validator -# +# # Extended to use only strict mode with following allowed characters: # ' - apostrophe # diff --git a/lib/event_filter.rb b/lib/event_filter.rb index 9b4b8c3801..163937c02c 100644 --- a/lib/event_filter.rb +++ b/lib/event_filter.rb @@ -23,7 +23,7 @@ class EventFilter end end - def initialize params + def initialize(params) @params = if params params.dup else @@ -31,7 +31,7 @@ class EventFilter end end - def apply_filter events + def apply_filter(events) return events unless params.present? filter = params.dup @@ -50,7 +50,7 @@ class EventFilter events = events.where(action: actions) end - def options key + def options(key) filter = params.dup if filter.include? key @@ -62,7 +62,7 @@ class EventFilter filter end - def active? key + def active?(key) params.include? key end end diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb index e51cb30bdd..6e4ed01e07 100644 --- a/lib/extracts_path.rb +++ b/lib/extracts_path.rb @@ -1,17 +1,9 @@ # Module providing methods for dealing with separating a tree-ish string and a # file path string when combined in a request parameter module ExtractsPath - extend ActiveSupport::Concern - # Raised when given an invalid file path class InvalidPathError < StandardError; end - included do - if respond_to?(:before_filter) - before_filter :assign_ref_vars - end - end - # Given a string containing both a Git tree-ish, such as a branch or tag, and # a filesystem path joined by forward slashes, attempts to separate the two. # @@ -110,7 +102,8 @@ module ExtractsPath raise InvalidPathError unless @commit @hex_path = Digest::SHA1.hexdigest(@path) - @logs_path = logs_file_project_ref_path(@project, @ref, @path) + @logs_path = logs_file_namespace_project_ref_path(@project.namespace, + @project, @ref, @path) rescue RuntimeError, NoMethodError, InvalidPathError not_found! diff --git a/lib/file_size_validator.rb b/lib/file_size_validator.rb index 42970c1be5..2eae55e534 100644 --- a/lib/file_size_validator.rb +++ b/lib/file_size_validator.rb @@ -25,8 +25,8 @@ class FileSizeValidator < ActiveModel::EachValidator keys.each do |key| value = options[key] - unless value.is_a?(Integer) && value >= 0 - raise ArgumentError, ":#{key} must be a nonnegative Integer" + unless (value.is_a?(Integer) && value >= 0) || value.is_a?(Symbol) + raise ArgumentError, ":#{key} must be a nonnegative Integer or symbol" end end end @@ -39,6 +39,14 @@ class FileSizeValidator < ActiveModel::EachValidator CHECKS.each do |key, validity_check| next unless check_value = options[key] + check_value = + case check_value + when Integer + check_value + when Symbol + record.send(check_value) + end + value ||= [] if key == :maximum value_size = value.size diff --git a/lib/gitlab.rb b/lib/gitlab.rb new file mode 100644 index 0000000000..5fc1862c3e --- /dev/null +++ b/lib/gitlab.rb @@ -0,0 +1,5 @@ +require 'gitlab/git' + +module Gitlab + autoload :Satellite, 'gitlab/satellite/satellite' +end diff --git a/lib/gitlab/access.rb b/lib/gitlab/access.rb index 87f9cfab60..424541b4a0 100644 --- a/lib/gitlab/access.rb +++ b/lib/gitlab/access.rb @@ -11,11 +11,20 @@ module Gitlab MASTER = 40 OWNER = 50 + # Branch protection settings + PROTECTION_NONE = 0 + PROTECTION_DEV_CAN_PUSH = 1 + PROTECTION_FULL = 2 + class << self def values options.values end + def all_values + options_with_owner.values + end + def options { "Guest" => GUEST, @@ -39,6 +48,18 @@ module Gitlab master: MASTER, } end + + def protection_options + { + "Not protected, developers and masters can (force) push and delete the branch" => PROTECTION_NONE, + "Partially protected, developers can also push but prevent all force pushes and deletion" => PROTECTION_DEV_CAN_PUSH, + "Fully protected, only masters can push and prevent all force pushes and deletion" => PROTECTION_FULL, + } + end + + def protection_values + protection_options.values + end end def human_access diff --git a/lib/gitlab/app_logger.rb b/lib/gitlab/app_logger.rb index 8e4717b46e..dddcb2538f 100644 --- a/lib/gitlab/app_logger.rb +++ b/lib/gitlab/app_logger.rb @@ -1,7 +1,7 @@ module Gitlab class AppLogger < Gitlab::Logger - def self.file_name - 'application.log' + def self.file_name_noext + 'application' end def format_message(severity, timestamp, progname, msg) diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 955abc1bed..30509528b8 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -1,24 +1,18 @@ module Gitlab class Auth def find(login, password) - user = User.find_by(email: login) || User.find_by(username: login) + user = User.by_login(login) + # If no user is found, or it's an LDAP server, try LDAP. + # LDAP users are only authenticated via LDAP if user.nil? || user.ldap_user? # Second chance - try LDAP authentication - return nil unless ldap_conf.enabled + return nil unless Gitlab::LDAP::Config.enabled? - Gitlab::LDAP::User.authenticate(login, password) + Gitlab::LDAP::Authentication.login(login, password) else user if user.valid_password?(password) end end - - def log - Gitlab::AppLogger - end - - def ldap_conf - @ldap_conf ||= Gitlab.config.ldap - end end end diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index c2f3b851c0..050b5ba29d 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -1,3 +1,4 @@ +require_relative 'rack_attack_helpers' require_relative 'shell_env' module Grack @@ -10,8 +11,9 @@ module Grack @request = Rack::Request.new(env) @auth = Request.new(env) - # Need this patch due to the rails mount + @gitlab_ci = false + # Need this patch due to the rails mount # Need this if under RELATIVE_URL_ROOT unless Gitlab.config.gitlab.relative_url_root.empty? # If website is mounted using relative_url_root need to remove it first @@ -22,8 +24,12 @@ module Grack @env['SCRIPT_NAME'] = "" - if project - auth! + auth! + + if project && authorized_request? + @app.call(env) + elsif @user.nil? && !@gitlab_ci + unauthorized else render_not_found end @@ -32,35 +38,30 @@ module Grack private def auth! - if @auth.provided? - return bad_request unless @auth.basic? + return unless @auth.provided? - # Authentication with username and password - login, password = @auth.credentials + return bad_request unless @auth.basic? - # Allow authentication for GitLab CI service - # if valid token passed - if gitlab_ci_request?(login, password) - return @app.call(env) - end + # Authentication with username and password + login, password = @auth.credentials - @user = authenticate_user(login, password) - - if @user - Gitlab::ShellEnv.set_env(@user) - @env['REMOTE_USER'] = @auth.username - end + # Allow authentication for GitLab CI service + # if valid token passed + if gitlab_ci_request?(login, password) + @gitlab_ci = true + return end - if authorized_request? - @app.call(env) - else - unauthorized + @user = authenticate_user(login, password) + + if @user + Gitlab::ShellEnv.set_env(@user) + @env['REMOTE_USER'] = @auth.username end end def gitlab_ci_request?(login, password) - if login == "gitlab-ci-token" && project.gitlab_ci? + if login == "gitlab-ci-token" && project && project.gitlab_ci? token = project.gitlab_ci_service.token if token.present? && token == password && git_cmd == 'git-upload-pack' @@ -71,16 +72,64 @@ module Grack false end + def oauth_access_token_check(login, password) + if login == "oauth2" && git_cmd == 'git-upload-pack' && password.present? + token = Doorkeeper::AccessToken.by_token(password) + token && token.accessible? && User.find_by(id: token.resource_owner_id) + end + end + def authenticate_user(login, password) - auth = Gitlab::Auth.new - auth.find(login, password) + user = Gitlab::Auth.new.find(login, password) + + unless user + user = oauth_access_token_check(login, password) + end + + # 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" + end + end + end + + user end def authorized_request? + return true if @gitlab_ci + case git_cmd when *Gitlab::GitAccess::DOWNLOAD_COMMANDS if user - Gitlab::GitAccess.new.download_allowed?(user, project) + Gitlab::GitAccess.new(user, project).download_access_check.allowed? elsif project.public? # Allow clone/fetch for public projects true @@ -90,7 +139,7 @@ module Grack when *Gitlab::GitAccess::PUSH_COMMANDS if user # Skip user authorization on upload request. - # It will be serverd by update hook in repository + # It will be done by the pre-receive hook in the repository. true else false @@ -111,7 +160,9 @@ module Grack end def project - @project ||= project_by_path(@request.path_info) + return @project if defined?(@project) + + @project = project_by_path(@request.path_info) end def project_by_path(path) @@ -119,12 +170,13 @@ module Grack path_with_namespace = m.last path_with_namespace.gsub!(/\.wiki$/, '') + path_with_namespace[0] = '' if path_with_namespace.start_with?('/') Project.find_with_namespace(path_with_namespace) end end def render_not_found - [404, {"Content-Type" => "text/plain"}, ["Not Found"]] + [404, { "Content-Type" => "text/plain" }, ["Not Found"]] end end end diff --git a/lib/gitlab/backend/rack_attack_helpers.rb b/lib/gitlab/backend/rack_attack_helpers.rb new file mode 100644 index 0000000000..8538f3f6ec --- /dev/null +++ b/lib/gitlab/backend/rack_attack_helpers.rb @@ -0,0 +1,31 @@ +# rack-attack v4.2.0 doesn't yet support clearing of keys. +# Taken from https://github.com/kickstarter/rack-attack/issues/113 +class Rack::Attack::Allow2Ban + def self.reset(discriminator, options) + findtime = options[:findtime] or raise ArgumentError, "Must pass findtime option" + + cache.reset_count("#{key_prefix}:count:#{discriminator}", findtime) + cache.delete("#{key_prefix}:ban:#{discriminator}") + end +end + +class Rack::Attack::Cache + def reset_count(unprefixed_key, period) + epoch_time = Time.now.to_i + # Add 1 to expires_in to avoid timing error: http://git.io/i1PHXA + expires_in = period - (epoch_time % period) + 1 + key = "#{(epoch_time / period).to_i}:#{unprefixed_key}" + delete(key) + end + + def delete(unprefixed_key) + store.delete("#{prefix}:#{unprefixed_key}") + end +end + +class Rack::Attack::StoreProxy::RedisStoreProxy + def delete(key, options={}) + self.del(key) + rescue Redis::BaseError + end +end diff --git a/lib/gitlab/backend/shell.rb b/lib/gitlab/backend/shell.rb index 53bff3037e..530f9d93de 100644 --- a/lib/gitlab/backend/shell.rb +++ b/lib/gitlab/backend/shell.rb @@ -8,6 +8,13 @@ module Gitlab end end + class << self + def version_required + @version_required ||= File.read(Rails.root. + join('GITLAB_SHELL_VERSION')).strip + end + end + # Init new repository # # name - project path with namespace @@ -16,7 +23,8 @@ module Gitlab # add_repository("gitlab/gitlab-ci") # def add_repository(name) - system "#{gitlab_shell_path}/bin/gitlab-projects", "add-project", "#{name}.git" + Gitlab::Utils.system_silent([gitlab_shell_projects_path, + 'add-project', "#{name}.git"]) end # Import repository @@ -27,7 +35,8 @@ module Gitlab # import_repository("gitlab/gitlab-ci", "https://github.com/randx/six.git") # def import_repository(name, url) - system "#{gitlab_shell_path}/bin/gitlab-projects", "import-project", "#{name}.git", url, '240' + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'import-project', + "#{name}.git", url, '240']) end # Move repository @@ -39,7 +48,8 @@ module Gitlab # mv_repository("gitlab/gitlab-ci", "randx/gitlab-ci-new.git") # def mv_repository(path, new_path) - system "#{gitlab_shell_path}/bin/gitlab-projects", "mv-project", "#{path}.git", "#{new_path}.git" + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'mv-project', + "#{path}.git", "#{new_path}.git"]) end # Update HEAD for repository @@ -51,7 +61,8 @@ module Gitlab # update_repository_head("gitlab/gitlab-ci", "3-1-stable") # def update_repository_head(path, branch) - system "#{gitlab_shell_path}/bin/gitlab-projects", "update-head", "#{path}.git", branch + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'update-head', + "#{path}.git", branch]) end # Fork repository to new namespace @@ -63,7 +74,8 @@ module Gitlab # fork_repository("gitlab/gitlab-ci", "randx") # def fork_repository(path, fork_namespace) - system "#{gitlab_shell_path}/bin/gitlab-projects", "fork-project", "#{path}.git", fork_namespace + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'fork-project', + "#{path}.git", fork_namespace]) end # Remove repository from file system @@ -74,7 +86,8 @@ module Gitlab # remove_repository("gitlab/gitlab-ci") # def remove_repository(name) - system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-project", "#{name}.git" + Gitlab::Utils.system_silent([gitlab_shell_projects_path, + 'rm-project', "#{name}.git"]) end # Add repository branch from passed ref @@ -87,7 +100,8 @@ module Gitlab # add_branch("gitlab/gitlab-ci", "4-0-stable", "master") # def add_branch(path, branch_name, ref) - system "#{gitlab_shell_path}/bin/gitlab-projects", "create-branch", "#{path}.git", branch_name, ref + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'create-branch', + "#{path}.git", branch_name, ref]) end # Remove repository branch @@ -99,7 +113,8 @@ module Gitlab # rm_branch("gitlab/gitlab-ci", "4-0-stable") # def rm_branch(path, branch_name) - system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-branch", "#{path}.git", branch_name + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-branch', + "#{path}.git", branch_name]) end # Add repository tag from passed ref @@ -107,12 +122,17 @@ module Gitlab # path - project path with namespace # tag_name - new tag name # ref - HEAD for new tag + # message - optional message for tag (annotated tag) # # Ex. # add_tag("gitlab/gitlab-ci", "v4.0", "master") + # add_tag("gitlab/gitlab-ci", "v4.0", "master", "message") # - def add_tag(path, tag_name, ref) - system "#{gitlab_shell_path}/bin/gitlab-projects", "create-tag", "#{path}.git", tag_name, ref + def add_tag(path, tag_name, ref, message = nil) + cmd = %W(#{gitlab_shell_path}/bin/gitlab-projects create-tag #{path}.git + #{tag_name} #{ref}) + cmd << message unless message.nil? || message.empty? + Gitlab::Utils.system_silent(cmd) end # Remove repository tag @@ -124,7 +144,8 @@ module Gitlab # rm_tag("gitlab/gitlab-ci", "v4.0") # def rm_tag(path, tag_name) - system "#{gitlab_shell_path}/bin/gitlab-projects", "rm-tag", "#{path}.git", tag_name + Gitlab::Utils.system_silent([gitlab_shell_projects_path, 'rm-tag', + "#{path}.git", tag_name]) end # Add new key to gitlab-shell @@ -133,7 +154,8 @@ module Gitlab # add_key("key-42", "sha-rsa ...") # def add_key(key_id, key_content) - system "#{gitlab_shell_path}/bin/gitlab-keys", "add-key", key_id, key_content + Gitlab::Utils.system_silent([gitlab_shell_keys_path, + 'add-key', key_id, key_content]) end # Batch-add keys to authorized_keys @@ -152,7 +174,8 @@ module Gitlab # remove_key("key-342", "sha-rsa ...") # def remove_key(key_id, key_content) - system "#{gitlab_shell_path}/bin/gitlab-keys", "rm-key", key_id, key_content + Gitlab::Utils.system_silent([gitlab_shell_keys_path, + 'rm-key', key_id, key_content]) end # Remove all ssh keys from gitlab shell @@ -161,7 +184,7 @@ module Gitlab # remove_all_keys # def remove_all_keys - system "#{gitlab_shell_path}/bin/gitlab-keys", "clear" + Gitlab::Utils.system_silent([gitlab_shell_keys_path, 'clear']) end # Add empty directory for storing repositories @@ -208,7 +231,7 @@ module Gitlab FileUtils.rm_r(satellites_path, force: true) end - def url_to_repo path + def url_to_repo(path) Gitlab.config.gitlab_shell.ssh_path_prefix + "#{path}.git" end @@ -217,7 +240,7 @@ module Gitlab gitlab_shell_version_file = "#{gitlab_shell_path}/VERSION" if File.readable?(gitlab_shell_version_file) - File.read(gitlab_shell_version_file) + File.read(gitlab_shell_version_file).chomp end end @@ -244,5 +267,13 @@ module Gitlab def exists?(dir_name) File.exists?(full_path(dir_name)) end + + def gitlab_shell_projects_path + File.join(gitlab_shell_path, 'bin', 'gitlab-projects') + end + + def gitlab_shell_keys_path + File.join(gitlab_shell_path, 'bin', 'gitlab-keys') + end end end diff --git a/lib/gitlab/backend/shell_adapter.rb b/lib/gitlab/backend/shell_adapter.rb index f247f4593d..fbe2a7a0d7 100644 --- a/lib/gitlab/backend/shell_adapter.rb +++ b/lib/gitlab/backend/shell_adapter.rb @@ -9,4 +9,3 @@ module Gitlab end end end - diff --git a/lib/gitlab/bitbucket_import.rb b/lib/gitlab/bitbucket_import.rb new file mode 100644 index 0000000000..7298152e7e --- /dev/null +++ b/lib/gitlab/bitbucket_import.rb @@ -0,0 +1,6 @@ +module Gitlab + module BitbucketImport + mattr_accessor :public_key + @public_key = nil + end +end diff --git a/lib/gitlab/bitbucket_import/client.rb b/lib/gitlab/bitbucket_import/client.rb new file mode 100644 index 0000000000..5b1952b967 --- /dev/null +++ b/lib/gitlab/bitbucket_import/client.rb @@ -0,0 +1,99 @@ +module Gitlab + module BitbucketImport + class Client + attr_reader :consumer, :api + + def initialize(access_token = nil, access_token_secret = nil) + @consumer = ::OAuth::Consumer.new( + config.app_id, + config.app_secret, + bitbucket_options + ) + + if access_token && access_token_secret + @api = ::OAuth::AccessToken.new(@consumer, access_token, access_token_secret) + end + end + + def request_token(redirect_uri) + request_token = consumer.get_request_token(oauth_callback: redirect_uri) + + { + oauth_token: request_token.token, + oauth_token_secret: request_token.secret, + oauth_callback_confirmed: request_token.callback_confirmed?.to_s + } + end + + def authorize_url(request_token, redirect_uri) + request_token = ::OAuth::RequestToken.from_hash(consumer, request_token) if request_token.is_a?(Hash) + + if request_token.callback_confirmed? + request_token.authorize_url + else + request_token.authorize_url(oauth_callback: redirect_uri) + end + end + + def get_token(request_token, oauth_verifier, redirect_uri) + request_token = ::OAuth::RequestToken.from_hash(consumer, request_token) if request_token.is_a?(Hash) + + if request_token.callback_confirmed? + request_token.get_access_token(oauth_verifier: oauth_verifier) + else + request_token.get_access_token(oauth_callback: redirect_uri) + end + end + + def user + JSON.parse(api.get("/api/1.0/user").body) + end + + def issues(project_identifier) + JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}/issues").body) + end + + def issue_comments(project_identifier, issue_id) + JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}/issues/#{issue_id}/comments").body) + end + + def project(project_identifier) + JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}").body) + end + + def find_deploy_key(project_identifier, key) + JSON.parse(api.get("/api/1.0/repositories/#{project_identifier}/deploy-keys").body).find do |deploy_key| + deploy_key["key"].chomp == key.chomp + end + end + + def add_deploy_key(project_identifier, key) + deploy_key = find_deploy_key(project_identifier, key) + return if deploy_key + + JSON.parse(api.post("/api/1.0/repositories/#{project_identifier}/deploy-keys", key: key, label: "GitLab import key").body) + end + + def delete_deploy_key(project_identifier, key) + deploy_key = find_deploy_key(project_identifier, key) + return unless deploy_key + + api.delete("/api/1.0/repositories/#{project_identifier}/deploy-keys/#{deploy_key["pk"]}").code == "204" + end + + def projects + JSON.parse(api.get("/api/1.0/user/repositories").body).select { |repo| repo["scm"] == "git" } + end + + private + + def config + Gitlab.config.omniauth.providers.find { |provider| provider.name == "bitbucket"} + end + + def bitbucket_options + OmniAuth::Strategies::Bitbucket.default_options[:client_options].symbolize_keys + end + end + end +end diff --git a/lib/gitlab/bitbucket_import/importer.rb b/lib/gitlab/bitbucket_import/importer.rb new file mode 100644 index 0000000000..42c93707ca --- /dev/null +++ b/lib/gitlab/bitbucket_import/importer.rb @@ -0,0 +1,52 @@ +module Gitlab + module BitbucketImport + class Importer + attr_reader :project, :client + + def initialize(project) + @project = project + @client = Client.new(project.creator.bitbucket_access_token, project.creator.bitbucket_access_token_secret) + @formatter = Gitlab::ImportFormatter.new + end + + def execute + project_identifier = project.import_source + + return true unless client.project(project_identifier)["has_issues"] + + #Issues && Comments + issues = client.issues(project_identifier) + + issues["issues"].each do |issue| + body = @formatter.author_line(issue["reported_by"]["username"], issue["content"]) + + comments = client.issue_comments(project_identifier, issue["local_id"]) + + if comments.any? + body += @formatter.comments_header + end + + comments.each do |comment| + body += @formatter.comment(comment["author_info"]["username"], comment["utc_created_on"], comment["content"]) + end + + project.issues.create!( + description: body, + title: issue["title"], + state: %w(resolved invalid duplicate wontfix).include?(issue["status"]) ? 'closed' : 'opened', + author_id: gl_user_id(project, issue["reported_by"]["username"]) + ) + end + + true + end + + private + + def gl_user_id(project, bitbucket_id) + user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'bitbucket'", bitbucket_id.to_s) + (user && user.id) || project.creator_id + end + end + end +end diff --git a/lib/gitlab/bitbucket_import/key_adder.rb b/lib/gitlab/bitbucket_import/key_adder.rb new file mode 100644 index 0000000000..9931aa7e02 --- /dev/null +++ b/lib/gitlab/bitbucket_import/key_adder.rb @@ -0,0 +1,23 @@ +module Gitlab + module BitbucketImport + class KeyAdder + attr_reader :repo, :current_user, :client + + def initialize(repo, current_user) + @repo, @current_user = repo, current_user + @client = Client.new(current_user.bitbucket_access_token, current_user.bitbucket_access_token_secret) + end + + def execute + return false unless BitbucketImport.public_key.present? + + project_identifier = "#{repo["owner"]}/#{repo["slug"]}" + client.add_deploy_key(project_identifier, BitbucketImport.public_key) + + true + rescue + false + end + end + end +end diff --git a/lib/gitlab/bitbucket_import/key_deleter.rb b/lib/gitlab/bitbucket_import/key_deleter.rb new file mode 100644 index 0000000000..1a24a86fc3 --- /dev/null +++ b/lib/gitlab/bitbucket_import/key_deleter.rb @@ -0,0 +1,23 @@ +module Gitlab + module BitbucketImport + class KeyDeleter + attr_reader :project, :current_user, :client + + def initialize(project) + @project = project + @current_user = project.creator + @client = Client.new(current_user.bitbucket_access_token, current_user.bitbucket_access_token_secret) + end + + def execute + return false unless BitbucketImport.public_key.present? + + client.delete_deploy_key(project.import_source, BitbucketImport.public_key) + + true + rescue + false + end + end + end +end diff --git a/lib/gitlab/bitbucket_import/project_creator.rb b/lib/gitlab/bitbucket_import/project_creator.rb new file mode 100644 index 0000000000..54420e62c9 --- /dev/null +++ b/lib/gitlab/bitbucket_import/project_creator.rb @@ -0,0 +1,26 @@ +module Gitlab + module BitbucketImport + class ProjectCreator + attr_reader :repo, :namespace, :current_user + + def initialize(repo, namespace, current_user) + @repo = repo + @namespace = namespace + @current_user = current_user + end + + def execute + ::Projects::CreateService.new(current_user, + name: repo["name"], + path: repo["slug"], + description: repo["description"], + namespace_id: namespace.id, + visibility_level: repo["is_private"] ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::VisibilityLevel::PUBLIC, + import_type: "bitbucket", + import_source: "#{repo["owner"]}/#{repo["slug"]}", + import_url: "ssh://git@bitbucket.org/#{repo["owner"]}/#{repo["slug"]}.git" + ).execute + end + end + end +end diff --git a/lib/gitlab/blacklist.rb b/lib/gitlab/blacklist.rb index 6bc2c3b487..43145e0ee1 100644 --- a/lib/gitlab/blacklist.rb +++ b/lib/gitlab/blacklist.rb @@ -3,7 +3,32 @@ module Gitlab extend self def path - %w(admin dashboard files groups help profile projects search public assets u s teams merge_requests issues users snippets services repository hooks notes) + %w( + admin + dashboard + files + groups + help + profile + projects + search + public + assets + u + s + teams + merge_requests + issues + users + snippets + services + repository + hooks + notes + unsubscribes + all + ci + ) end end end diff --git a/lib/gitlab/closing_issue_extractor.rb b/lib/gitlab/closing_issue_extractor.rb index 90f1370c20..ab184d95c0 100644 --- a/lib/gitlab/closing_issue_extractor.rb +++ b/lib/gitlab/closing_issue_extractor.rb @@ -1,16 +1,20 @@ module Gitlab - module ClosingIssueExtractor + class ClosingIssueExtractor ISSUE_CLOSING_REGEX = Regexp.new(Gitlab.config.gitlab.issue_closing_pattern) - def self.closed_by_message_in_project(message, project) - md = ISSUE_CLOSING_REGEX.match(message) - if md - extractor = Gitlab::ReferenceExtractor.new - extractor.analyze(md[0]) - extractor.issues_for(project) - else - [] - end + def initialize(project, current_user = nil) + @extractor = Gitlab::ReferenceExtractor.new(project, current_user) + end + + def closed_by_message(message) + return [] if message.nil? + + closing_statements = message.scan(ISSUE_CLOSING_REGEX). + map { |ref| ref[0] }.join(" ") + + @extractor.analyze(closing_statements) + + @extractor.issues end end end diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb new file mode 100644 index 0000000000..3fd0823df0 --- /dev/null +++ b/lib/gitlab/contributions_calendar.rb @@ -0,0 +1,56 @@ +module Gitlab + class ContributionsCalendar + attr_reader :timestamps, :projects, :user + + def initialize(projects, user) + @projects = projects + @user = user + end + + def timestamps + return @timestamps if @timestamps.present? + + @timestamps = {} + date_from = 1.year.ago + date_to = Date.today + + events = Event.reorder(nil).contributions.where(author_id: user.id). + where("created_at > ?", date_from).where(project_id: projects). + group('date(created_at)'). + select('date(created_at), count(id) as total_amount'). + map(&:attributes) + + dates = (1.year.ago.to_date..(Date.today + 1.day)).to_a + + dates.each do |date| + date_id = date.to_time.to_i.to_s + @timestamps[date_id] = 0 + day_events = events.find { |day_events| day_events["date"] == date } + + if day_events + @timestamps[date_id] = day_events["total_amount"] + end + end + + @timestamps + end + + def events_by_date(date) + events = Event.contributions.where(author_id: user.id). + where("created_at > ? AND created_at < ?", date.beginning_of_day, date.end_of_day). + where(project_id: projects) + + events.select do |event| + event.push? || event.issue? || event.merge_request? + end + end + + def starting_year + (Time.now - 1.year).strftime("%Y") + end + + def starting_month + Date.today.strftime("%m").to_i + end + end +end diff --git a/lib/gitlab/contributors.rb b/lib/gitlab/contributor.rb similarity index 100% rename from lib/gitlab/contributors.rb rename to lib/gitlab/contributor.rb diff --git a/lib/gitlab/current_settings.rb b/lib/gitlab/current_settings.rb new file mode 100644 index 0000000000..d8f696d247 --- /dev/null +++ b/lib/gitlab/current_settings.rb @@ -0,0 +1,28 @@ +module Gitlab + module CurrentSettings + def current_application_settings + key = :current_application_settings + + RequestStore.store[key] ||= begin + if ActiveRecord::Base.connected? && ActiveRecord::Base.connection.table_exists?('application_settings') + ApplicationSetting.current || ApplicationSetting.create_from_defaults + else + fake_application_settings + end + end + end + + def fake_application_settings + OpenStruct.new( + default_projects_limit: Settings.gitlab['default_projects_limit'], + default_branch_protection: Settings.gitlab['default_branch_protection'], + signup_enabled: Settings.gitlab['signup_enabled'], + signin_enabled: Settings.gitlab['signin_enabled'], + gravatar_enabled: Settings.gravatar['enabled'], + sign_in_text: Settings.extra['sign_in_text'], + restricted_visibility_levels: Settings.gitlab['restricted_visibility_levels'], + max_attachment_size: Settings.gitlab['max_attachment_size'] + ) + end + end +end diff --git a/lib/gitlab/diff/file.rb b/lib/gitlab/diff/file.rb new file mode 100644 index 0000000000..4daf65331e --- /dev/null +++ b/lib/gitlab/diff/file.rb @@ -0,0 +1,49 @@ +module Gitlab + module Diff + class File + attr_reader :diff + + delegate :new_file, :deleted_file, :renamed_file, + :old_path, :new_path, to: :diff, prefix: false + + def initialize(diff) + @diff = diff + end + + # Array of Gitlab::DIff::Line objects + def diff_lines + @lines ||= parser.parse(raw_diff.lines) + end + + def mode_changed? + !!(diff.a_mode && diff.b_mode && diff.a_mode != diff.b_mode) + end + + def parser + Gitlab::Diff::Parser.new + end + + def raw_diff + diff.diff.to_s + end + + def next_line(index) + diff_lines[index + 1] + end + + def prev_line(index) + if index > 0 + diff_lines[index - 1] + end + end + + def file_path + if diff.new_path.present? + diff.new_path + elsif diff.old_path.present? + diff.old_path + end + end + end + end +end diff --git a/lib/gitlab/diff/line.rb b/lib/gitlab/diff/line.rb new file mode 100644 index 0000000000..8ac1b15e88 --- /dev/null +++ b/lib/gitlab/diff/line.rb @@ -0,0 +1,12 @@ +module Gitlab + module Diff + class Line + attr_reader :type, :text, :index, :old_pos, :new_pos + + def initialize(text, type, index, old_pos, new_pos) + @text, @type, @index = text, type, index + @old_pos, @new_pos = old_pos, new_pos + end + end + end +end diff --git a/lib/gitlab/diff/line_code.rb b/lib/gitlab/diff/line_code.rb new file mode 100644 index 0000000000..f3578ab3d3 --- /dev/null +++ b/lib/gitlab/diff/line_code.rb @@ -0,0 +1,9 @@ +module Gitlab + module Diff + class LineCode + def self.generate(file_path, new_line_position, old_line_position) + "#{Digest::SHA1.hexdigest(file_path)}_#{old_line_position}_#{new_line_position}" + end + end + end +end diff --git a/lib/gitlab/diff/parser.rb b/lib/gitlab/diff/parser.rb new file mode 100644 index 0000000000..c1d9520ddf --- /dev/null +++ b/lib/gitlab/diff/parser.rb @@ -0,0 +1,81 @@ +module Gitlab + module Diff + class Parser + include Enumerable + + def parse(lines) + @lines = lines + lines_obj = [] + line_obj_index = 0 + line_old = 1 + line_new = 1 + type = nil + + lines_arr = ::Gitlab::InlineDiff.processing lines + + lines_arr.each do |line| + raw_line = line.dup + + next if filename?(line) + + full_line = html_escape(line.gsub(/\n/, '')) + full_line = ::Gitlab::InlineDiff.replace_markers full_line + + 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 + lines_obj << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new) + line_obj_index += 1 + next + else + type = identification_type(line) + lines_obj << Gitlab::Diff::Line.new(full_line, type, line_obj_index, line_old, line_new) + line_obj_index += 1 + end + + + if line[0] == "+" + line_new += 1 + elsif line[0] == "-" + line_old += 1 + else + line_new += 1 + line_old += 1 + end + end + + lines_obj + end + + def empty? + @lines.empty? + end + + private + + def filename?(line) + line.start_with?('--- /dev/null', '+++ /dev/null', '--- a', '+++ b', + '--- /tmp/diffy', '+++ /tmp/diffy') + end + + def identification_type(line) + if line[0] == "+" + "new" + elsif line[0] == "-" + "old" + else + nil + end + end + + def html_escape(str) + replacements = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' } + str.gsub(/[&"'><]/, replacements) + end + end + end +end diff --git a/lib/gitlab/diff_parser.rb b/lib/gitlab/diff_parser.rb deleted file mode 100644 index b244295027..0000000000 --- a/lib/gitlab/diff_parser.rb +++ /dev/null @@ -1,83 +0,0 @@ -module Gitlab - class DiffParser - include Enumerable - - attr_reader :lines, :new_path - - def initialize(lines, new_path = '') - @lines = lines - @new_path = new_path - end - - def each - line_old = 1 - line_new = 1 - type = nil - - lines_arr = ::Gitlab::InlineDiff.processing lines - lines_arr.each do |line| - raw_line = line.dup - - next if filename?(line) - - full_line = html_escape(line.gsub(/\n/, '')) - full_line = ::Gitlab::InlineDiff.replace_markers full_line - - 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 - yield(full_line, type, nil, line_new, line_old) - next - else - type = identification_type(line) - line_code = generate_line_code(new_path, line_new, line_old) - yield(full_line, type, line_code, line_new, line_old, raw_line) - end - - - if line[0] == "+" - line_new += 1 - elsif line[0] == "-" - line_old += 1 - else - line_new += 1 - line_old += 1 - end - end - end - - def empty? - @lines.empty? - end - - private - - def filename?(line) - line.start_with?('--- /dev/null', '+++ /dev/null', '--- a', '+++ b', - '--- /tmp/diffy', '+++ /tmp/diffy') - end - - def identification_type(line) - if line[0] == "+" - "new" - elsif line[0] == "-" - "old" - else - nil - end - end - - def generate_line_code(path, line_new, line_old) - "#{Digest::SHA1.hexdigest(path)}_#{line_old}_#{line_new}" - end - - def html_escape str - replacements = { '&' => '&', '>' => '>', '<' => '<', '"' => '"', "'" => ''' } - str.gsub(/[&"'><]/, replacements) - end - end -end diff --git a/lib/gitlab/force_push_check.rb b/lib/gitlab/force_push_check.rb new file mode 100644 index 0000000000..fdb6a35c78 --- /dev/null +++ b/lib/gitlab/force_push_check.rb @@ -0,0 +1,15 @@ +module Gitlab + class ForcePushCheck + def self.force_push?(project, oldrev, newrev) + return false if project.empty_repo? + + # Created or deleted branch + if Gitlab::Git.blank_ref?(oldrev) || Gitlab::Git.blank_ref?(newrev) + false + else + missed_refs, _ = Gitlab::Popen.popen(%W(git --git-dir=#{project.repository.path_to_repo} rev-list #{oldrev} ^#{newrev})) + missed_refs.split("\n").size > 0 + end + end + end +end diff --git a/lib/gitlab/git.rb b/lib/gitlab/git.rb new file mode 100644 index 0000000000..0c350d7c67 --- /dev/null +++ b/lib/gitlab/git.rb @@ -0,0 +1,25 @@ +module Gitlab + module Git + BLANK_SHA = '0' * 40 + TAG_REF_PREFIX = "refs/tags/" + BRANCH_REF_PREFIX = "refs/heads/" + + class << self + def ref_name(ref) + ref.gsub(/\Arefs\/(tags|heads)\//, '') + end + + def tag_ref?(ref) + ref.start_with?(TAG_REF_PREFIX) + end + + def branch_ref?(ref) + ref.start_with?(BRANCH_REF_PREFIX) + end + + def blank_ref?(ref) + ref == BLANK_SHA + end + end + end +end diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 38b3d82e2f..bc72b7528d 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -3,72 +3,196 @@ module Gitlab DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive } PUSH_COMMANDS = %w{ git-receive-pack } - attr_reader :params, :project, :git_cmd, :user + attr_reader :actor, :project - def allowed?(actor, cmd, project, ref = nil, oldrev = nil, newrev = nil, forced_push = false) + def initialize(actor, project) + @actor = actor + @project = project + end + + def user + return @user if defined?(@user) + + @user = + case actor + when User + actor + when DeployKey + nil + when Key + actor.user + end + end + + def deploy_key + actor if actor.is_a?(DeployKey) + end + + def can_push_to_branch?(ref) + return false unless user + + if project.protected_branch?(ref) && + !(project.developers_can_push_to_protected_branch?(ref) && project.team.developer?(user)) + user.can?(:push_code_to_protected_branches, project) + else + user.can?(:push_code, project) + end + end + + def can_read_project? + if user + user.can?(:read_project, project) + elsif deploy_key + deploy_key.projects.include?(project) + else + false + end + end + + def check(cmd, changes = nil) case cmd when *DOWNLOAD_COMMANDS - if actor.is_a? User - download_allowed?(actor, project) - elsif actor.is_a? DeployKey - actor.projects.include?(project) - elsif actor.is_a? Key - download_allowed?(actor.user, project) - else - raise 'Wrong actor' - end + download_access_check when *PUSH_COMMANDS - if actor.is_a? User - push_allowed?(actor, project, ref, oldrev, newrev, forced_push) - elsif actor.is_a? DeployKey - # Deploy key not allowed to push - return false - elsif actor.is_a? Key - push_allowed?(actor.user, project, ref, oldrev, newrev, forced_push) - else - raise 'Wrong actor' + push_access_check(changes) + else + build_status_object(false, "Wrong command") + end + end + + def download_access_check + if user + user_download_access_check + elsif deploy_key + deploy_key_download_access_check + else + raise 'Wrong actor' + end + end + + def push_access_check(changes) + if user + user_push_access_check(changes) + elsif deploy_key + build_status_object(false, "Deploy key not allowed to push") + else + raise 'Wrong actor' + end + end + + def user_download_access_check + if user && user_allowed? && user.can?(:download_code, project) + build_status_object(true) + else + build_status_object(false, "You don't have access") + end + end + + def deploy_key_download_access_check + if can_read_project? + build_status_object(true) + else + build_status_object(false, "Deploy key not allowed to access this project") + end + end + + def user_push_access_check(changes) + unless user && user_allowed? + return build_status_object(false, "You don't have access") + end + + if changes.blank? + return build_status_object(true) + end + + unless project.repository.exists? + return build_status_object(false, "Repository does not exist") + end + + changes = changes.lines if changes.kind_of?(String) + + # Iterate over all changes to find if user allowed all of them to be applied + changes.map(&:strip).reject(&:blank?).each do |change| + status = change_access_check(change) + unless status.allowed? + # If user does not have access to make at least one change - cancel all push + return status end + end + + build_status_object(true) + end + + def change_access_check(change) + oldrev, newrev, ref = change.split(' ') + + action = + if project.protected_branch?(branch_name(ref)) + protected_branch_action(oldrev, newrev, branch_name(ref)) + elsif protected_tag?(tag_name(ref)) + # Prevent any changes to existing git tag unless user has permissions + :admin_project + else + :push_code + end + + if user.can?(action, project) + build_status_object(true) else - false + build_status_object(false, "You don't have permission") end end - def download_allowed?(user, project) - if user && user_allowed?(user) - user.can?(:download_code, project) - else - false - end - end - - def push_allowed?(user, project, ref, oldrev, newrev, forced_push) - if user && user_allowed?(user) - action = if project.protected_branch?(ref) - # we dont allow force push to protected branch - if forced_push.to_s == 'true' - :force_push_code_to_protected_branches - # and we dont allow remove of protected branch - elsif newrev =~ /0000000/ - :remove_protected_branches - else - :push_code_to_protected_branches - end - elsif project.repository && project.repository.tag_names.include?(ref) - # Prevent any changes to existing git tag unless user has permissions - :admin_project - else - :push_code - end - user.can?(action, project) - else - false - end + def forced_push?(oldrev, newrev) + Gitlab::ForcePushCheck.force_push?(project, oldrev, newrev) end private - def user_allowed?(user) + def protected_branch_action(oldrev, newrev, branch_name) + # we dont allow force push to protected branch + if forced_push?(oldrev, newrev) + :force_push_code_to_protected_branches + elsif Gitlab::Git.blank_ref?(newrev) + # and we dont allow remove of protected branch + :remove_protected_branches + elsif project.developers_can_push_to_protected_branch?(branch_name) + :push_code + else + :push_code_to_protected_branches + end + end + + def protected_tag?(tag_name) + project.repository.tag_names.include?(tag_name) + end + + def user_allowed? Gitlab::UserAccess.allowed?(user) end + + def branch_name(ref) + ref = ref.to_s + if Gitlab::Git.branch_ref?(ref) + Gitlab::Git.ref_name(ref) + else + nil + end + end + + def tag_name(ref) + ref = ref.to_s + if Gitlab::Git.tag_ref?(ref) + Gitlab::Git.ref_name(ref) + else + nil + end + end + + protected + + def build_status_object(status, message = '') + GitAccessStatus.new(status, message) + end end end diff --git a/lib/gitlab/git_access_status.rb b/lib/gitlab/git_access_status.rb new file mode 100644 index 0000000000..5a806ff6e0 --- /dev/null +++ b/lib/gitlab/git_access_status.rb @@ -0,0 +1,15 @@ +module Gitlab + class GitAccessStatus + attr_accessor :status, :message + alias_method :allowed?, :status + + def initialize(status, message = '') + @status = status + @message = message + end + + def to_json + { status: @status, message: @message }.to_json + end + end +end diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb new file mode 100644 index 0000000000..73d99b9620 --- /dev/null +++ b/lib/gitlab/git_access_wiki.rb @@ -0,0 +1,11 @@ +module Gitlab + class GitAccessWiki < GitAccess + def change_access_check(change) + if user.can?(:write_wiki, project) + build_status_object(true) + else + build_status_object(false, "You don't have access") + end + end + end +end diff --git a/lib/gitlab/git_logger.rb b/lib/gitlab/git_logger.rb index fbfed205a0..9e02ccc0f4 100644 --- a/lib/gitlab/git_logger.rb +++ b/lib/gitlab/git_logger.rb @@ -1,7 +1,7 @@ module Gitlab class GitLogger < Gitlab::Logger - def self.file_name - 'githost.log' + def self.file_name_noext + 'githost' end def format_message(severity, timestamp, progname, msg) diff --git a/lib/gitlab/git_ref_validator.rb b/lib/gitlab/git_ref_validator.rb new file mode 100644 index 0000000000..39d17def93 --- /dev/null +++ b/lib/gitlab/git_ref_validator.rb @@ -0,0 +1,12 @@ +module Gitlab + module GitRefValidator + extend self + # Validates a given name against the git reference specification + # + # Returns true for a valid reference name, false otherwise + def validate(ref_name) + Gitlab::Utils.system_silent( + %W(git check-ref-format refs/#{ref_name})) + end + end +end diff --git a/lib/gitlab/github_import/client.rb b/lib/gitlab/github_import/client.rb new file mode 100644 index 0000000000..270cbcd9cc --- /dev/null +++ b/lib/gitlab/github_import/client.rb @@ -0,0 +1,53 @@ +module Gitlab + module GithubImport + class Client + attr_reader :client, :api + + def initialize(access_token) + @client = ::OAuth2::Client.new( + config.app_id, + config.app_secret, + github_options + ) + + if access_token + ::Octokit.auto_paginate = true + @api = ::Octokit::Client.new(access_token: access_token) + end + end + + def authorize_url(redirect_uri) + client.auth_code.authorize_url({ + redirect_uri: redirect_uri, + scope: "repo, user, user:email" + }) + end + + def get_token(code) + client.auth_code.get_token(code).token + end + + def method_missing(method, *args, &block) + if api.respond_to?(method) + api.send(method, *args, &block) + else + super(method, *args, &block) + end + end + + def respond_to?(method) + api.respond_to?(method) || super + end + + private + + def config + Gitlab.config.omniauth.providers.find{|provider| provider.name == "github"} + end + + def github_options + OmniAuth::Strategies::GitHub.default_options[:client_options].symbolize_keys + end + end + end +end diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb new file mode 100644 index 0000000000..23832b3233 --- /dev/null +++ b/lib/gitlab/github_import/importer.rb @@ -0,0 +1,46 @@ +module Gitlab + module GithubImport + class Importer + attr_reader :project, :client + + def initialize(project) + @project = project + @client = Client.new(project.creator.github_access_token) + @formatter = Gitlab::ImportFormatter.new + end + + def execute + #Issues && Comments + client.list_issues(project.import_source, state: :all).each do |issue| + if issue.pull_request.nil? + + body = @formatter.author_line(issue.user.login, issue.body) + + if issue.comments > 0 + body += @formatter.comments_header + + client.issue_comments(project.import_source, issue.number).each do |c| + body += @formatter.comment(c.user.login, c.created_at, c.body) + end + end + + project.issues.create!( + description: body, + title: issue.title, + state: issue.state == 'closed' ? 'closed' : 'opened', + author_id: gl_user_id(project, issue.user.id) + ) + end + end + end + + private + + def gl_user_id(project, github_id) + user = User.joins(:identities). + find_by("identities.extern_uid = ? AND identities.provider = 'github'", github_id.to_s) + (user && user.id) || project.creator_id + end + end + end +end diff --git a/lib/gitlab/github_import/project_creator.rb b/lib/gitlab/github_import/project_creator.rb new file mode 100644 index 0000000000..2723eec933 --- /dev/null +++ b/lib/gitlab/github_import/project_creator.rb @@ -0,0 +1,26 @@ +module Gitlab + module GithubImport + class ProjectCreator + attr_reader :repo, :namespace, :current_user + + def initialize(repo, namespace, current_user) + @repo = repo + @namespace = namespace + @current_user = current_user + end + + def execute + ::Projects::CreateService.new(current_user, + name: repo.name, + path: repo.name, + description: repo.description, + namespace_id: namespace.id, + visibility_level: repo.private ? Gitlab::VisibilityLevel::PRIVATE : Gitlab::VisibilityLevel::PUBLIC, + import_type: "github", + import_source: repo.full_name, + import_url: repo.clone_url.sub("https://", "https://#{current_user.github_access_token}@") + ).execute + end + end + end +end diff --git a/lib/gitlab/gitlab_import/client.rb b/lib/gitlab/gitlab_import/client.rb new file mode 100644 index 0000000000..9c00896c91 --- /dev/null +++ b/lib/gitlab/gitlab_import/client.rb @@ -0,0 +1,82 @@ +module Gitlab + module GitlabImport + class Client + attr_reader :client, :api + + PER_PAGE = 100 + + def initialize(access_token) + @client = ::OAuth2::Client.new( + config.app_id, + config.app_secret, + gitlab_options + ) + + if access_token + @api = OAuth2::AccessToken.from_hash(@client, access_token: access_token) + end + end + + def authorize_url(redirect_uri) + client.auth_code.authorize_url({ + redirect_uri: redirect_uri, + scope: "api" + }) + end + + def get_token(code, redirect_uri) + client.auth_code.get_token(code, redirect_uri: redirect_uri).token + end + + def user + api.get("/api/v3/user").parsed + end + + def issues(project_identifier) + lazy_page_iterator(PER_PAGE) do |page| + api.get("/api/v3/projects/#{project_identifier}/issues?per_page=#{PER_PAGE}&page=#{page}").parsed + end + end + + def issue_comments(project_identifier, issue_id) + lazy_page_iterator(PER_PAGE) do |page| + api.get("/api/v3/projects/#{project_identifier}/issues/#{issue_id}/notes?per_page=#{PER_PAGE}&page=#{page}").parsed + end + end + + def project(id) + api.get("/api/v3/projects/#{id}").parsed + end + + def projects + lazy_page_iterator(PER_PAGE) do |page| + api.get("/api/v3/projects?per_page=#{PER_PAGE}&page=#{page}").parsed + end + end + + private + + def lazy_page_iterator(per_page) + Enumerator.new do |y| + page = 1 + loop do + items = yield(page) + items.each do |item| + y << item + end + break if items.empty? || items.size < per_page + page += 1 + end + end + end + + def config + Gitlab.config.omniauth.providers.find{|provider| provider.name == "gitlab"} + end + + def gitlab_options + OmniAuth::Strategies::GitLab.default_options[:client_options].symbolize_keys + end + end + end +end diff --git a/lib/gitlab/gitlab_import/importer.rb b/lib/gitlab/gitlab_import/importer.rb new file mode 100644 index 0000000000..c5304a0699 --- /dev/null +++ b/lib/gitlab/gitlab_import/importer.rb @@ -0,0 +1,50 @@ +module Gitlab + module GitlabImport + class Importer + attr_reader :project, :client + + def initialize(project) + @project = project + @client = Client.new(project.creator.gitlab_access_token) + @formatter = Gitlab::ImportFormatter.new + end + + def execute + project_identifier = URI.encode(project.import_source, '/') + + #Issues && Comments + issues = client.issues(project_identifier) + + issues.each do |issue| + body = @formatter.author_line(issue["author"]["name"], issue["description"]) + + comments = client.issue_comments(project_identifier, issue["id"]) + + if comments.any? + body += @formatter.comments_header + end + + comments.each do |comment| + body += @formatter.comment(comment["author"]["name"], comment["created_at"], comment["body"]) + end + + project.issues.create!( + description: body, + title: issue["title"], + state: issue["state"], + author_id: gl_user_id(project, issue["author"]["id"]) + ) + end + + true + end + + private + + def gl_user_id(project, gitlab_id) + user = User.joins(:identities).find_by("identities.extern_uid = ? AND identities.provider = 'gitlab'", gitlab_id.to_s) + (user && user.id) || project.creator_id + end + end + end +end diff --git a/lib/gitlab/gitlab_import/project_creator.rb b/lib/gitlab/gitlab_import/project_creator.rb new file mode 100644 index 0000000000..f0d7141bf5 --- /dev/null +++ b/lib/gitlab/gitlab_import/project_creator.rb @@ -0,0 +1,26 @@ +module Gitlab + module GitlabImport + class ProjectCreator + attr_reader :repo, :namespace, :current_user + + def initialize(repo, namespace, current_user) + @repo = repo + @namespace = namespace + @current_user = current_user + end + + def execute + ::Projects::CreateService.new(current_user, + name: repo["name"], + path: repo["path"], + description: repo["description"], + namespace_id: namespace.id, + visibility_level: repo["visibility_level"], + import_type: "gitlab", + import_source: repo["path_with_namespace"], + import_url: repo["http_url_to_repo"].sub("://", "://oauth2:#{current_user.gitlab_access_token}@") + ).execute + end + end + end +end diff --git a/lib/gitlab/gitorious_import/client.rb b/lib/gitlab/gitorious_import/client.rb new file mode 100644 index 0000000000..8cdc3d4afa --- /dev/null +++ b/lib/gitlab/gitorious_import/client.rb @@ -0,0 +1,31 @@ +module Gitlab + module GitoriousImport + GITORIOUS_HOST = "https://gitorious.org" + + class Client + attr_reader :repo_list + + def initialize(repo_list) + @repo_list = repo_list + end + + def authorize_url(redirect_uri) + "#{GITORIOUS_HOST}/gitlab-import?callback_url=#{redirect_uri}" + end + + def repos + @repos ||= repo_names.map { |full_name| Repository.new(full_name) } + end + + def repo(id) + repos.find { |repo| repo.id == id } + end + + private + + def repo_names + repo_list.to_s.split(',').map(&:strip).reject(&:blank?) + end + end + end +end diff --git a/lib/gitlab/gitorious_import/project_creator.rb b/lib/gitlab/gitorious_import/project_creator.rb new file mode 100644 index 0000000000..cc9a91c91f --- /dev/null +++ b/lib/gitlab/gitorious_import/project_creator.rb @@ -0,0 +1,26 @@ +module Gitlab + module GitoriousImport + class ProjectCreator + attr_reader :repo, :namespace, :current_user + + def initialize(repo, namespace, current_user) + @repo = repo + @namespace = namespace + @current_user = current_user + end + + def execute + ::Projects::CreateService.new(current_user, + name: repo.name, + path: repo.path, + description: repo.description, + namespace_id: namespace.id, + visibility_level: Gitlab::VisibilityLevel::PUBLIC, + import_type: "gitorious", + import_source: repo.full_name, + import_url: repo.import_url + ).execute + end + end + end +end diff --git a/lib/gitlab/gitorious_import/repository.rb b/lib/gitlab/gitorious_import/repository.rb new file mode 100644 index 0000000000..f702797dc6 --- /dev/null +++ b/lib/gitlab/gitorious_import/repository.rb @@ -0,0 +1,37 @@ +module Gitlab + module GitoriousImport + GITORIOUS_HOST = "https://gitorious.org" + + Repository = Struct.new(:full_name) do + def id + Digest::SHA1.hexdigest(full_name) + end + + def namespace + segments.first + end + + def path + segments.last + end + + def name + path.titleize + end + + def description + "" + end + + def import_url + "#{GITORIOUS_HOST}/#{full_name}.git" + end + + private + + def segments + full_name.split('/') + end + end + end +end diff --git a/lib/gitlab/google_code_import/client.rb b/lib/gitlab/google_code_import/client.rb new file mode 100644 index 0000000000..02f31e45f8 --- /dev/null +++ b/lib/gitlab/google_code_import/client.rb @@ -0,0 +1,48 @@ +module Gitlab + module GoogleCodeImport + class Client + attr_reader :raw_data + + def self.mask_email(author) + parts = author.split("@", 2) + parts[0] = "#{parts[0][0...-3]}..." + parts.join("@") + end + + def initialize(raw_data) + @raw_data = raw_data + end + + def valid? + raw_data.is_a?(Hash) && raw_data["kind"] == "projecthosting#user" && raw_data.has_key?("projects") + end + + def repos + @repos ||= raw_data["projects"].map { |raw_repo| GoogleCodeImport::Repository.new(raw_repo) }.select(&:git?) + end + + def repo(id) + repos.find { |repo| repo.id == id } + end + + def user_map + user_map = Hash.new { |hash, user| hash[user] = self.class.mask_email(user) } + + repos.each do |repo| + next unless repo.valid? && repo.issues + + repo.issues.each do |raw_issue| + # Touching is enough to add the entry and masked email. + user_map[raw_issue["author"]["name"]] + + raw_issue["comments"]["items"].each do |raw_comment| + user_map[raw_comment["author"]["name"]] + end + end + end + + Hash[user_map.sort] + end + end + end +end diff --git a/lib/gitlab/google_code_import/importer.rb b/lib/gitlab/google_code_import/importer.rb new file mode 100644 index 0000000000..70bfe05977 --- /dev/null +++ b/lib/gitlab/google_code_import/importer.rb @@ -0,0 +1,377 @@ +module Gitlab + module GoogleCodeImport + class Importer + attr_reader :project, :repo + + def initialize(project) + @project = project + + import_data = project.import_data.try(:data) + repo_data = import_data["repo"] if import_data + @repo = GoogleCodeImport::Repository.new(repo_data) + + @closed_statuses = [] + @known_labels = Set.new + end + + def execute + return true unless repo.valid? + + import_status_labels + + import_labels + + import_issues + + true + end + + private + + def user_map + @user_map ||= begin + user_map = Hash.new do |hash, user| + # Replace ... by \.\.\., so `johnsm...@gmail.com` isn't autolinked. + Client.mask_email(user).sub("...", "\\.\\.\\.") + end + + import_data = project.import_data.try(:data) + stored_user_map = import_data["user_map"] if import_data + user_map.update(stored_user_map) if stored_user_map + + user_map + end + end + + def import_status_labels + repo.raw_data["issuesConfig"]["statuses"].each do |status| + closed = !status["meansOpen"] + @closed_statuses << status["status"] if closed + + name = nice_status_name(status["status"]) + create_label(name) + @known_labels << name + end + end + + def import_labels + repo.raw_data["issuesConfig"]["labels"].each do |label| + name = nice_label_name(label["label"]) + create_label(name) + @known_labels << name + end + end + + def import_issues + return unless repo.issues + + while raw_issue = repo.issues.shift + author = user_map[raw_issue["author"]["name"]] + date = DateTime.parse(raw_issue["published"]).to_formatted_s(:long) + + comments = raw_issue["comments"]["items"] + issue_comment = comments.shift + + content = format_content(issue_comment["content"]) + attachments = format_attachments(raw_issue["id"], 0, issue_comment["attachments"]) + + body = format_issue_body(author, date, content, attachments) + + labels = [] + raw_issue["labels"].each do |label| + name = nice_label_name(label) + labels << name + + unless @known_labels.include?(name) + create_label(name) + @known_labels << name + end + end + labels << nice_status_name(raw_issue["status"]) + + assignee_id = nil + if raw_issue.has_key?("owner") + username = user_map[raw_issue["owner"]["name"]] + + if username.start_with?("@") + username = username[1..-1] + + if user = User.find_by(username: username) + assignee_id = user.id + end + end + end + + issue = Issue.create!( + project_id: project.id, + title: raw_issue["title"], + description: body, + author_id: project.creator_id, + assignee_id: assignee_id, + state: raw_issue["state"] == "closed" ? "closed" : "opened" + ) + issue.add_labels_by_names(labels) + + if issue.iid != raw_issue["id"] + issue.update_attribute(:iid, raw_issue["id"]) + end + + import_issue_comments(issue, comments) + end + end + + def import_issue_comments(issue, comments) + Note.transaction do + while raw_comment = comments.shift + next if raw_comment.has_key?("deletedBy") + + content = format_content(raw_comment["content"]) + updates = format_updates(raw_comment["updates"]) + attachments = format_attachments(issue.iid, raw_comment["id"], raw_comment["attachments"]) + + next if content.blank? && updates.blank? && attachments.blank? + + author = user_map[raw_comment["author"]["name"]] + date = DateTime.parse(raw_comment["published"]).to_formatted_s(:long) + + body = format_issue_comment_body( + raw_comment["id"], + author, + date, + content, + updates, + attachments + ) + + # Needs to match order of `comment_columns` below. + Note.create!( + project_id: project.id, + noteable_type: "Issue", + noteable_id: issue.id, + author_id: project.creator_id, + note: body + ) + end + end + end + + def nice_label_color(name) + case name + when /\AComponent:/ + "#fff39e" + when /\AOpSys:/ + "#e2e2e2" + when /\AMilestone:/ + "#fee3ff" + + when *@closed_statuses.map { |s| nice_status_name(s) } + "#cfcfcf" + when "Status: New" + "#428bca" + when "Status: Accepted" + "#5cb85c" + when "Status: Started" + "#8e44ad" + + when "Priority: Critical" + "#ffcfcf" + when "Priority: High" + "#deffcf" + when "Priority: Medium" + "#fff5cc" + when "Priority: Low" + "#cfe9ff" + + when "Type: Defect" + "#d9534f" + when "Type: Enhancement" + "#44ad8e" + when "Type: Task" + "#4b6dd0" + when "Type: Review" + "#8e44ad" + when "Type: Other" + "#7f8c8d" + else + "#e2e2e2" + end + end + + def nice_label_name(name) + name.sub("-", ": ") + end + + def nice_status_name(name) + "Status: #{name}" + end + + def linkify_issues(s) + s = s.gsub(/([Ii]ssue) ([0-9]+)/, '\1 #\2') + s = s.gsub(/([Cc]omment) #([0-9]+)/, '\1 \2') + s + end + + def escape_for_markdown(s) + # No headings and lists + s = s.gsub(/^#/, "\\#") + s = s.gsub(/^-/, "\\-") + + # No inline code + s = s.gsub("`", "\\`") + + # Carriage returns make me sad + s = s.gsub("\r", "") + + # Markdown ignores single newlines, but we need them as
    . + s = s.gsub("\n", " \n") + + s + end + + def create_label(name) + color = nice_label_color(name) + Label.create!(project_id: project.id, name: name, color: color) + end + + def format_content(raw_content) + linkify_issues(escape_for_markdown(raw_content)) + end + + def format_updates(raw_updates) + updates = [] + + if raw_updates.has_key?("status") + updates << "*Status: #{raw_updates["status"]}*" + end + + if raw_updates.has_key?("owner") + updates << "*Owner: #{user_map[raw_updates["owner"]]}*" + end + + if raw_updates.has_key?("cc") + cc = raw_updates["cc"].map do |l| + deleted = l.start_with?("-") + l = l[1..-1] if deleted + l = user_map[l] + l = "~~#{l}~~" if deleted + l + end + + updates << "*Cc: #{cc.join(", ")}*" + end + + if raw_updates.has_key?("labels") + labels = raw_updates["labels"].map do |l| + deleted = l.start_with?("-") + l = l[1..-1] if deleted + l = nice_label_name(l) + l = "~~#{l}~~" if deleted + l + end + + updates << "*Labels: #{labels.join(", ")}*" + end + + if raw_updates.has_key?("mergedInto") + updates << "*Merged into: ##{raw_updates["mergedInto"]}*" + end + + if raw_updates.has_key?("blockedOn") + blocked_ons = raw_updates["blockedOn"].map do |raw_blocked_on| + name, id = raw_blocked_on.split(":", 2) + + deleted = name.start_with?("-") + name = name[1..-1] if deleted + + text = + if name == project.import_source + "##{id}" + else + "#{project.namespace.path}/#{name}##{id}" + end + text = "~~#{text}~~" if deleted + text + end + updates << "*Blocked on: #{blocked_ons.join(", ")}*" + end + + if raw_updates.has_key?("blocking") + blockings = raw_updates["blocking"].map do |raw_blocked_on| + name, id = raw_blocked_on.split(":", 2) + + deleted = name.start_with?("-") + name = name[1..-1] if deleted + + text = + if name == project.import_source + "##{id}" + else + "#{project.namespace.path}/#{name}##{id}" + end + text = "~~#{text}~~" if deleted + text + end + updates << "*Blocking: #{blockings.join(", ")}*" + end + + updates + end + + def format_attachments(issue_id, comment_id, raw_attachments) + return [] unless raw_attachments + + raw_attachments.map do |attachment| + next if attachment["isDeleted"] + + filename = attachment["fileName"] + link = "https://storage.googleapis.com/google-code-attachments/#{@repo.name}/issue-#{issue_id}/comment-#{comment_id}/#{filename}" + + text = "[#{filename}](#{link})" + text = "!#{text}" if filename =~ /\.(png|jpg|jpeg|gif|bmp|tiff)\z/ + text + end.compact + end + + def format_issue_comment_body(id, author, date, content, updates, attachments) + body = [] + body << "*Comment #{id} by #{author} on #{date}*" + body << "---" + + if content.blank? + content = "*(No comment has been entered for this change)*" + end + body << content + + if updates.any? + body << "---" + body += updates + end + + if attachments.any? + body << "---" + body += attachments + end + + body.join("\n\n") + end + + def format_issue_body(author, date, content, attachments) + body = [] + body << "*By #{author} on #{date} (imported from Google Code)*" + body << "---" + + if content.blank? + content = "*(No description has been entered for this issue)*" + end + body << content + + if attachments.any? + body << "---" + body += attachments + end + + body.join("\n\n") + end + end + end +end diff --git a/lib/gitlab/google_code_import/project_creator.rb b/lib/gitlab/google_code_import/project_creator.rb new file mode 100644 index 0000000000..0cfeaf9d61 --- /dev/null +++ b/lib/gitlab/google_code_import/project_creator.rb @@ -0,0 +1,37 @@ +module Gitlab + module GoogleCodeImport + class ProjectCreator + attr_reader :repo, :namespace, :current_user, :user_map + + def initialize(repo, namespace, current_user, user_map = nil) + @repo = repo + @namespace = namespace + @current_user = current_user + @user_map = user_map + end + + def execute + project = ::Projects::CreateService.new(current_user, + name: repo.name, + path: repo.name, + description: repo.summary, + namespace: namespace, + creator: current_user, + visibility_level: Gitlab::VisibilityLevel::PUBLIC, + import_type: "google_code", + import_source: repo.name, + import_url: repo.import_url + ).execute + + import_data = project.create_import_data( + data: { + "repo" => repo.raw_data, + "user_map" => user_map + } + ) + + project + end + end + end +end diff --git a/lib/gitlab/google_code_import/repository.rb b/lib/gitlab/google_code_import/repository.rb new file mode 100644 index 0000000000..ad33fc2cad --- /dev/null +++ b/lib/gitlab/google_code_import/repository.rb @@ -0,0 +1,43 @@ +module Gitlab + module GoogleCodeImport + class Repository + attr_accessor :raw_data + + def initialize(raw_data) + @raw_data = raw_data + end + + def valid? + raw_data.is_a?(Hash) && raw_data["kind"] == "projecthosting#project" + end + + def id + raw_data["externalId"] + end + + def name + raw_data["name"] + end + + def summary + raw_data["summary"] + end + + def description + raw_data["description"] + end + + def git? + raw_data["versionControlSystem"] == "git" + end + + def import_url + raw_data["repositoryUrls"].first + end + + def issues + raw_data["issues"] && raw_data["issues"]["items"] + end + end + end +end diff --git a/lib/gitlab/graphs/commits.rb b/lib/gitlab/graphs/commits.rb new file mode 100644 index 0000000000..2122339d2d --- /dev/null +++ b/lib/gitlab/graphs/commits.rb @@ -0,0 +1,49 @@ +module Gitlab + module Graphs + class Commits + attr_reader :commits, :start_date, :end_date, :duration, + :commits_per_week_days, :commits_per_time, :commits_per_month + + def initialize(commits) + @commits = commits + @start_date = commits.last.committed_date.to_date + @end_date = commits.first.committed_date.to_date + @duration = (@end_date - @start_date).to_i + + collect_data + end + + def authors + @authors ||= @commits.map(&:author_email).uniq.size + end + + def commit_per_day + @commit_per_day ||= (@commits.size.to_f / @duration).round(1) + end + + def collect_data + @commits_per_week_days = {} + Date::DAYNAMES.each { |day| @commits_per_week_days[day] = 0 } + + @commits_per_time = {} + (0..23).to_a.each { |hour| @commits_per_time[hour] = 0 } + + @commits_per_month = {} + (1..31).to_a.each { |day| @commits_per_month[day] = 0 } + + @commits.each do |commit| + hour = commit.committed_date.strftime('%k').to_i + day_of_month = commit.committed_date.strftime('%e').to_i + weekday = commit.committed_date.strftime('%A') + + @commits_per_week_days[weekday] ||= 0 + @commits_per_week_days[weekday] += 1 + @commits_per_time[hour] ||= 0 + @commits_per_time[hour] += 1 + @commits_per_month[day_of_month] ||= 0 + @commits_per_month[day_of_month] += 1 + end + end + end + end +end diff --git a/lib/gitlab/import_formatter.rb b/lib/gitlab/import_formatter.rb new file mode 100644 index 0000000000..72e041a90b --- /dev/null +++ b/lib/gitlab/import_formatter.rb @@ -0,0 +1,15 @@ +module Gitlab + class ImportFormatter + def comment(author, date, body) + "\n\n*By #{author} on #{date}*\n\n#{body}" + end + + def comments_header + "\n\n\n**Imported comments:**\n" + end + + def author_line(author, body) + "*Created by: #{author}*\n\n#{body}" + end + end +end diff --git a/lib/gitlab/inline_diff.rb b/lib/gitlab/inline_diff.rb index 89c8e0680c..3517ecdf5c 100644 --- a/lib/gitlab/inline_diff.rb +++ b/lib/gitlab/inline_diff.rb @@ -5,7 +5,7 @@ module Gitlab START = "#!idiff-start!#" FINISH = "#!idiff-finish!#" - def processing diff_arr + def processing(diff_arr) indexes = _indexes_of_changed_lines diff_arr indexes.each do |index| @@ -52,7 +52,7 @@ module Gitlab diff_arr end - def _indexes_of_changed_lines diff_arr + def _indexes_of_changed_lines(diff_arr) chain_of_first_symbols = "" diff_arr.each_with_index do |line, i| chain_of_first_symbols += line[0] @@ -68,7 +68,7 @@ module Gitlab indexes end - def replace_markers line + def replace_markers(line) line.gsub!(START, "") line.gsub!(FINISH, "") line diff --git a/lib/gitlab/issues_labels.rb b/lib/gitlab/issues_labels.rb index 0d34976736..1bec608829 100644 --- a/lib/gitlab/issues_labels.rb +++ b/lib/gitlab/issues_labels.rb @@ -15,7 +15,6 @@ module Gitlab { title: "support", color: yellow }, { title: "discussion", color: blue }, { title: "suggestion", color: blue }, - { title: "feature", color: green }, { title: "enhancement", color: green } ] diff --git a/lib/gitlab/key_fingerprint.rb b/lib/gitlab/key_fingerprint.rb new file mode 100644 index 0000000000..baf52ff750 --- /dev/null +++ b/lib/gitlab/key_fingerprint.rb @@ -0,0 +1,55 @@ +module Gitlab + class KeyFingerprint + include Gitlab::Popen + + attr_accessor :key + + def initialize(key) + @key = key + end + + def fingerprint + cmd_status = 0 + cmd_output = '' + + Tempfile.open('gitlab_key_file') do |file| + file.puts key + file.rewind + + cmd = [] + cmd.push *%W(ssh-keygen) + cmd.push *%W(-E md5) if explicit_fingerprint_algorithm? + cmd.push *%W(-lf #{file.path}) + + cmd_output, cmd_status = popen(cmd, '/tmp') + end + + return nil unless cmd_status.zero? + + # 16 hex bytes separated by ':', optionally starting with "MD5:" + fingerprint_matches = cmd_output.match(/(MD5:)?(?(\h{2}:){15}\h{2})/) + return nil unless fingerprint_matches + + fingerprint_matches[:fingerprint] + end + + private + + def explicit_fingerprint_algorithm? + # OpenSSH 6.8 introduces a new default output format for fingerprints. + # Check the version and decide which command to use. + + version_output, version_status = popen(%W(ssh -V)) + return false unless version_status.zero? + + version_matches = version_output.match(/OpenSSH_(?\d+)\.(?\d+)/) + return false unless version_matches + + version_info = Gitlab::VersionInfo.new(version_matches[:major].to_i, version_matches[:minor].to_i) + + required_version_info = Gitlab::VersionInfo.new(6, 8) + + version_info >= required_version_info + end + end +end diff --git a/lib/gitlab/ldap/access.rb b/lib/gitlab/ldap/access.rb index 62709a1294..960fb3849b 100644 --- a/lib/gitlab/ldap/access.rb +++ b/lib/gitlab/ldap/access.rb @@ -1,18 +1,21 @@ +# LDAP authorization model +# +# * Check if we are allowed access (not blocked) +# module Gitlab module LDAP class Access - attr_reader :adapter + attr_reader :adapter, :provider, :user - def self.open(&block) - Gitlab::LDAP::Adapter.open do |adapter| - block.call(self.new(adapter)) + def self.open(user, &block) + Gitlab::LDAP::Adapter.open(user.ldap_identity.provider) do |adapter| + block.call(self.new(user, adapter)) end end def self.allowed?(user) - self.open do |access| - if access.allowed?(user) - # GitLab EE LDAP code goes here + self.open(user) do |access| + if access.allowed? user.last_credential_check_at = Time.now user.save true @@ -22,19 +25,38 @@ module Gitlab end end - def initialize(adapter=nil) + def initialize(user, adapter=nil) @adapter = adapter + @user = user + @provider = user.ldap_identity.provider end - def allowed?(user) - if Gitlab::LDAP::Person.find_by_dn(user.extern_uid, adapter) - !Gitlab::LDAP::Person.active_directory_disabled?(user.extern_uid, adapter) + def allowed? + if Gitlab::LDAP::Person.find_by_dn(user.ldap_identity.extern_uid, adapter) + return true unless ldap_config.active_directory + + # Block user in GitLab if he/she was blocked in AD + if Gitlab::LDAP::Person.disabled_via_active_directory?(user.ldap_identity.extern_uid, adapter) + user.block unless user.blocked? + false + else + user.activate if user.blocked? + true + end else false end rescue false end + + def adapter + @adapter ||= Gitlab::LDAP::Adapter.new(provider) + end + + def ldap_config + Gitlab::LDAP::Config.new(provider) + end end end end diff --git a/lib/gitlab/ldap/adapter.rb b/lib/gitlab/ldap/adapter.rb index ca239bea88..577a890a7d 100644 --- a/lib/gitlab/ldap/adapter.rb +++ b/lib/gitlab/ldap/adapter.rb @@ -1,55 +1,28 @@ module Gitlab module LDAP class Adapter - attr_reader :ldap + attr_reader :provider, :ldap - def self.open(&block) - Net::LDAP.open(adapter_options) do |ldap| - block.call(self.new(ldap)) + def self.open(provider, &block) + Net::LDAP.open(config(provider).adapter_options) do |ldap| + block.call(self.new(provider, ldap)) end end - def self.config - Gitlab.config.ldap + def self.config(provider) + Gitlab::LDAP::Config.new(provider) end - def self.adapter_options - encryption = - case config['method'].to_s - when 'ssl' - :simple_tls - when 'tls' - :start_tls - else - nil - end - - options = { - host: config['host'], - port: config['port'], - encryption: encryption - } - - auth_options = { - auth: { - method: :simple, - username: config['bind_dn'], - password: config['password'] - } - } - - if config['password'] || config['bind_dn'] - options.merge!(auth_options) - end - options + def initialize(provider, ldap=nil) + @provider = provider + @ldap = ldap || Net::LDAP.new(config.adapter_options) end - - def initialize(ldap=nil) - @ldap = ldap || Net::LDAP.new(self.class.adapter_options) + def config + Gitlab::LDAP::Config.new(provider) end - def users(field, value) + def users(field, value, limit = nil) if field.to_sym == :dn options = { base: value, @@ -57,13 +30,13 @@ module Gitlab } else options = { - base: config['base'], + base: config.base, filter: Net::LDAP::Filter.eq(field, value) } end - if config['user_filter'].present? - user_filter = Net::LDAP::Filter.construct(config['user_filter']) + if config.user_filter.present? + user_filter = Net::LDAP::Filter.construct(config.user_filter) options[:filter] = if options[:filter] Net::LDAP::Filter.join(options[:filter], user_filter) @@ -72,12 +45,16 @@ module Gitlab end end + if limit.present? + options.merge!(size: limit) + end + entries = ldap_search(options).select do |entry| entry.respond_to? config.uid end entries.map do |entry| - Gitlab::LDAP::Person.new(entry) + Gitlab::LDAP::Person.new(entry, provider) end end @@ -86,7 +63,10 @@ module Gitlab end def dn_matches_filter?(dn, filter) - ldap_search(base: dn, filter: filter, scope: Net::LDAP::SearchScope_BaseObject, attributes: %w{dn}).any? + ldap_search(base: dn, + filter: filter, + scope: Net::LDAP::SearchScope_BaseObject, + attributes: %w{dn}).any? end def ldap_search(*args) @@ -104,12 +84,6 @@ module Gitlab results end end - - private - - def config - @config ||= self.class.config - end end end end diff --git a/lib/gitlab/ldap/authentication.rb b/lib/gitlab/ldap/authentication.rb new file mode 100644 index 0000000000..649cf3194b --- /dev/null +++ b/lib/gitlab/ldap/authentication.rb @@ -0,0 +1,71 @@ +# This calls helps to authenticate to LDAP by providing username and password +# +# Since multiple LDAP servers are supported, it will loop through all of them +# until a valid bind is found +# + +module Gitlab + module LDAP + class Authentication + def self.login(login, password) + return unless Gitlab::LDAP::Config.enabled? + return unless login.present? && password.present? + + auth = nil + # loop through providers until valid bind + providers.find do |provider| + auth = new(provider) + auth.login(login, password) # true will exit the loop + end + + # If (login, password) was invalid for all providers, the value of auth is now the last + # Gitlab::LDAP::Authentication instance we tried. + auth.user + end + + def self.providers + Gitlab::LDAP::Config.providers + end + + attr_accessor :provider, :ldap_user + + def initialize(provider) + @provider = provider + end + + def login(login, password) + @ldap_user = adapter.bind_as( + filter: user_filter(login), + size: 1, + password: password + ) + end + + def adapter + OmniAuth::LDAP::Adaptor.new(config.options.symbolize_keys) + end + + def config + Gitlab::LDAP::Config.new(provider) + end + + def user_filter(login) + filter = Net::LDAP::Filter.equals(config.uid, login) + + # Apply LDAP user filter if present + if config.user_filter.present? + filter = Net::LDAP::Filter.join( + filter, + Net::LDAP::Filter.construct(config.user_filter) + ) + end + filter + end + + def user + return nil unless ldap_user + Gitlab::LDAP::User.find_by_uid_and_provider(ldap_user.dn, provider) + end + end + end +end diff --git a/lib/gitlab/ldap/config.rb b/lib/gitlab/ldap/config.rb new file mode 100644 index 0000000000..d2ffa2e1fe --- /dev/null +++ b/lib/gitlab/ldap/config.rb @@ -0,0 +1,122 @@ +# Load a specific server configuration +module Gitlab + module LDAP + class Config + attr_accessor :provider, :options + + def self.enabled? + Gitlab.config.ldap.enabled + end + + def self.servers + Gitlab.config.ldap.servers.values + end + + def self.providers + servers.map {|server| server['provider_name'] } + end + + def self.valid_provider?(provider) + providers.include?(provider) + end + + def self.invalid_provider(provider) + raise "Unknown provider (#{provider}). Available providers: #{providers}" + end + + def initialize(provider) + if self.class.valid_provider?(provider) + @provider = provider + else + self.class.invalid_provider(provider) + end + @options = config_for(@provider) # Use @provider, not provider + end + + def enabled? + base_config.enabled + end + + def adapter_options + { + host: options['host'], + port: options['port'], + encryption: encryption + }.tap do |options| + options.merge!(auth_options) if has_auth? + end + end + + def base + options['base'] + end + + def uid + options['uid'] + end + + def sync_ssh_keys? + sync_ssh_keys.present? + end + + # The LDAP attribute in which the ssh keys are stored + def sync_ssh_keys + options['sync_ssh_keys'] + end + + def user_filter + options['user_filter'] + end + + def group_base + options['group_base'] + end + + def admin_group + options['admin_group'] + end + + def active_directory + options['active_directory'] + end + + def block_auto_created_users + options['block_auto_created_users'] + end + + protected + def base_config + Gitlab.config.ldap + end + + def config_for(provider) + base_config.servers.values.find { |server| server['provider_name'] == provider } + end + + def encryption + case options['method'].to_s + when 'ssl' + :simple_tls + when 'tls' + :start_tls + else + nil + end + end + + def auth_options + { + auth: { + method: :simple, + username: options['bind_dn'], + password: options['password'] + } + } + end + + def has_auth? + options['password'] || options['bind_dn'] + end + end + end +end diff --git a/lib/gitlab/ldap/person.rb b/lib/gitlab/ldap/person.rb index 9ad6618bd4..b81f3e8e8f 100644 --- a/lib/gitlab/ldap/person.rb +++ b/lib/gitlab/ldap/person.rb @@ -6,24 +6,25 @@ module Gitlab # Source: http://ctogonewild.com/2009/09/03/bitmask-searches-in-ldap/ AD_USER_DISABLED = Net::LDAP::Filter.ex("userAccountControl:1.2.840.113556.1.4.803", "2") - def self.find_by_uid(uid, adapter=nil) - adapter ||= Gitlab::LDAP::Adapter.new - adapter.user(config.uid, uid) + attr_accessor :entry, :provider + + def self.find_by_uid(uid, adapter) + uid = Net::LDAP::Filter.escape(uid) + adapter.user(adapter.config.uid, uid) end - def self.find_by_dn(dn, adapter=nil) - adapter ||= Gitlab::LDAP::Adapter.new + def self.find_by_dn(dn, adapter) adapter.user('dn', dn) end - def self.active_directory_disabled?(dn, adapter=nil) - adapter ||= Gitlab::LDAP::Adapter.new + def self.disabled_via_active_directory?(dn, adapter) adapter.dn_matches_filter?(dn, AD_USER_DISABLED) end - def initialize(entry) + def initialize(entry, provider) Rails.logger.debug { "Instantiating #{self.class.name} with LDIF:\n#{entry.to_ldif}" } @entry = entry + @provider = provider end def name @@ -38,6 +39,10 @@ module Gitlab uid end + def email + entry.try(:mail) + end + def dn entry.dn end @@ -48,12 +53,8 @@ module Gitlab @entry end - def adapter - @adapter ||= Gitlab::LDAP::Adapter.new - end - def config - @config ||= Gitlab.config.ldap + @config ||= Gitlab::LDAP::Config.new(provider) end end end diff --git a/lib/gitlab/ldap/user.rb b/lib/gitlab/ldap/user.rb index be3fcc4f03..f7f3ba9ad7 100644 --- a/lib/gitlab/ldap/user.rb +++ b/lib/gitlab/ldap/user.rb @@ -1,4 +1,4 @@ -require 'gitlab/oauth/user' +require 'gitlab/o_auth/user' # LDAP extension for User model # @@ -10,103 +10,61 @@ module Gitlab module LDAP class User < Gitlab::OAuth::User class << self - def find_or_create(auth) - @auth = auth - - if uid.blank? || email.blank? || username.blank? - raise_error("Account must provide a dn, uid and email address") - end - - user = find(auth) - - unless user - # Look for user with same emails - # - # Possible cases: - # * When user already has account and need to link their LDAP account. - # * LDAP uid changed for user with same email and we need to update their uid - # - user = find_user(email) - - if user - user.update_attributes(extern_uid: uid, provider: provider) - log.info("(LDAP) Updating legacy LDAP user #{email} with extern_uid => #{uid}") - else - # Create a new user inside GitLab database - # based on LDAP credentials - # - # - user = create(auth) - end - end - - user - end - - def find_user(email) - user = model.find_by(email: email) - - # If no user found and allow_username_or_email_login is true - # we look for user by extracting part of their email - if !user && email && ldap_conf['allow_username_or_email_login'] - uname = email.partition('@').first - # Strip apostrophes since they are disallowed as part of username - username = uname.gsub("'", "") - user = model.find_by(username: username) - end - - user - end - - def authenticate(login, password) - # Check user against LDAP backend if user is not authenticated - # Only check with valid login and password to prevent anonymous bind results - return nil unless ldap_conf.enabled && login.present? && password.present? - - ldap = OmniAuth::LDAP::Adaptor.new(ldap_conf) - filter = Net::LDAP::Filter.eq(ldap.uid, login) - - # Apply LDAP user filter if present - if ldap_conf['user_filter'].present? - user_filter = Net::LDAP::Filter.construct(ldap_conf['user_filter']) - filter = Net::LDAP::Filter.join(filter, user_filter) - end - - ldap_user = ldap.bind_as( - filter: filter, - size: 1, - password: password - ) - - find_by_uid(ldap_user.dn) if ldap_user - end - - private - - def find_by_uid_and_provider - find_by_uid(uid) - end - - def find_by_uid(uid) + def find_by_uid_and_provider(uid, provider) # LDAP distinguished name is case-insensitive - model.where("provider = ? and lower(extern_uid) = ?", provider, uid.downcase).last + identity = ::Identity. + where(provider: provider). + where('lower(extern_uid) = ?', uid.downcase).last + identity && identity.user end + end - def username - auth.info.nickname.to_s.force_encoding("utf-8") - end + def initialize(auth_hash) + super + update_user_attributes + end - def provider - 'ldap' - end + # instance methods + def gl_user + @gl_user ||= find_by_uid_and_provider || find_by_email || build_new_user + end - def raise_error(message) - raise OmniAuth::Error, "(LDAP) " + message - end + def find_by_uid_and_provider + self.class.find_by_uid_and_provider( + auth_hash.uid.downcase, auth_hash.provider) + end - def ldap_conf - Gitlab.config.ldap - end + def find_by_email + ::User.find_by(email: auth_hash.email) + end + + def update_user_attributes + return unless persisted? + + gl_user.skip_reconfirmation! + gl_user.email = auth_hash.email + + # Build new identity only if we dont have have same one + gl_user.identities.find_or_initialize_by(provider: auth_hash.provider, + extern_uid: auth_hash.uid) + + gl_user + end + + def changed? + gl_user.changed? || gl_user.identities.any?(&:changed?) + end + + def block_after_signup? + ldap_config.block_auto_created_users + end + + def allowed? + Gitlab::LDAP::Access.allowed?(gl_user) + end + + def ldap_config + Gitlab::LDAP::Config.new(auth_hash.provider) end end end diff --git a/lib/gitlab/logger.rb b/lib/gitlab/logger.rb index 64cf3303ea..59b21149a9 100644 --- a/lib/gitlab/logger.rb +++ b/lib/gitlab/logger.rb @@ -1,5 +1,9 @@ module Gitlab class Logger < ::Logger + def self.file_name + file_name_noext + '.log' + end + def self.error(message) build.error(message) end @@ -15,7 +19,7 @@ module Gitlab tail_output.split("\n") end - def self.read_latest_for filename + def self.read_latest_for(filename) path = Rails.root.join("log", filename) tail_output, _ = Gitlab::Popen.popen(%W(tail -n 2000 #{path})) tail_output.split("\n") diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb index b248d8f943..47c456d8dc 100644 --- a/lib/gitlab/markdown.rb +++ b/lib/gitlab/markdown.rb @@ -1,3 +1,6 @@ +require 'html/pipeline' +require 'html/pipeline/gitlab' + module Gitlab # Custom parser for GitLab-flavored Markdown # @@ -11,6 +14,7 @@ module Gitlab # * !123 for merge requests # * $123 for snippets # * 123456 for commits + # * 123456...7890123 for commit ranges (comparisons) # # It also parses Emoji codes to insert images. See # http://www.emoji-cheat-sheet.com/ for a list of the supported icons. @@ -28,14 +32,26 @@ module Gitlab module Markdown include IssuesHelper - attr_reader :html_options + attr_reader :options, :html_options # Public: Parse the provided text with GitLab-Flavored Markdown # # text - the source text - # project - extra options for the reference links as given to link_to + # project - the project # html_options - extra options for the reference links as given to link_to def gfm(text, project = @project, html_options = {}) + gfm_with_options(text, {}, project, html_options) + end + + # Public: Parse the provided text with GitLab-Flavored Markdown + # + # text - the source text + # options - parse_tasks - render tasks + # - xhtml - output XHTML instead of HTML + # - reference_only_path - Use relative path for reference links + # project - the project + # html_options - extra options for the reference links as given to link_to + def gfm_with_options(text, options = {}, project = @project, html_options = {}) return text if text.nil? # Duplicate the string so we don't alter the original, then call to_str @@ -43,8 +59,61 @@ module Gitlab # for gsub calls to work as we need them to. text = text.dup.to_str + options.reverse_merge!( + parse_tasks: false, + xhtml: false, + reference_only_path: true + ) + + @options = options @html_options = html_options + # TODO: add popups with additional information + + # Used markdown pipelines in GitLab: + # GitlabEmojiFilter - performs emoji replacement. + # SanitizationFilter - remove unsafe HTML tags and attributes + # + # see https://gitlab.com/gitlab-org/html-pipeline-gitlab for more filters + filters = [ + HTML::Pipeline::Gitlab::GitlabEmojiFilter, + HTML::Pipeline::SanitizationFilter + ] + + whitelist = HTML::Pipeline::SanitizationFilter::WHITELIST + whitelist[:attributes][:all].push('class', 'id') + whitelist[:elements].push('span') + + # Remove the rel attribute that the sanitize gem adds, and remove the + # href attribute if it contains inline javascript + fix_anchors = lambda do |env| + name, node = env[:node_name], env[:node] + if name == 'a' + node.remove_attribute('rel') + if node['href'] && node['href'].match('javascript:') + node.remove_attribute('href') + end + end + end + whitelist[:transformers].push(fix_anchors) + + markdown_context = { + asset_root: Gitlab.config.gitlab.url, + asset_host: Gitlab::Application.config.asset_host, + whitelist: whitelist + } + + markdown_pipeline = HTML::Pipeline::Gitlab.new(filters).pipeline + + result = markdown_pipeline.call(text, markdown_context) + + save_options = 0 + if options[:xhtml] + save_options |= Nokogiri::XML::Node::SaveOptions::AS_XHTML + end + + text = result[:output].to_html(save_with: save_options) + # Extract pre blocks so they are not altered # from http://github.github.com/github-flavored-markdown/ text.gsub!(%r{

    }) + expect(markdown(actual, no_header_anchors: true)). + to match(%r{Working around ##{issue.iid}

    BfIE$srT>nbZl6e;&A} z1!(Qja;;jJ(H=T-I%U&THZw&`>WeZUlzT0uCC}liE^Aa>>O&^RX4waH3Q4dcN(To^ zSqZY$U?BEjrXHd2C65uP!FcJFVe?LtU+WXMcI4~H$w+ew;QBA^DSWGQJGxum^;LnrK|aX#jopV^gW z5%srnO0ty7JkBY?CmT20cUiOqkYBv_tJEqh0p7>W)px1x?>Pb~;pY&Xo*fpgd{Veg zMsAs^q_LF0Q%FT$Zu=0cFJ!;f!L(oIDKmzTuM4(MIQXTu zk3vX)R^M{-RNX$ldFQDvd7)=gMrPTqNpjuc?-oeQRLa~^a@?6<7INtQ=N8YXxAV?Z zt~P}S5BrTdi%1>S?GL-|Ww3GN+Fu?ytT4Svo83P!+g6S>e6^{du%>rg+?(KIhbE{u z_rk&ZcQQggD9yx+w0>IXpVHU4gLsSDv=?688(w|kLCFfACvwwKAt4K*;Xd-4=t#z~ zuBnnACbhb!2tbuK(XqY{Xt6na-V6KHEoU2#iaY)RxX97bE6RqhEyO2biv9|V69IU_ zQlp8!TI_diZOd8&8d*|SGqCOBMa9{7{!afo$KZztzBEyPF@Y{0Qj(MZCI9^A8=+I9 zxVC>VH^lY*kKA(34htNb2%Umk4-CIW%QDp__vg7MgF-sb4wpC6oT_YQ62^=Z`&vq| zp<@k3>i-v1%T&FL8fsSmjk|!}_Gv-Ioe!>!h#6tW2_Z9wMask@YCrC)QmBpa_Z)v$ zb*nNY;vYXkbkp!ZgQIoLeqe**pZ2%O$JuPWF=qK9VRz$!cK>=WLdPAm9p57c6`0nl z*~!-=y7k^>!_3!j8|RZ+7XJNua^ZH+sl;P8v>*N| zHTZDy^{CQOx6)TdmXiT<)R#dzM&C^~_#l(=7~#yE*jJKWpR_L_?&*)`mTf~nSMT(o z%$h5;*C&J~aDKV;PVKWV;n2ysSD=xi+Y4U~Ki~X4M5R*Bx4tQ%3Db#Mp6kBy`d^YB z{upe6^YgX36@!!Epokp{Q*QH}`+r<|s2FzSwbI@nzWe|EqP^^k^nOs(y4WiAPATB? NC)(YyZbxA1e*ua1Z^i%s literal 0 HcmV?d00001 diff --git a/doc/workflow/gitdashflow.png b/doc/workflow/gitdashflow.png new file mode 100644 index 0000000000000000000000000000000000000000..f2f091dd10b1b63a911977cdc4c059ac2f1925e6 GIT binary patch literal 184726 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 z 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ℜ_jFnd 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 Z@?A5C)q3pvDPx3n#x! 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

    }) + expect(markdown(actual, no_header_anchors: true)). + to match(%r{Apply !#{merge_request.iid}}) end it "should add ids and links to headers" do # Test every rule except nested tags. text = '..Ab_c-d. e..' id = 'ab_c-d-e' - markdown("# #{text}").should match(%r{

    #{text}

    }) - markdown("# #{text}", {no_header_anchors:true}).should == "

    #{text}

    " + expect(markdown("# #{text}")). + to match(%r{

    #{text}

    }) + expect(markdown("# #{text}", {no_header_anchors:true})). + to eq("

    #{text}

    ") id = 'link-text' - markdown("# [link text](url) ![img alt](url)").should match( + expect(markdown("# [link text](url) ![img alt](url)")).to match( %r{

    link text ]*>

    } ) end @@ -419,14 +595,37 @@ describe GitlabMarkdownHelper do actual = "\n* dark: ##{issue.iid}\n* light by @#{member.user.username}" - markdown(actual).should match(%r{
  • dark: ##{issue.iid}
  • }) - markdown(actual).should match(%r{
  • light by @#{member.user.username}
  • }) + expect(markdown(actual)). + to match(%r{
  • dark: ##{issue.iid}
  • }) + expect(markdown(actual)). + to match(%r{
  • light by @#{member.user.username}
  • }) + end + + it "should not link the apostrophe to issue 39" do + project.team << [user, :master] + allow(project.issues). + to receive(:where).with(iid: '39').and_return([issue]) + + actual = "Yes, it is @#{member.user.username}'s task." + expected = /Yes, it is @#{member.user.username}<\/a>'s task/ + expect(markdown(actual)).to match(expected) + end + + it "should not link the apostrophe to issue 39 in code blocks" do + project.team << [user, :master] + allow(project.issues). + to receive(:where).with(iid: '39').and_return([issue]) + + actual = "Yes, `it is @#{member.user.username}'s task.`" + expected = /Yes, it is @gfm\'s task.<\/code>/ + expect(markdown(actual)).to match(expected) end it "should handle references in " do actual = "Apply _!#{merge_request.iid}_ ASAP" - markdown(actual).should match(%r{Apply !#{merge_request.iid}}) + expect(markdown(actual)). + to match(%r{Apply !#{merge_request.iid}}) end it "should handle tables" do @@ -435,80 +634,140 @@ describe GitlabMarkdownHelper do | cell 1 | cell 2 | | cell 3 | cell 4 |} - markdown(actual).should match(/\A\n
    \n
    some code from $#{snippet.id}\nhere too\n
    \n
    \n\n\n" + target_html = "
    some code from $#{snippet.id}\nhere too\n
    \n" - helper.markdown("\n some code from $#{snippet.id}\n here too\n").should == target_html - helper.markdown("\n```\nsome code from $#{snippet.id}\nhere too\n```\n").should == target_html + expect(helper.markdown("\n some code from $#{snippet.id}\n here too\n")). + to eq(target_html) + expect(helper.markdown("\n```\nsome code from $#{snippet.id}\nhere too\n```\n")). + to eq(target_html) end it "should leave inline code untouched" do - markdown("\nDon't use `$#{snippet.id}` here.\n").should == "

    Don't use $#{snippet.id} here.

    \n" + expect(markdown("\nDon't use `$#{snippet.id}` here.\n")).to eq( + "

    Don't use $#{snippet.id} here.

    \n" + ) end it "should leave ref-like autolinks untouched" do - markdown("look at http://example.tld/#!#{merge_request.iid}").should == "

    look at http://example.tld/#!#{merge_request.iid}

    \n" + expect(markdown("look at http://example.tld/#!#{merge_request.iid}")).to eq("

    look at http://example.tld/#!#{merge_request.iid}

    \n") end it "should leave ref-like href of 'manual' links untouched" do - markdown("why not [inspect !#{merge_request.iid}](http://example.tld/#!#{merge_request.iid})").should == "

    why not inspect !#{merge_request.iid}

    \n" + expect(markdown("why not [inspect !#{merge_request.iid}](http://example.tld/#!#{merge_request.iid})")).to eq("

    why not inspect !#{merge_request.iid}

    \n") end it "should leave ref-like src of images untouched" do - markdown("screen shot: ![some image](http://example.tld/#!#{merge_request.iid})").should == "

    screen shot: \"some

    \n" + expect(markdown("screen shot: ![some image](http://example.tld/#!#{merge_request.iid})")).to eq("

    screen shot: \"some

    \n") end it "should generate absolute urls for refs" do - markdown("##{issue.iid}").should include(project_issue_url(project, issue)) + expect(markdown("##{issue.iid}")).to include(namespace_project_issue_path(project.namespace, project, issue)) end it "should generate absolute urls for emoji" do - markdown(":smile:").should include("src=\"#{url_to_image("emoji/smile")}") + expect(markdown(':smile:')).to( + include(%(src="#{Gitlab.config.gitlab.url}/assets/emoji/#{Emoji.emoji_filename('smile')}.png)) + ) end + it "should generate absolute urls for emoji if relative url is present" do + allow(Gitlab.config.gitlab).to receive(:url).and_return('http://localhost/gitlab/root') + expect(markdown(":smile:")).to include("src=\"http://localhost/gitlab/root/assets/emoji/#{Emoji.emoji_filename('smile')}.png") + end + + it "should generate absolute urls for emoji if asset_host is present" do + allow(Gitlab::Application.config).to receive(:asset_host).and_return("https://cdn.example.com") + ActionView::Base.any_instance.stub_chain(:config, :asset_host).and_return("https://cdn.example.com") + expect(markdown(":smile:")).to include("src=\"https://cdn.example.com/assets/emoji/#{Emoji.emoji_filename('smile')}.png") + end + + it "should handle relative urls for a file in master" do actual = "[GitLab API doc](doc/api/README.md)\n" expected = "

    GitLab API doc

    \n" - markdown(actual).should match(expected) + expect(markdown(actual)).to match(expected) + end + + it "should handle relative urls for a file in master with an anchor" do + actual = "[GitLab API doc](doc/api/README.md#section)\n" + expected = "

    GitLab API doc

    \n" + expect(markdown(actual)).to match(expected) + end + + it "should not handle relative urls for the current file with an anchor" do + actual = "[GitLab API doc](#section)\n" + expected = "

    GitLab API doc

    \n" + expect(markdown(actual)).to match(expected) end it "should handle relative urls for a directory in master" do actual = "[GitLab API doc](doc/api)\n" expected = "

    GitLab API doc

    \n" - markdown(actual).should match(expected) + expect(markdown(actual)).to match(expected) end it "should handle absolute urls" do actual = "[GitLab](https://www.gitlab.com)\n" expected = "

    GitLab

    \n" - markdown(actual).should match(expected) + expect(markdown(actual)).to match(expected) end it "should handle relative urls in reference links for a file in master" do actual = "[GitLab API doc][GitLab readme]\n [GitLab readme]: doc/api/README.md\n" expected = "

    GitLab API doc

    \n" - markdown(actual).should match(expected) + expect(markdown(actual)).to match(expected) end it "should handle relative urls in reference links for a directory in master" do actual = "[GitLab API doc directory][GitLab readmes]\n [GitLab readmes]: doc/api/\n" expected = "

    GitLab API doc directory

    \n" - markdown(actual).should match(expected) + expect(markdown(actual)).to match(expected) end it "should not handle malformed relative urls in reference links for a file in master" do actual = "[GitLab readme]: doc/api/README.md\n" expected = "" - markdown(actual).should match(expected) + expect(markdown(actual)).to match(expected) + end + + it 'should allow whitelisted HTML tags from the user' do + actual = '
    Term
    Definition
    ' + expect(markdown(actual)).to match(actual) + end + + it 'should sanitize tags that are not whitelisted' do + actual = 'no blinks' + expected = 'no inputs allowed no blinks' + expect(markdown(actual)).to match(expected) + expect(markdown(actual)).not_to match('<.textarea>') + expect(markdown(actual)).not_to match('<.blink>') + end + + it 'should allow whitelisted tag attributes from the user' do + actual = 'link text' + expect(markdown(actual)).to match(actual) + end + + it 'should sanitize tag attributes that are not whitelisted' do + actual = 'link text' + expected = 'link text' + expect(markdown(actual)).to match(expected) + end + + it 'should sanitize javascript in attributes' do + actual = %q(link text) + expected = 'link text' + expect(markdown(actual)).to match(expected) end end - describe "markdwon for empty repository" do + describe 'markdown for empty repository' do before do @project = empty_project @repository = empty_project.repository @@ -517,31 +776,141 @@ describe GitlabMarkdownHelper do it "should not touch relative urls" do actual = "[GitLab API doc][GitLab readme]\n [GitLab readme]: doc/api/README.md\n" expected = "

    GitLab API doc

    \n" - markdown(actual).should match(expected) + expect(markdown(actual)).to match(expected) end end describe "#render_wiki_content" do before do @wiki = double('WikiPage') - @wiki.stub(:content).and_return('wiki content') + allow(@wiki).to receive(:content).and_return('wiki content') end it "should use GitLab Flavored Markdown for markdown files" do - @wiki.stub(:format).and_return(:markdown) + allow(@wiki).to receive(:format).and_return(:markdown) - helper.should_receive(:markdown).with('wiki content') + expect(helper).to receive(:markdown).with('wiki content') helper.render_wiki_content(@wiki) end it "should use the Gollum renderer for all other file types" do - @wiki.stub(:format).and_return(:rdoc) + allow(@wiki).to receive(:format).and_return(:rdoc) formatted_content_stub = double('formatted_content') - formatted_content_stub.should_receive(:html_safe) - @wiki.stub(:formatted_content).and_return(formatted_content_stub) + expect(formatted_content_stub).to receive(:html_safe) + allow(@wiki).to receive(:formatted_content).and_return(formatted_content_stub) helper.render_wiki_content(@wiki) end end + + describe '#gfm_with_tasks' do + before(:all) do + @source_text_asterisk = <Redmine\n" \ - "" - end - - it "returns the correct issues trackers available with current tracker 'gitlab' selected" do - project_issues_trackers('gitlab').should == - "\n" \ - "" - end - - it "returns the correct issues trackers available with current tracker 'redmine' selected" do - project_issues_trackers('redmine').should == - "\n" \ - "" + describe "#project_status_css_class" do + it "returns appropriate class" do + expect(project_status_css_class("started")).to eq("active") + expect(project_status_css_class("failed")).to eq("danger") + expect(project_status_css_class("finished")).to eq("success") end end end diff --git a/spec/helpers/search_helper_spec.rb b/spec/helpers/search_helper_spec.rb index 733f275472..b327f4f911 100644 --- a/spec/helpers/search_helper_spec.rb +++ b/spec/helpers/search_helper_spec.rb @@ -13,7 +13,7 @@ describe SearchHelper do end it "it returns nil" do - search_autocomplete_opts("q").should be_nil + expect(search_autocomplete_opts("q")).to be_nil end end @@ -25,29 +25,29 @@ describe SearchHelper do end it "includes Help sections" do - search_autocomplete_opts("hel").size.should == 9 + expect(search_autocomplete_opts("hel").size).to eq(9) end it "includes default sections" do - search_autocomplete_opts("adm").size.should == 1 + expect(search_autocomplete_opts("adm").size).to eq(1) end it "includes the user's groups" do create(:group).add_owner(user) - search_autocomplete_opts("gro").size.should == 1 + expect(search_autocomplete_opts("gro").size).to eq(1) end it "includes the user's projects" do project = create(:project, namespace: create(:namespace, owner: user)) - search_autocomplete_opts(project.name).size.should == 1 + expect(search_autocomplete_opts(project.name).size).to eq(1) end context "with a current project" do before { @project = create(:project) } it "includes project-specific sections" do - search_autocomplete_opts("Files").size.should == 1 - search_autocomplete_opts("Commits").size.should == 1 + expect(search_autocomplete_opts("Files").size).to eq(1) + expect(search_autocomplete_opts("Commits").size).to eq(1) end end end diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb index 41c9f038c2..e99c3f5bc1 100644 --- a/spec/helpers/submodule_helper_spec.rb +++ b/spec/helpers/submodule_helper_spec.rb @@ -1,6 +1,8 @@ require 'spec_helper' describe SubmoduleHelper do + include RepoHelpers + describe 'submodule links' do let(:submodule_item) { double(id: 'hash', path: 'rack') } let(:config) { Gitlab.config.gitlab } @@ -19,28 +21,28 @@ describe SubmoduleHelper do Gitlab.config.gitlab_shell.stub(ssh_port: 22) # set this just to be sure Gitlab.config.gitlab_shell.stub(ssh_path_prefix: Settings.send(:build_gitlab_shell_ssh_path_prefix)) stub_url([ config.user, '@', config.host, ':gitlab-org/gitlab-ce.git' ].join('')) - submodule_links(submodule_item).should == [ project_path('gitlab-org/gitlab-ce'), project_tree_path('gitlab-org/gitlab-ce', 'hash') ] + expect(submodule_links(submodule_item)).to eq([ namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash') ]) end it 'should detect ssh on non-standard port' do Gitlab.config.gitlab_shell.stub(ssh_port: 2222) Gitlab.config.gitlab_shell.stub(ssh_path_prefix: Settings.send(:build_gitlab_shell_ssh_path_prefix)) stub_url([ 'ssh://', config.user, '@', config.host, ':2222/gitlab-org/gitlab-ce.git' ].join('')) - submodule_links(submodule_item).should == [ project_path('gitlab-org/gitlab-ce'), project_tree_path('gitlab-org/gitlab-ce', 'hash') ] + expect(submodule_links(submodule_item)).to eq([ namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash') ]) end it 'should detect http on standard port' do Gitlab.config.gitlab.stub(port: 80) Gitlab.config.gitlab.stub(url: Settings.send(:build_gitlab_url)) stub_url([ 'http://', config.host, '/gitlab-org/gitlab-ce.git' ].join('')) - submodule_links(submodule_item).should == [ project_path('gitlab-org/gitlab-ce'), project_tree_path('gitlab-org/gitlab-ce', 'hash') ] + expect(submodule_links(submodule_item)).to eq([ namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash') ]) end it 'should detect http on non-standard port' do Gitlab.config.gitlab.stub(port: 3000) Gitlab.config.gitlab.stub(url: Settings.send(:build_gitlab_url)) stub_url([ 'http://', config.host, ':3000/gitlab-org/gitlab-ce.git' ].join('')) - submodule_links(submodule_item).should == [ project_path('gitlab-org/gitlab-ce'), project_tree_path('gitlab-org/gitlab-ce', 'hash') ] + expect(submodule_links(submodule_item)).to eq([ namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash') ]) end it 'should work with relative_url_root' do @@ -48,67 +50,100 @@ describe SubmoduleHelper do Gitlab.config.gitlab.stub(relative_url_root: '/gitlab/root') Gitlab.config.gitlab.stub(url: Settings.send(:build_gitlab_url)) stub_url([ 'http://', config.host, '/gitlab/root/gitlab-org/gitlab-ce.git' ].join('')) - submodule_links(submodule_item).should == [ project_path('gitlab-org/gitlab-ce'), project_tree_path('gitlab-org/gitlab-ce', 'hash') ] + expect(submodule_links(submodule_item)).to eq([ namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash') ]) end end context 'submodule on github.com' do it 'should detect ssh' do stub_url('git@github.com:gitlab-org/gitlab-ce.git') - submodule_links(submodule_item).should == [ 'https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash' ] + expect(submodule_links(submodule_item)).to eq([ 'https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash' ]) end it 'should detect http' do stub_url('http://github.com/gitlab-org/gitlab-ce.git') - submodule_links(submodule_item).should == [ 'https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash' ] + expect(submodule_links(submodule_item)).to eq([ 'https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash' ]) end it 'should detect https' do stub_url('https://github.com/gitlab-org/gitlab-ce.git') - submodule_links(submodule_item).should == [ 'https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash' ] + expect(submodule_links(submodule_item)).to eq([ 'https://github.com/gitlab-org/gitlab-ce', 'https://github.com/gitlab-org/gitlab-ce/tree/hash' ]) end it 'should return original with non-standard url' do stub_url('http://github.com/gitlab-org/gitlab-ce') - submodule_links(submodule_item).should == [ repo.submodule_url_for, nil ] + expect(submodule_links(submodule_item)).to eq([ repo.submodule_url_for, nil ]) stub_url('http://github.com/another/gitlab-org/gitlab-ce.git') - submodule_links(submodule_item).should == [ repo.submodule_url_for, nil ] + expect(submodule_links(submodule_item)).to eq([ repo.submodule_url_for, nil ]) end end context 'submodule on gitlab.com' do it 'should detect ssh' do stub_url('git@gitlab.com:gitlab-org/gitlab-ce.git') - submodule_links(submodule_item).should == [ 'https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash' ] + expect(submodule_links(submodule_item)).to eq([ 'https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash' ]) end it 'should detect http' do stub_url('http://gitlab.com/gitlab-org/gitlab-ce.git') - submodule_links(submodule_item).should == [ 'https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash' ] + expect(submodule_links(submodule_item)).to eq([ 'https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash' ]) end it 'should detect https' do stub_url('https://gitlab.com/gitlab-org/gitlab-ce.git') - submodule_links(submodule_item).should == [ 'https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash' ] + expect(submodule_links(submodule_item)).to eq([ 'https://gitlab.com/gitlab-org/gitlab-ce', 'https://gitlab.com/gitlab-org/gitlab-ce/tree/hash' ]) end it 'should return original with non-standard url' do stub_url('http://gitlab.com/gitlab-org/gitlab-ce') - submodule_links(submodule_item).should == [ repo.submodule_url_for, nil ] + expect(submodule_links(submodule_item)).to eq([ repo.submodule_url_for, nil ]) stub_url('http://gitlab.com/another/gitlab-org/gitlab-ce.git') - submodule_links(submodule_item).should == [ repo.submodule_url_for, nil ] + expect(submodule_links(submodule_item)).to eq([ repo.submodule_url_for, nil ]) end end context 'submodule on unsupported' do it 'should return original' do stub_url('http://mygitserver.com/gitlab-org/gitlab-ce') - submodule_links(submodule_item).should == [ repo.submodule_url_for, nil ] + expect(submodule_links(submodule_item)).to eq([ repo.submodule_url_for, nil ]) stub_url('http://mygitserver.com/gitlab-org/gitlab-ce.git') - submodule_links(submodule_item).should == [ repo.submodule_url_for, nil ] + expect(submodule_links(submodule_item)).to eq([ repo.submodule_url_for, nil ]) + end + end + + context 'submodules with relative links' do + let(:group) { create(:group) } + let(:project) { create(:project, group: group) } + + before do + self.instance_variable_set(:@project, project) + end + + it 'one level down' do + commit_id = sample_commit[:id] + result = relative_self_links('../test.git', commit_id) + expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"]) + end + + it 'two levels down' do + commit_id = sample_commit[:id] + result = relative_self_links('../../test.git', commit_id) + expect(result).to eq(["/#{group.path}/test", "/#{group.path}/test/tree/#{commit_id}"]) + end + + it 'one level down with namespace and repo' do + commit_id = sample_commit[:id] + result = relative_self_links('../foobar/test.git', commit_id) + expect(result).to eq(["/foobar/test", "/foobar/test/tree/#{commit_id}"]) + end + + it 'two levels down with namespace and repo' do + commit_id = sample_commit[:id] + result = relative_self_links('../foobar/baz/test.git', commit_id) + expect(result).to eq(["/baz/test", "/baz/test/tree/#{commit_id}"]) end end end diff --git a/spec/helpers/tab_helper_spec.rb b/spec/helpers/tab_helper_spec.rb index fa8a3f554f..fc0ceecfbe 100644 --- a/spec/helpers/tab_helper_spec.rb +++ b/spec/helpers/tab_helper_spec.rb @@ -5,40 +5,40 @@ describe TabHelper do describe 'nav_link' do before do - controller.stub(:controller_name).and_return('foo') + allow(controller).to receive(:controller_name).and_return('foo') allow(self).to receive(:action_name).and_return('foo') end it "captures block output" do - nav_link { "Testing Blocks" }.should match(/Testing Blocks/) + expect(nav_link { "Testing Blocks" }).to match(/Testing Blocks/) end it "performs checks on the current controller" do - nav_link(controller: :foo).should match(/
  • /) - nav_link(controller: :bar).should_not match(/active/) - nav_link(controller: [:foo, :bar]).should match(/active/) + expect(nav_link(controller: :foo)).to match(/
  • /) + expect(nav_link(controller: :bar)).not_to match(/active/) + expect(nav_link(controller: [:foo, :bar])).to match(/active/) end it "performs checks on the current action" do - nav_link(action: :foo).should match(/
  • /) - nav_link(action: :bar).should_not match(/active/) - nav_link(action: [:foo, :bar]).should match(/active/) + expect(nav_link(action: :foo)).to match(/
  • /) + expect(nav_link(action: :bar)).not_to match(/active/) + expect(nav_link(action: [:foo, :bar])).to match(/active/) end it "performs checks on both controller and action when both are present" do - nav_link(controller: :bar, action: :foo).should_not match(/active/) - nav_link(controller: :foo, action: :bar).should_not match(/active/) - nav_link(controller: :foo, action: :foo).should match(/active/) + expect(nav_link(controller: :bar, action: :foo)).not_to match(/active/) + expect(nav_link(controller: :foo, action: :bar)).not_to match(/active/) + expect(nav_link(controller: :foo, action: :foo)).to match(/active/) end it "accepts a path shorthand" do - nav_link(path: 'foo#bar').should_not match(/active/) - nav_link(path: 'foo#foo').should match(/active/) + expect(nav_link(path: 'foo#bar')).not_to match(/active/) + expect(nav_link(path: 'foo#foo')).to match(/active/) end it "passes extra html options to the list element" do - nav_link(action: :foo, html_options: {class: 'home'}).should match(/
  • /) - nav_link(html_options: {class: 'active'}).should match(/
  • /) + expect(nav_link(action: :foo, html_options: {class: 'home'})).to match(/
  • /) + expect(nav_link(html_options: {class: 'active'})).to match(/
  • /) end end end diff --git a/spec/helpers/tree_helper_spec.rb b/spec/helpers/tree_helper_spec.rb new file mode 100644 index 0000000000..8271e00f41 --- /dev/null +++ b/spec/helpers/tree_helper_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe TreeHelper do + describe 'flatten_tree' do + let(:project) { create(:project) } + + before { + @repository = project.repository + @commit = project.repository.commit("e56497bb") + } + + context "on a directory containing more than one file/directory" do + let(:tree_item) { double(name: "files", path: "files") } + + it "should return the directory name" do + expect(flatten_tree(tree_item)).to match('files') + end + end + + context "on a directory containing only one directory" do + let(:tree_item) { double(name: "foo", path: "foo") } + + it "should return the flattened path" do + expect(flatten_tree(tree_item)).to match('foo/bar') + end + end + end +end diff --git a/spec/lib/auth_spec.rb b/spec/lib/auth_spec.rb deleted file mode 100644 index 073b811c3f..0000000000 --- a/spec/lib/auth_spec.rb +++ /dev/null @@ -1,28 +0,0 @@ -require 'spec_helper' - -describe Gitlab::Auth do - let(:gl_auth) { Gitlab::Auth.new } - - describe :find do - before do - @user = create( - :user, - username: 'john', - password: '88877711', - password_confirmation: '88877711' - ) - end - - it "should find user by valid login/password" do - gl_auth.find('john', '88877711').should == @user - end - - it "should not find user with invalid password" do - gl_auth.find('john', 'invalid11').should_not == @user - end - - it "should not find user with invalid login and password" do - gl_auth.find('jon', 'invalid11').should_not == @user - end - end -end diff --git a/spec/lib/disable_email_interceptor_spec.rb b/spec/lib/disable_email_interceptor_spec.rb new file mode 100644 index 0000000000..06d5450688 --- /dev/null +++ b/spec/lib/disable_email_interceptor_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe DisableEmailInterceptor do + before do + ActionMailer::Base.register_interceptor(DisableEmailInterceptor) + end + + it 'should not send emails' do + allow(Gitlab.config.gitlab).to receive(:email_enabled).and_return(false) + expect { + deliver_mail + }.not_to change(ActionMailer::Base.deliveries, :count) + end + + after do + # Removing interceptor from the list because unregister_interceptor is + # implemented in later version of mail gem + # See: https://github.com/mikel/mail/pull/705 + Mail.class_variable_set(:@@delivery_interceptors, []) + end + + def deliver_mail + key = create :personal_key + Notify.new_ssh_key_email(key.id) + end +end diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb index 7b3818ea5c..ac602eac15 100644 --- a/spec/lib/extracts_path_spec.rb +++ b/spec/lib/extracts_path_spec.rb @@ -14,44 +14,46 @@ describe ExtractsPath do describe '#extract_ref' do it "returns an empty pair when no @project is set" do @project = nil - extract_ref('master/CHANGELOG').should == ['', ''] + expect(extract_ref('master/CHANGELOG')).to eq(['', '']) end context "without a path" do it "extracts a valid branch" do - extract_ref('master').should == ['master', ''] + expect(extract_ref('master')).to eq(['master', '']) end it "extracts a valid tag" do - extract_ref('v2.0.0').should == ['v2.0.0', ''] + expect(extract_ref('v2.0.0')).to eq(['v2.0.0', '']) end it "extracts a valid commit ref without a path" do - extract_ref('f4b14494ef6abf3d144c28e4af0c20143383e062').should == + expect(extract_ref('f4b14494ef6abf3d144c28e4af0c20143383e062')).to eq( ['f4b14494ef6abf3d144c28e4af0c20143383e062', ''] + ) end it "falls back to a primitive split for an invalid ref" do - extract_ref('stable').should == ['stable', ''] + expect(extract_ref('stable')).to eq(['stable', '']) end end context "with a path" do it "extracts a valid branch" do - extract_ref('foo/bar/baz/CHANGELOG').should == ['foo/bar/baz', 'CHANGELOG'] + expect(extract_ref('foo/bar/baz/CHANGELOG')).to eq(['foo/bar/baz', 'CHANGELOG']) end it "extracts a valid tag" do - extract_ref('v2.0.0/CHANGELOG').should == ['v2.0.0', 'CHANGELOG'] + expect(extract_ref('v2.0.0/CHANGELOG')).to eq(['v2.0.0', 'CHANGELOG']) end it "extracts a valid commit SHA" do - extract_ref('f4b14494ef6abf3d144c28e4af0c20143383e062/CHANGELOG').should == + expect(extract_ref('f4b14494ef6abf3d144c28e4af0c20143383e062/CHANGELOG')).to eq( ['f4b14494ef6abf3d144c28e4af0c20143383e062', 'CHANGELOG'] + ) end it "falls back to a primitive split for an invalid ref" do - extract_ref('stable/CHANGELOG').should == ['stable', 'CHANGELOG'] + expect(extract_ref('stable/CHANGELOG')).to eq(['stable', 'CHANGELOG']) end end end diff --git a/spec/lib/file_size_validator_spec.rb b/spec/lib/file_size_validator_spec.rb new file mode 100644 index 0000000000..5c89c85471 --- /dev/null +++ b/spec/lib/file_size_validator_spec.rb @@ -0,0 +1,43 @@ +require 'spec_helper' + +describe 'Gitlab::FileSizeValidatorSpec' do + let(:validator) { FileSizeValidator.new(options) } + let(:attachment) { AttachmentUploader.new } + let(:note) { create(:note) } + + describe 'options uses an integer' do + let(:options) { { maximum: 10, attributes: { attachment: attachment } } } + + it 'attachment exceeds maximum limit' do + allow(attachment).to receive(:size) { 100 } + validator.validate_each(note, :attachment, attachment) + expect(note.errors).to have_key(:attachment) + end + + it 'attachment under maximum limit' do + allow(attachment).to receive(:size) { 1 } + validator.validate_each(note, :attachment, attachment) + expect(note.errors).not_to have_key(:attachment) + end + end + + describe 'options uses a symbol' do + let(:options) { { maximum: :test, + attributes: { attachment: attachment } } } + before do + allow(note).to receive(:test) { 10 } + end + + it 'attachment exceeds maximum limit' do + allow(attachment).to receive(:size) { 100 } + validator.validate_each(note, :attachment, attachment) + expect(note.errors).to have_key(:attachment) + end + + it 'attachment under maximum limit' do + allow(attachment).to receive(:size) { 1 } + validator.validate_each(note, :attachment, attachment) + expect(note.errors).not_to have_key(:attachment) + end + end +end diff --git a/spec/lib/git_ref_validator_spec.rb b/spec/lib/git_ref_validator_spec.rb new file mode 100644 index 0000000000..4633b6f393 --- /dev/null +++ b/spec/lib/git_ref_validator_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Gitlab::GitRefValidator do + it { expect(Gitlab::GitRefValidator.validate('feature/new')).to be_truthy } + it { expect(Gitlab::GitRefValidator.validate('implement_@all')).to be_truthy } + it { expect(Gitlab::GitRefValidator.validate('my_new_feature')).to be_truthy } + it { expect(Gitlab::GitRefValidator.validate('#1')).to be_truthy } + it { expect(Gitlab::GitRefValidator.validate('feature/~new/')).to be_falsey } + it { expect(Gitlab::GitRefValidator.validate('feature/^new/')).to be_falsey } + it { expect(Gitlab::GitRefValidator.validate('feature/:new/')).to be_falsey } + it { expect(Gitlab::GitRefValidator.validate('feature/?new/')).to be_falsey } + it { expect(Gitlab::GitRefValidator.validate('feature/*new/')).to be_falsey } + it { expect(Gitlab::GitRefValidator.validate('feature/[new/')).to be_falsey } + it { expect(Gitlab::GitRefValidator.validate('feature/new/')).to be_falsey } + it { expect(Gitlab::GitRefValidator.validate('feature/new.')).to be_falsey } + it { expect(Gitlab::GitRefValidator.validate('feature\@{')).to be_falsey } + it { expect(Gitlab::GitRefValidator.validate('feature\new')).to be_falsey } + it { expect(Gitlab::GitRefValidator.validate('feature//new')).to be_falsey } + it { expect(Gitlab::GitRefValidator.validate('feature new')).to be_falsey } +end diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb new file mode 100644 index 0000000000..95fc7e16a1 --- /dev/null +++ b/spec/lib/gitlab/auth_spec.rb @@ -0,0 +1,54 @@ +require 'spec_helper' + +describe Gitlab::Auth do + let(:gl_auth) { Gitlab::Auth.new } + + describe :find do + let!(:user) do + create(:user, + username: username, + password: password, + password_confirmation: password) + end + let(:username) { 'John' } # username isn't lowercase, test this + let(:password) { 'my-secret' } + + it "should find user by valid login/password" do + expect( gl_auth.find(username, password) ).to eql user + end + + it 'should find user by valid email/password with case-insensitive email' do + expect(gl_auth.find(user.email.upcase, password)).to eql user + end + + it 'should find user by valid username/password with case-insensitive username' do + expect(gl_auth.find(username.upcase, password)).to eql user + end + + it "should not find user with invalid password" do + password = 'wrong' + expect( gl_auth.find(username, password) ).to_not eql user + end + + it "should not find user with invalid login" do + user = 'wrong' + expect( gl_auth.find(username, password) ).to_not eql user + end + + context "with ldap enabled" do + before { Gitlab::LDAP::Config.stub(enabled?: true) } + + it "tries to autheticate with db before ldap" do + expect(Gitlab::LDAP::Authentication).not_to receive(:login) + + gl_auth.find(username, password) + end + + it "uses ldap as fallback to for authentication" do + expect(Gitlab::LDAP::Authentication).to receive(:login) + + gl_auth.find('ldap_user', 'password') + end + end + end +end diff --git a/spec/lib/gitlab/backend/grack_auth_spec.rb b/spec/lib/gitlab/backend/grack_auth_spec.rb new file mode 100644 index 0000000000..d0aad54f67 --- /dev/null +++ b/spec/lib/gitlab/backend/grack_auth_spec.rb @@ -0,0 +1,196 @@ +require "spec_helper" + +describe Grack::Auth do + let(:user) { create(:user) } + let(:project) { create(:project) } + + let(:app) { lambda { |env| [200, {}, "Success!"] } } + let!(:auth) { Grack::Auth.new(app) } + let(:env) { + { + "rack.input" => "", + "REQUEST_METHOD" => "GET", + "QUERY_STRING" => "service=git-upload-pack" + } + } + let(:status) { auth.call(env).first } + + describe "#call" do + context "when the project doesn't exist" do + before do + env["PATH_INFO"] = "doesnt/exist.git" + end + + context "when no authentication is provided" do + it "responds with status 401" do + expect(status).to eq(401) + end + end + + context "when username and password are provided" do + context "when authentication fails" do + before do + env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials(user.username, "nope") + end + + it "responds with status 401" do + expect(status).to eq(401) + end + end + + context "when authentication succeeds" do + before do + env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password) + end + + it "responds with status 404" do + expect(status).to eq(404) + end + end + end + end + + context "when the project exists" do + before do + env["PATH_INFO"] = project.path_with_namespace + ".git" + end + + context "when the project is public" do + before do + project.update_attribute(:visibility_level, Project::PUBLIC) + end + + it "responds with status 200" do + expect(status).to eq(200) + end + end + + context "when the project is private" do + before do + project.update_attribute(:visibility_level, Project::PRIVATE) + end + + context "when no authentication is provided" do + it "responds with status 401" do + expect(status).to eq(401) + end + end + + context "when username and password are provided" do + context "when authentication fails" do + before do + env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials(user.username, "nope") + end + + it "responds with status 401" do + expect(status).to eq(401) + end + + context "when the user is IP banned" do + before do + expect(Rack::Attack::Allow2Ban).to receive(:filter).and_return(true) + allow_any_instance_of(Rack::Request).to receive(:ip).and_return('1.2.3.4') + end + + it "responds with status 401" do + expect(status).to eq(401) + end + end + end + + context "when authentication succeeds" do + before do + env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password) + end + + context "when the user has access to the project" do + before do + project.team << [user, :master] + end + + context "when the user is blocked" do + before do + user.block + project.team << [user, :master] + end + + it "responds with status 404" do + expect(status).to eq(404) + end + end + + context "when the user isn't blocked" do + before do + expect(Rack::Attack::Allow2Ban).to receive(:reset) + end + + it "responds with status 200" do + expect(status).to eq(200) + end + end + + context "when blank password attempts follow a valid login" do + let(:options) { Gitlab.config.rack_attack.git_basic_auth } + let(:maxretry) { options[:maxretry] - 1 } + let(:ip) { '1.2.3.4' } + + before do + allow_any_instance_of(Rack::Request).to receive(:ip).and_return(ip) + Rack::Attack::Allow2Ban.reset(ip, options) + end + + after do + Rack::Attack::Allow2Ban.reset(ip, options) + end + + def attempt_login(include_password) + password = include_password ? user.password : "" + env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials(user.username, password) + Grack::Auth.new(app) + auth.call(env).first + end + + it "repeated attempts followed by successful attempt" do + for n in 0..maxretry do + expect(attempt_login(false)).to eq(401) + end + + expect(attempt_login(true)).to eq(200) + expect(Rack::Attack::Allow2Ban.send(:banned?, ip)).to eq(nil) + + for n in 0..maxretry do + expect(attempt_login(false)).to eq(401) + end + end + end + end + + context "when the user doesn't have access to the project" do + it "responds with status 404" do + expect(status).to eq(404) + end + end + end + end + + context "when a gitlab ci token is provided" do + let(:token) { "123" } + + before do + gitlab_ci_service = project.build_gitlab_ci_service + gitlab_ci_service.active = true + gitlab_ci_service.token = token + gitlab_ci_service.project_url = "http://google.com" + gitlab_ci_service.save + + env["HTTP_AUTHORIZATION"] = ActionController::HttpAuthentication::Basic.encode_credentials("gitlab-ci-token", token) + end + + it "responds with status 200" do + expect(status).to eq(200) + end + end + end + end + end +end diff --git a/spec/lib/gitlab/backend/rack_attack_helpers_spec.rb b/spec/lib/gitlab/backend/rack_attack_helpers_spec.rb new file mode 100644 index 0000000000..2ac496fd66 --- /dev/null +++ b/spec/lib/gitlab/backend/rack_attack_helpers_spec.rb @@ -0,0 +1,35 @@ +require "spec_helper" + +describe 'RackAttackHelpers' do + describe 'reset' do + let(:discriminator) { 'test-key'} + let(:maxretry) { 5 } + let(:period) { 1.minute } + let(:options) { { findtime: period, bantime: 60, maxretry: maxretry } } + + def do_filter + for i in 1..maxretry - 1 do + status = Rack::Attack::Allow2Ban.filter(discriminator, options) { true } + expect(status).to eq(false) + end + end + + def do_reset + Rack::Attack::Allow2Ban.reset(discriminator, options) + end + + before do + do_reset + end + + after do + do_reset + end + + it 'user is not banned after n - 1 retries' do + do_filter + do_reset + do_filter + end + end +end diff --git a/spec/lib/gitlab/backend/shell_spec.rb b/spec/lib/gitlab/backend/shell_spec.rb index f00ec0fa40..27279465c1 100644 --- a/spec/lib/gitlab/backend/shell_spec.rb +++ b/spec/lib/gitlab/backend/shell_spec.rb @@ -8,11 +8,11 @@ describe Gitlab::Shell do Project.stub(find: project) end - it { should respond_to :add_key } - it { should respond_to :remove_key } - it { should respond_to :add_repository } - it { should respond_to :remove_repository } - it { should respond_to :fork_repository } + it { is_expected.to respond_to :add_key } + it { is_expected.to respond_to :remove_key } + it { is_expected.to respond_to :add_repository } + it { is_expected.to respond_to :remove_repository } + it { is_expected.to respond_to :fork_repository } - it { gitlab_shell.url_to_repo('diaspora').should == Gitlab.config.gitlab_shell.ssh_path_prefix + "diaspora.git" } + it { expect(gitlab_shell.url_to_repo('diaspora')).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + "diaspora.git") } end diff --git a/spec/lib/gitlab/bitbucket_import/client_spec.rb b/spec/lib/gitlab/bitbucket_import/client_spec.rb new file mode 100644 index 0000000000..dd450e9967 --- /dev/null +++ b/spec/lib/gitlab/bitbucket_import/client_spec.rb @@ -0,0 +1,17 @@ +require 'spec_helper' + +describe Gitlab::BitbucketImport::Client do + let(:token) { '123456' } + let(:secret) { 'secret' } + let(:client) { Gitlab::BitbucketImport::Client.new(token, secret) } + + before do + Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "bitbucket") + end + + it 'all OAuth client options are symbols' do + client.consumer.options.keys.each do |key| + expect(key).to be_kind_of(Symbol) + end + end +end diff --git a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb new file mode 100644 index 0000000000..0ec6a43f68 --- /dev/null +++ b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe Gitlab::BitbucketImport::ProjectCreator do + let(:user) { create(:user, bitbucket_access_token: "asdffg", bitbucket_access_token_secret: "sekret") } + let(:repo) { { + name: 'Vim', + slug: 'vim', + is_private: true, + owner: "asd"}.with_indifferent_access + } + let(:namespace){ create(:group, owner: user) } + + before do + namespace.add_owner(user) + end + + it 'creates project' do + allow_any_instance_of(Project).to receive(:add_import_job) + + project_creator = Gitlab::BitbucketImport::ProjectCreator.new(repo, namespace, user) + project = project_creator.execute + + expect(project.import_url).to eq("ssh://git@bitbucket.org/asd/vim.git") + expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) + end +end diff --git a/spec/lib/gitlab/closing_issue_extractor_spec.rb b/spec/lib/gitlab/closing_issue_extractor_spec.rb new file mode 100644 index 0000000000..cb7b0fbb89 --- /dev/null +++ b/spec/lib/gitlab/closing_issue_extractor_spec.rb @@ -0,0 +1,176 @@ +require 'spec_helper' + +describe Gitlab::ClosingIssueExtractor do + let(:project) { create(:project) } + let(:issue) { create(:issue, project: project) } + let(:iid1) { issue.iid } + + subject { described_class.new(project, project.creator) } + + describe "#closed_by_message" do + context 'with a single reference' do + it do + message = "Awesome commit (Closes ##{iid1})" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "Awesome commit (closes ##{iid1})" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "Closed ##{iid1}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "closed ##{iid1}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "Closing ##{iid1}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "closing ##{iid1}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "Close ##{iid1}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "close ##{iid1}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "Awesome commit (Fixes ##{iid1})" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "Awesome commit (fixes ##{iid1})" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "Fixed ##{iid1}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "fixed ##{iid1}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "Fixing ##{iid1}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "fixing ##{iid1}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "Fix ##{iid1}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "fix ##{iid1}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "Awesome commit (Resolves ##{iid1})" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "Awesome commit (resolves ##{iid1})" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "Resolved ##{iid1}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "resolved ##{iid1}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "Resolving ##{iid1}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "resolving ##{iid1}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "Resolve ##{iid1}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + + it do + message = "resolve ##{iid1}" + expect(subject.closed_by_message(message)).to eq([issue]) + end + end + + context 'with multiple references' do + let(:other_issue) { create(:issue, project: project) } + let(:third_issue) { create(:issue, project: project) } + let(:iid2) { other_issue.iid } + let(:iid3) { third_issue.iid } + + it 'fetches issues in single line message' do + message = "Closes ##{iid1} and fix ##{iid2}" + + expect(subject.closed_by_message(message)). + to eq([issue, other_issue]) + end + + it 'fetches comma-separated issues references in single line message' do + message = "Closes ##{iid1}, closes ##{iid2}" + + expect(subject.closed_by_message(message)). + to eq([issue, other_issue]) + end + + it 'fetches comma-separated issues numbers in single line message' do + message = "Closes ##{iid1}, ##{iid2} and ##{iid3}" + + expect(subject.closed_by_message(message)). + to eq([issue, other_issue, third_issue]) + end + + it 'fetches issues in multi-line message' do + message = "Awesome commit (closes ##{iid1})\nAlso fixes ##{iid2}" + + expect(subject.closed_by_message(message)). + to eq([issue, other_issue]) + end + + it 'fetches issues in hybrid message' do + message = "Awesome commit (closes ##{iid1})\n"\ + "Also fixing issues ##{iid2}, ##{iid3} and #4" + + expect(subject.closed_by_message(message)). + to eq([issue, other_issue, third_issue]) + end + end + end +end diff --git a/spec/lib/gitlab/diff/file_spec.rb b/spec/lib/gitlab/diff/file_spec.rb new file mode 100644 index 0000000000..40eb45e37c --- /dev/null +++ b/spec/lib/gitlab/diff/file_spec.rb @@ -0,0 +1,21 @@ +require 'spec_helper' + +describe Gitlab::Diff::File do + include RepoHelpers + + let(:project) { create(:project) } + let(:commit) { project.repository.commit(sample_commit.id) } + let(:diff) { commit.diffs.first } + let(:diff_file) { Gitlab::Diff::File.new(diff) } + + describe :diff_lines do + let(:diff_lines) { diff_file.diff_lines } + + it { expect(diff_lines.size).to eq(30) } + it { expect(diff_lines.first).to be_kind_of(Gitlab::Diff::Line) } + end + + describe :mode_changed? do + it { expect(diff_file.mode_changed?).to be_falsey } + end +end diff --git a/spec/lib/gitlab/diff/parser_spec.rb b/spec/lib/gitlab/diff/parser_spec.rb new file mode 100644 index 0000000000..918f6d0ead --- /dev/null +++ b/spec/lib/gitlab/diff/parser_spec.rb @@ -0,0 +1,93 @@ +require 'spec_helper' + +describe Gitlab::Diff::Parser do + include RepoHelpers + + let(:project) { create(:project) } + let(:commit) { project.repository.commit(sample_commit.id) } + let(:diff) { commit.diffs.first } + let(:parser) { Gitlab::Diff::Parser.new } + + describe :parse do + let(:diff) do + < path } +- options = { chdir: path } ++ ++ vars = { ++ "PWD" => path ++ } ++ ++ options = { ++ chdir: path ++ } + + unless File.directory?(path) + FileUtils.mkdir_p(path) +@@ -19,6 +25,7 @@ module Popen + + @cmd_output = "" + @cmd_status = 0 ++ + Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr| + @cmd_output << stdout.read + @cmd_output << stderr.read +eos + end + + before do + @lines = parser.parse(diff.lines) + end + + it { expect(@lines.size).to eq(30) } + + describe 'lines' do + describe 'first line' do + let(:line) { @lines.first } + + it { expect(line.type).to eq('match') } + it { expect(line.old_pos).to eq(6) } + it { expect(line.new_pos).to eq(6) } + it { expect(line.text).to eq('@@ -6,12 +6,18 @@ module Popen') } + end + + describe 'removal line' do + let(:line) { @lines[10] } + + it { expect(line.type).to eq('old') } + it { expect(line.old_pos).to eq(14) } + it { expect(line.new_pos).to eq(13) } + it { expect(line.text).to eq('- options = { chdir: path }') } + end + + describe 'addition line' do + let(:line) { @lines[16] } + + it { expect(line.type).to eq('new') } + it { expect(line.old_pos).to eq(15) } + it { expect(line.new_pos).to eq(18) } + it { expect(line.text).to eq('+ options = {') } + end + + describe 'unchanged line' do + let(:line) { @lines.last } + + it { expect(line.type).to eq(nil) } + it { expect(line.old_pos).to eq(24) } + it { expect(line.new_pos).to eq(31) } + it { expect(line.text).to eq(' @cmd_output << stderr.read') } + end + end + end +end diff --git a/spec/lib/gitlab/git_access_spec.rb b/spec/lib/gitlab/git_access_spec.rb new file mode 100644 index 0000000000..39be9d6464 --- /dev/null +++ b/spec/lib/gitlab/git_access_spec.rb @@ -0,0 +1,235 @@ +require 'spec_helper' + +describe Gitlab::GitAccess do + let(:access) { Gitlab::GitAccess.new(actor, project) } + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:actor) { user } + + describe 'can_push_to_branch?' do + describe 'push to none protected branch' do + it "returns true if user is a master" do + project.team << [user, :master] + expect(access.can_push_to_branch?("random_branch")).to be_truthy + end + + it "returns true if user is a developer" do + project.team << [user, :developer] + expect(access.can_push_to_branch?("random_branch")).to be_truthy + end + + it "returns false if user is a reporter" do + project.team << [user, :reporter] + expect(access.can_push_to_branch?("random_branch")).to be_falsey + end + end + + describe 'push to protected branch' do + before do + @branch = create :protected_branch, project: project + end + + it "returns true if user is a master" do + project.team << [user, :master] + expect(access.can_push_to_branch?(@branch.name)).to be_truthy + end + + it "returns false if user is a developer" do + project.team << [user, :developer] + expect(access.can_push_to_branch?(@branch.name)).to be_falsey + end + + it "returns false if user is a reporter" do + project.team << [user, :reporter] + expect(access.can_push_to_branch?(@branch.name)).to be_falsey + end + end + + describe 'push to protected branch if allowed for developers' do + before do + @branch = create :protected_branch, project: project, developers_can_push: true + end + + it "returns true if user is a master" do + project.team << [user, :master] + expect(access.can_push_to_branch?(@branch.name)).to be_truthy + end + + it "returns true if user is a developer" do + project.team << [user, :developer] + expect(access.can_push_to_branch?(@branch.name)).to be_truthy + end + + it "returns false if user is a reporter" do + project.team << [user, :reporter] + expect(access.can_push_to_branch?(@branch.name)).to be_falsey + end + end + + end + + describe 'download_access_check' do + describe 'master permissions' do + before { project.team << [user, :master] } + + context 'pull code' do + subject { access.download_access_check } + + it { expect(subject.allowed?).to be_truthy } + end + end + + describe 'guest permissions' do + before { project.team << [user, :guest] } + + context 'pull code' do + subject { access.download_access_check } + + it { expect(subject.allowed?).to be_falsey } + end + end + + describe 'blocked user' do + before do + project.team << [user, :master] + user.block + end + + context 'pull code' do + subject { access.download_access_check } + + it { expect(subject.allowed?).to be_falsey } + end + end + + describe 'without acccess to project' do + context 'pull code' do + subject { access.download_access_check } + + it { expect(subject.allowed?).to be_falsey } + end + end + + describe 'deploy key permissions' do + let(:key) { create(:deploy_key) } + let(:actor) { key } + + context 'pull code' do + context 'allowed' do + before { key.projects << project } + subject { access.download_access_check } + + it { expect(subject.allowed?).to be_truthy } + end + + context 'denied' do + subject { access.download_access_check } + + it { expect(subject.allowed?).to be_falsey } + end + end + end + end + + describe 'push_access_check' do + def protect_feature_branch + create(:protected_branch, name: 'feature', project: project) + end + + def changes + { + push_new_branch: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/heads/wow", + push_master: '6f6d7e7ed 570e7b2ab refs/heads/master', + push_protected_branch: '6f6d7e7ed 570e7b2ab refs/heads/feature', + push_remove_protected_branch: "570e7b2ab #{Gitlab::Git::BLANK_SHA} "\ + 'refs/heads/feature', + push_tag: '6f6d7e7ed 570e7b2ab refs/tags/v1.0.0', + push_new_tag: "#{Gitlab::Git::BLANK_SHA} 570e7b2ab refs/tags/v7.8.9", + push_all: ['6f6d7e7ed 570e7b2ab refs/heads/master', '6f6d7e7ed 570e7b2ab refs/heads/feature'] + } + end + + def self.permissions_matrix + { + master: { + push_new_branch: true, + push_master: true, + push_protected_branch: true, + push_remove_protected_branch: false, + push_tag: true, + push_new_tag: true, + push_all: true, + }, + + developer: { + push_new_branch: true, + push_master: true, + push_protected_branch: false, + push_remove_protected_branch: false, + push_tag: false, + push_new_tag: true, + push_all: false, + }, + + reporter: { + push_new_branch: false, + push_master: false, + push_protected_branch: false, + push_remove_protected_branch: false, + push_tag: false, + push_new_tag: false, + push_all: false, + }, + + guest: { + push_new_branch: false, + push_master: false, + push_protected_branch: false, + push_remove_protected_branch: false, + push_tag: false, + push_new_tag: false, + push_all: false, + } + } + end + + def self.updated_permissions_matrix + updated_permissions_matrix = permissions_matrix.dup + updated_permissions_matrix[:developer][:push_protected_branch] = true + updated_permissions_matrix[:developer][:push_all] = true + updated_permissions_matrix + end + + permissions_matrix.keys.each do |role| + describe "#{role} access" do + before { protect_feature_branch } + before { project.team << [user, role] } + + permissions_matrix[role].each do |action, allowed| + context action do + subject { access.push_access_check(changes[action]) } + + it { expect(subject.allowed?).to allowed ? be_truthy : be_falsey } + end + end + end + end + + context "with enabled developers push to protected branches " do + updated_permissions_matrix.keys.each do |role| + describe "#{role} access" do + before { create(:protected_branch, name: 'feature', developers_can_push: true, project: project) } + before { project.team << [user, role] } + + updated_permissions_matrix[role].each do |action, allowed| + context action do + subject { access.push_access_check(changes[action]) } + + it { expect(subject.allowed?).to allowed ? be_truthy : be_falsey } + end + end + end + end + end + end +end diff --git a/spec/lib/gitlab/git_access_wiki_spec.rb b/spec/lib/gitlab/git_access_wiki_spec.rb new file mode 100644 index 0000000000..4cb91094cb --- /dev/null +++ b/spec/lib/gitlab/git_access_wiki_spec.rb @@ -0,0 +1,22 @@ +require 'spec_helper' + +describe Gitlab::GitAccessWiki do + let(:access) { Gitlab::GitAccessWiki.new(user, project) } + let(:project) { create(:project) } + let(:user) { create(:user) } + + describe 'push_allowed?' do + before do + create(:protected_branch, name: 'master', project: project) + project.team << [user, :developer] + end + + subject { access.push_access_check(changes) } + + it { expect(subject.allowed?).to be_truthy } + end + + def changes + ['6f6d7e7ed 570e7b2ab refs/heads/master'] + end +end diff --git a/spec/lib/gitlab/github_import/client_spec.rb b/spec/lib/gitlab/github_import/client_spec.rb new file mode 100644 index 0000000000..2661812031 --- /dev/null +++ b/spec/lib/gitlab/github_import/client_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +describe Gitlab::GithubImport::Client do + let(:token) { '123456' } + let(:client) { Gitlab::GithubImport::Client.new(token) } + + before do + Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "github") + end + + it 'all OAuth2 client options are symbols' do + client.client.options.keys.each do |key| + expect(key).to be_kind_of(Symbol) + end + end +end diff --git a/spec/lib/gitlab/github_import/project_creator_spec.rb b/spec/lib/gitlab/github_import/project_creator_spec.rb new file mode 100644 index 0000000000..3bf52cb685 --- /dev/null +++ b/spec/lib/gitlab/github_import/project_creator_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe Gitlab::GithubImport::ProjectCreator do + let(:user) { create(:user, github_access_token: "asdffg") } + let(:repo) { OpenStruct.new( + login: 'vim', + name: 'vim', + private: true, + full_name: 'asd/vim', + clone_url: "https://gitlab.com/asd/vim.git", + owner: OpenStruct.new(login: "john")) + } + let(:namespace){ create(:group, owner: user) } + + before do + namespace.add_owner(user) + end + + it 'creates project' do + allow_any_instance_of(Project).to receive(:add_import_job) + + project_creator = Gitlab::GithubImport::ProjectCreator.new(repo, namespace, user) + project = project_creator.execute + + expect(project.import_url).to eq("https://asdffg@gitlab.com/asd/vim.git") + expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) + end +end diff --git a/spec/lib/gitlab/gitlab_import/client_spec.rb b/spec/lib/gitlab/gitlab_import/client_spec.rb new file mode 100644 index 0000000000..c511c51547 --- /dev/null +++ b/spec/lib/gitlab/gitlab_import/client_spec.rb @@ -0,0 +1,16 @@ +require 'spec_helper' + +describe Gitlab::GitlabImport::Client do + let(:token) { '123456' } + let(:client) { Gitlab::GitlabImport::Client.new(token) } + + before do + Gitlab.config.omniauth.providers << OpenStruct.new(app_id: "asd123", app_secret: "asd123", name: "gitlab") + end + + it 'all OAuth2 client options are symbols' do + client.client.options.keys.each do |key| + expect(key).to be_kind_of(Symbol) + end + end +end diff --git a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb new file mode 100644 index 0000000000..3cefe4ea8e --- /dev/null +++ b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe Gitlab::GitlabImport::ProjectCreator do + let(:user) { create(:user, gitlab_access_token: "asdffg") } + let(:repo) { { + name: 'vim', + path: 'vim', + visibility_level: Gitlab::VisibilityLevel::PRIVATE, + path_with_namespace: 'asd/vim', + http_url_to_repo: "https://gitlab.com/asd/vim.git", + owner: {name: "john"}}.with_indifferent_access + } + let(:namespace){ create(:group, owner: user) } + + before do + namespace.add_owner(user) + end + + it 'creates project' do + allow_any_instance_of(Project).to receive(:add_import_job) + + project_creator = Gitlab::GitlabImport::ProjectCreator.new(repo, namespace, user) + project = project_creator.execute + + expect(project.import_url).to eq("https://oauth2:asdffg@gitlab.com/asd/vim.git") + expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PRIVATE) + end +end diff --git a/spec/lib/gitlab/gitlab_markdown_helper_spec.rb b/spec/lib/gitlab/gitlab_markdown_helper_spec.rb index 540618a560..ab613193f4 100644 --- a/spec/lib/gitlab/gitlab_markdown_helper_spec.rb +++ b/spec/lib/gitlab/gitlab_markdown_helper_spec.rb @@ -5,24 +5,24 @@ describe Gitlab::MarkdownHelper do %w(textile rdoc org creole wiki mediawiki rst adoc asciidoc asc).each do |type| it "returns true for #{type} files" do - Gitlab::MarkdownHelper.markup?("README.#{type}").should be_true + expect(Gitlab::MarkdownHelper.markup?("README.#{type}")).to be_truthy end end it 'returns false when given a non-markup filename' do - Gitlab::MarkdownHelper.markup?('README.rb').should_not be_true + expect(Gitlab::MarkdownHelper.markup?('README.rb')).not_to be_truthy end end describe '#gitlab_markdown?' do %w(mdown md markdown).each do |type| it "returns true for #{type} files" do - Gitlab::MarkdownHelper.gitlab_markdown?("README.#{type}").should be_true + expect(Gitlab::MarkdownHelper.gitlab_markdown?("README.#{type}")).to be_truthy end end it 'returns false when given a non-markdown filename' do - Gitlab::MarkdownHelper.gitlab_markdown?('README.rb').should_not be_true + expect(Gitlab::MarkdownHelper.gitlab_markdown?('README.rb')).not_to be_truthy end end end diff --git a/spec/lib/gitlab/gitorious_import/project_creator_spec.rb b/spec/lib/gitlab/gitorious_import/project_creator_spec.rb new file mode 100644 index 0000000000..c1125ca635 --- /dev/null +++ b/spec/lib/gitlab/gitorious_import/project_creator_spec.rb @@ -0,0 +1,26 @@ +require 'spec_helper' + +describe Gitlab::GitoriousImport::ProjectCreator do + let(:user) { create(:user) } + let(:repo) { Gitlab::GitoriousImport::Repository.new('foo/bar-baz-qux') } + let(:namespace){ create(:group, owner: user) } + + before do + namespace.add_owner(user) + end + + it 'creates project' do + allow_any_instance_of(Project).to receive(:add_import_job) + + project_creator = Gitlab::GitoriousImport::ProjectCreator.new(repo, namespace, user) + project = project_creator.execute + + expect(project.name).to eq("Bar Baz Qux") + expect(project.path).to eq("bar-baz-qux") + expect(project.namespace).to eq(namespace) + expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC) + expect(project.import_type).to eq("gitorious") + expect(project.import_source).to eq("foo/bar-baz-qux") + expect(project.import_url).to eq("https://gitorious.org/foo/bar-baz-qux.git") + end +end diff --git a/spec/lib/gitlab/google_code_import/client_spec.rb b/spec/lib/gitlab/google_code_import/client_spec.rb new file mode 100644 index 0000000000..d2bf871daa --- /dev/null +++ b/spec/lib/gitlab/google_code_import/client_spec.rb @@ -0,0 +1,34 @@ +require "spec_helper" + +describe Gitlab::GoogleCodeImport::Client do + let(:raw_data) { JSON.parse(File.read(Rails.root.join("spec/fixtures/GoogleCodeProjectHosting.json"))) } + subject { described_class.new(raw_data) } + + describe "#valid?" do + context "when the data is valid" do + it "returns true" do + expect(subject).to be_valid + end + end + + context "when the data is invalid" do + let(:raw_data) { "No clue" } + + it "returns true" do + expect(subject).to_not be_valid + end + end + end + + describe "#repos" do + it "returns only Git repositories" do + expect(subject.repos.length).to eq(1) + end + end + + describe "#repo" do + it "returns the referenced repository" do + expect(subject.repo("tint2").name).to eq("tint2") + end + end +end diff --git a/spec/lib/gitlab/google_code_import/importer_spec.rb b/spec/lib/gitlab/google_code_import/importer_spec.rb new file mode 100644 index 0000000000..6737832833 --- /dev/null +++ b/spec/lib/gitlab/google_code_import/importer_spec.rb @@ -0,0 +1,85 @@ +require "spec_helper" + +describe Gitlab::GoogleCodeImport::Importer do + let(:mapped_user) { create(:user, username: "thilo123") } + let(:raw_data) { JSON.parse(File.read(Rails.root.join("spec/fixtures/GoogleCodeProjectHosting.json"))) } + let(:client) { Gitlab::GoogleCodeImport::Client.new(raw_data) } + let(:import_data) { + { + "repo" => client.repo("tint2").raw_data, + "user_map" => { + "thilo..." => "@#{mapped_user.username}" + } + } + } + let(:project) { create(:project) } + subject { described_class.new(project) } + + before do + project.create_import_data(data: import_data) + end + + describe "#execute" do + + it "imports status labels" do + subject.execute + + %w(New NeedInfo Accepted Wishlist Started Fixed Invalid Duplicate WontFix Incomplete).each do |status| + expect(project.labels.find_by(name: "Status: #{status}")).to_not be_nil + end + end + + it "imports labels" do + subject.execute + + %w( + Type-Defect Type-Enhancement Type-Task Type-Review Type-Other Milestone-0.12 Priority-Critical + Priority-High Priority-Medium Priority-Low OpSys-All OpSys-Windows OpSys-Linux OpSys-OSX Security + Performance Usability Maintainability Component-Panel Component-Taskbar Component-Battery + Component-Systray Component-Clock Component-Launcher Component-Tint2conf Component-Docs Component-New + ).each do |label| + label.sub!("-", ": ") + expect(project.labels.find_by(name: label)).to_not be_nil + end + end + + it "imports issues" do + subject.execute + + issue = project.issues.first + expect(issue).to_not be_nil + expect(issue.iid).to eq(169) + expect(issue.author).to eq(project.creator) + expect(issue.assignee).to eq(mapped_user) + expect(issue.state).to eq("closed") + expect(issue.label_names).to include("Priority: Medium") + expect(issue.label_names).to include("Status: Fixed") + expect(issue.label_names).to include("Type: Enhancement") + expect(issue.title).to eq("Scrolling through tasks") + expect(issue.state).to eq("closed") + expect(issue.description).to include("schattenpr\\.\\.\\.") + expect(issue.description).to include("November 18, 2009 00:20") + expect(issue.description).to include("Google Code") + expect(issue.description).to include('I like to scroll through the tasks with my scrollwheel (like in fluxbox).') + expect(issue.description).to include('Patch is attached that adds two new mouse-actions (next_task+prev_task)') + expect(issue.description).to include('that can be used for exactly that purpose.') + expect(issue.description).to include('all the best!') + expect(issue.description).to include('[tint2_task_scrolling.diff](https://storage.googleapis.com/google-code-attachments/tint2/issue-169/comment-0/tint2_task_scrolling.diff)') + expect(issue.description).to include('![screenshot.png](https://storage.googleapis.com/google-code-attachments/tint2/issue-169/comment-0/screenshot.png)') + end + + it "imports issue comments" do + subject.execute + + note = project.issues.first.notes.first + expect(note).to_not be_nil + expect(note.note).to include("Comment 1") + expect(note.note).to include("@#{mapped_user.username}") + expect(note.note).to include("November 18, 2009 05:14") + expect(note.note).to include("applied, thanks.") + expect(note.note).to include("Status: Fixed") + expect(note.note).to include("~~Type: Defect~~") + expect(note.note).to include("Type: Enhancement") + end + end +end diff --git a/spec/lib/gitlab/google_code_import/project_creator_spec.rb b/spec/lib/gitlab/google_code_import/project_creator_spec.rb new file mode 100644 index 0000000000..7a224538b8 --- /dev/null +++ b/spec/lib/gitlab/google_code_import/project_creator_spec.rb @@ -0,0 +1,27 @@ +require 'spec_helper' + +describe Gitlab::GoogleCodeImport::ProjectCreator do + let(:user) { create(:user) } + let(:repo) { + Gitlab::GoogleCodeImport::Repository.new( + "name" => 'vim', + "summary" => 'VI Improved', + "repositoryUrls" => [ "https://vim.googlecode.com/git/" ] + ) + } + let(:namespace){ create(:group, owner: user) } + + before do + namespace.add_owner(user) + end + + it 'creates project' do + allow_any_instance_of(Project).to receive(:add_import_job) + + project_creator = Gitlab::GoogleCodeImport::ProjectCreator.new(repo, namespace, user) + project = project_creator.execute + + expect(project.import_url).to eq("https://vim.googlecode.com/git/") + expect(project.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC) + end +end diff --git a/spec/lib/gitlab/key_fingerprint_spec.rb b/spec/lib/gitlab/key_fingerprint_spec.rb new file mode 100644 index 0000000000..266eab6e79 --- /dev/null +++ b/spec/lib/gitlab/key_fingerprint_spec.rb @@ -0,0 +1,12 @@ +require "spec_helper" + +describe Gitlab::KeyFingerprint do + let(:key) { "ssh-rsa AAAAB3NzaC1yc2EAAAABJQAAAIEAiPWx6WM4lhHNedGfBpPJNPpZ7yKu+dnn1SJejgt4596k6YjzGGphH2TUxwKzxcKDKKezwkpfnxPkSMkuEspGRt/aZZ9wa++Oi7Qkr8prgHc4soW6NUlfDzpvZK2H5E7eQaSeP3SAwGmQKUFHCddNaP0L+hM7zhFNzjFvpaMgJw0=" } + let(:fingerprint) { "3f:a2:ee:de:b5:de:53:c3:aa:2f:9c:45:24:4c:47:7b" } + + describe "#fingerprint" do + it "generates the key's fingerprint" do + expect(Gitlab::KeyFingerprint.new(key).fingerprint).to eq(fingerprint) + end + end +end diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb new file mode 100644 index 0000000000..707a0521ab --- /dev/null +++ b/spec/lib/gitlab/ldap/access_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +describe Gitlab::LDAP::Access do + let(:access) { Gitlab::LDAP::Access.new user } + let(:user) { create(:omniauth_user) } + + describe :allowed? do + subject { access.allowed? } + + context 'when the user cannot be found' do + before { Gitlab::LDAP::Person.stub(find_by_dn: nil) } + + it { is_expected.to be_falsey } + end + + context 'when the user is found' do + before { Gitlab::LDAP::Person.stub(find_by_dn: :ldap_user) } + + context 'and the user is diabled via active directory' do + before { Gitlab::LDAP::Person.stub(disabled_via_active_directory?: true) } + + it { is_expected.to be_falsey } + + it "should block user in GitLab" do + access.allowed? + user.should be_blocked + end + end + + context 'and has no disabled flag in active diretory' do + before do + user.block + + Gitlab::LDAP::Person.stub(disabled_via_active_directory?: false) + end + + it { is_expected.to be_truthy } + + it "should unblock user in GitLab" do + access.allowed? + user.should_not be_blocked + end + end + + context 'without ActiveDirectory enabled' do + before do + Gitlab::LDAP::Config.stub(enabled?: true) + Gitlab::LDAP::Config.any_instance.stub(active_directory: false) + end + + it { is_expected.to be_truthy } + end + end + end +end diff --git a/spec/lib/gitlab/ldap/ldap_adapter_spec.rb b/spec/lib/gitlab/ldap/adapter_spec.rb similarity index 78% rename from spec/lib/gitlab/ldap/ldap_adapter_spec.rb rename to spec/lib/gitlab/ldap/adapter_spec.rb index c3f0733443..b609e4b38f 100644 --- a/spec/lib/gitlab/ldap/ldap_adapter_spec.rb +++ b/spec/lib/gitlab/ldap/adapter_spec.rb @@ -1,7 +1,7 @@ require 'spec_helper' describe Gitlab::LDAP::Adapter do - let(:adapter) { Gitlab::LDAP::Adapter.new } + let(:adapter) { Gitlab::LDAP::Adapter.new 'ldapmain' } describe :dn_matches_filter? do let(:ldap) { double(:ldap) } @@ -12,20 +12,20 @@ describe Gitlab::LDAP::Adapter do context "and the result is non-empty" do before { ldap.stub(search: [:foo]) } - it { should be_true } + it { is_expected.to be_truthy } end context "and the result is empty" do before { ldap.stub(search: []) } - it { should be_false } + it { is_expected.to be_falsey } end end context "when the search encounters an error" do before { ldap.stub(search: nil, get_operation_result: double(code: 1, message: 'some error')) } - it { should be_false } + it { is_expected.to be_falsey } end end end diff --git a/spec/lib/gitlab/ldap/authentication_spec.rb b/spec/lib/gitlab/ldap/authentication_spec.rb new file mode 100644 index 0000000000..8afc2b21f4 --- /dev/null +++ b/spec/lib/gitlab/ldap/authentication_spec.rb @@ -0,0 +1,53 @@ +require 'spec_helper' + +describe Gitlab::LDAP::Authentication do + let(:klass) { Gitlab::LDAP::Authentication } + let(:user) { create(:omniauth_user, extern_uid: dn) } + let(:dn) { 'uid=john,ou=people,dc=example,dc=com' } + let(:login) { 'john' } + let(:password) { 'password' } + + describe :login do + let(:adapter) { double :adapter } + before do + Gitlab::LDAP::Config.stub(enabled?: true) + end + + it "finds the user if authentication is successful" do + user + # try only to fake the LDAP call + klass.any_instance.stub(adapter: double(:adapter, + bind_as: double(:ldap_user, dn: dn) + )) + expect(klass.login(login, password)).to be_truthy + end + + it "is false if the user does not exist" do + # try only to fake the LDAP call + klass.any_instance.stub(adapter: double(:adapter, + bind_as: double(:ldap_user, dn: dn) + )) + expect(klass.login(login, password)).to be_falsey + end + + it "is false if authentication fails" do + user + # try only to fake the LDAP call + klass.any_instance.stub(adapter: double(:adapter, bind_as: nil)) + expect(klass.login(login, password)).to be_falsey + end + + it "fails if ldap is disabled" do + Gitlab::LDAP::Config.stub(enabled?: false) + expect(klass.login(login, password)).to be_falsey + end + + it "fails if no login is supplied" do + expect(klass.login('', password)).to be_falsey + end + + it "fails if no password is supplied" do + expect(klass.login(login, '')).to be_falsey + end + end +end \ No newline at end of file diff --git a/spec/lib/gitlab/ldap/config_spec.rb b/spec/lib/gitlab/ldap/config_spec.rb new file mode 100644 index 0000000000..00e9076c78 --- /dev/null +++ b/spec/lib/gitlab/ldap/config_spec.rb @@ -0,0 +1,20 @@ +require 'spec_helper' + +describe Gitlab::LDAP::Config do + let(:config) { Gitlab::LDAP::Config.new provider } + let(:provider) { 'ldapmain' } + + describe '#initalize' do + it 'requires a provider' do + expect{ Gitlab::LDAP::Config.new }.to raise_error ArgumentError + end + + it "works" do + expect(config).to be_a described_class + end + + it "raises an error if a unknow provider is used" do + expect{ Gitlab::LDAP::Config.new 'unknown' }.to raise_error + end + end +end diff --git a/spec/lib/gitlab/ldap/ldap_access_spec.rb b/spec/lib/gitlab/ldap/ldap_access_spec.rb deleted file mode 100644 index d8c107502b..0000000000 --- a/spec/lib/gitlab/ldap/ldap_access_spec.rb +++ /dev/null @@ -1,32 +0,0 @@ -require 'spec_helper' - -describe Gitlab::LDAP::Access do - let(:access) { Gitlab::LDAP::Access.new } - let(:user) { create(:user) } - - describe :allowed? do - subject { access.allowed?(user) } - - context 'when the user cannot be found' do - before { Gitlab::LDAP::Person.stub(find_by_dn: nil) } - - it { should be_false } - end - - context 'when the user is found' do - before { Gitlab::LDAP::Person.stub(find_by_dn: :ldap_user) } - - context 'and the Active Directory disabled flag is set' do - before { Gitlab::LDAP::Person.stub(active_directory_disabled?: true) } - - it { should be_false } - end - - context 'and the Active Directory disabled flag is not set' do - before { Gitlab::LDAP::Person.stub(active_directory_disabled?: false) } - - it { should be_true } - end - end - end -end diff --git a/spec/lib/gitlab/ldap/ldap_user_auth_spec.rb b/spec/lib/gitlab/ldap/ldap_user_auth_spec.rb deleted file mode 100644 index 501642dca7..0000000000 --- a/spec/lib/gitlab/ldap/ldap_user_auth_spec.rb +++ /dev/null @@ -1,58 +0,0 @@ -require 'spec_helper' - -describe Gitlab::LDAP do - let(:gl_auth) { Gitlab::LDAP::User } - - before do - Gitlab.config.stub(omniauth: {}) - - @info = double( - uid: '12djsak321', - name: 'John', - email: 'john@mail.com', - nickname: 'john' - ) - end - - describe :find_for_ldap_auth do - before do - @auth = double( - uid: '12djsak321', - info: @info, - provider: 'ldap' - ) - end - - it "should update credentials by email if missing uid" do - user = double('User') - User.stub find_by_extern_uid_and_provider: nil - User.stub(:find_by).with(hash_including(email: anything())) { user } - user.should_receive :update_attributes - gl_auth.find_or_create(@auth) - end - - it "should update credentials by username if missing uid and Gitlab.config.ldap.allow_username_or_email_login is true" do - user = double('User') - value = Gitlab.config.ldap.allow_username_or_email_login - Gitlab.config.ldap['allow_username_or_email_login'] = true - User.stub find_by_extern_uid_and_provider: nil - User.stub(:find_by).with(hash_including(email: anything())) { nil } - User.stub(:find_by).with(hash_including(username: anything())) { user } - user.should_receive :update_attributes - gl_auth.find_or_create(@auth) - Gitlab.config.ldap['allow_username_or_email_login'] = value - end - - it "should not update credentials by username if missing uid and Gitlab.config.ldap.allow_username_or_email_login is false" do - user = double('User') - value = Gitlab.config.ldap.allow_username_or_email_login - Gitlab.config.ldap['allow_username_or_email_login'] = false - User.stub find_by_extern_uid_and_provider: nil - User.stub(:find_by).with(hash_including(email: anything())) { nil } - User.stub(:find_by).with(hash_including(username: anything())) { user } - user.should_not_receive :update_attributes - gl_auth.find_or_create(@auth) - Gitlab.config.ldap['allow_username_or_email_login'] = value - end - end -end diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb new file mode 100644 index 0000000000..42015c28c8 --- /dev/null +++ b/spec/lib/gitlab/ldap/user_spec.rb @@ -0,0 +1,106 @@ +require 'spec_helper' + +describe Gitlab::LDAP::User do + let(:ldap_user) { Gitlab::LDAP::User.new(auth_hash) } + let(:gl_user) { ldap_user.gl_user } + let(:info) do + { + name: 'John', + email: 'john@example.com', + nickname: 'john' + } + end + let(:auth_hash) do + double(uid: 'my-uid', provider: 'ldapmain', info: double(info)) + end + + describe :changed? do + it "marks existing ldap user as changed" do + existing_user = create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain') + expect(ldap_user.changed?).to be_truthy + end + + it "marks existing non-ldap user if the email matches as changed" do + existing_user = create(:user, email: 'john@example.com') + expect(ldap_user.changed?).to be_truthy + end + + it "dont marks existing ldap user as changed" do + existing_user = create(:omniauth_user, email: 'john@example.com', extern_uid: 'my-uid', provider: 'ldapmain') + expect(ldap_user.changed?).to be_falsey + end + end + + describe :find_or_create do + it "finds the user if already existing" do + existing_user = create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain') + + expect{ ldap_user.save }.to_not change{ User.count } + end + + it "connects to existing non-ldap user if the email matches" do + existing_user = create(:omniauth_user, email: 'john@example.com', provider: "twitter") + expect{ ldap_user.save }.to_not change{ User.count } + + existing_user.reload + expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid' + expect(existing_user.ldap_identity.provider).to eql 'ldapmain' + end + + it "creates a new user if not found" do + expect{ ldap_user.save }.to change{ User.count }.by(1) + end + end + + + describe 'blocking' do + context 'signup' do + context 'dont block on create' do + before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: false } + + it do + ldap_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end + + context 'block on create' do + before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: true } + + it do + ldap_user.save + expect(gl_user).to be_valid + expect(gl_user).to be_blocked + end + end + end + + context 'sign-in' do + before do + ldap_user.save + ldap_user.gl_user.activate + end + + context 'dont block on create' do + before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: false } + + it do + ldap_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end + + context 'block on create' do + before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: true } + + it do + ldap_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end + end + end +end diff --git a/spec/lib/gitlab/note_data_builder_spec.rb b/spec/lib/gitlab/note_data_builder_spec.rb new file mode 100644 index 0000000000..448cd0c688 --- /dev/null +++ b/spec/lib/gitlab/note_data_builder_spec.rb @@ -0,0 +1,73 @@ +require 'spec_helper' + +describe 'Gitlab::NoteDataBuilder' do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:data) { Gitlab::NoteDataBuilder.build(note, user) } + let(:note_url) { Gitlab::UrlBuilder.new(:note).build(note.id) } + let(:fixed_time) { Time.at(1425600000) } # Avoid time precision errors + + 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(note_url) + expect(data[:object_kind]).to eq('note') + expect(data[:user]).to eq(user.hook_attrs) + end + + describe 'When asking for a note on commit' do + let(:note) { create(:note_on_commit) } + + it 'returns the note and commit-specific data' do + expect(data).to have_key(:commit) + end + end + + describe 'When asking for a note on commit diff' do + let(:note) { create(:note_on_commit_diff) } + + it 'returns the note and commit-specific data' do + expect(data).to have_key(:commit) + end + 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) } + + it 'returns the note and issue-specific data' do + expect(data).to have_key(:issue) + expect(data[:issue]).to eq(issue.hook_attrs) + end + 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) } + + it 'returns the note and merge request data' do + expect(data).to have_key(:merge_request) + expect(data[:merge_request]).to eq(merge_request.hook_attrs) + end + 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) } + + it 'returns the note and merge request diff data' do + expect(data).to have_key(:merge_request) + expect(data[:merge_request]).to eq(merge_request.hook_attrs) + end + 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) } + + it 'returns the note and project snippet data' do + expect(data).to have_key(:snippet) + expect(data[:snippet]).to eq(snippet.hook_attrs) + end + end +end diff --git a/spec/lib/gitlab/o_auth/auth_hash_spec.rb b/spec/lib/gitlab/o_auth/auth_hash_spec.rb new file mode 100644 index 0000000000..5eb77b492b --- /dev/null +++ b/spec/lib/gitlab/o_auth/auth_hash_spec.rb @@ -0,0 +1,55 @@ +require 'spec_helper' + +describe Gitlab::OAuth::AuthHash do + let(:auth_hash) do + Gitlab::OAuth::AuthHash.new(double({ + provider: 'twitter', + uid: uid, + info: double(info_hash) + })) + end + let(:uid) { 'my-uid' } + let(:email) { 'my-email@example.com' } + let(:nickname) { 'my-nickname' } + let(:info_hash) { + { + email: email, + nickname: nickname, + name: 'John', + first_name: "John", + last_name: "Who" + } + } + + context "defaults" do + it { expect(auth_hash.provider).to eql 'twitter' } + it { expect(auth_hash.uid).to eql uid } + it { expect(auth_hash.email).to eql email } + it { expect(auth_hash.username).to eql nickname } + it { expect(auth_hash.name).to eql "John" } + it { expect(auth_hash.password).to_not be_empty } + end + + context "email not provided" do + before { info_hash.delete(:email) } + it "generates a temp email" do + expect( auth_hash.email).to start_with('temp-email-for-oauth') + end + end + + context "username not provided" do + before { info_hash.delete(:nickname) } + + it "takes the first part of the email as username" do + expect( auth_hash.username ).to eql "my-email" + end + end + + context "name not provided" do + before { info_hash.delete(:name) } + + it "concats first and lastname as the name" do + expect( auth_hash.name ).to eql "John Who" + end + end +end \ No newline at end of file diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb new file mode 100644 index 0000000000..44cdd1e4fa --- /dev/null +++ b/spec/lib/gitlab/o_auth/user_spec.rb @@ -0,0 +1,109 @@ +require 'spec_helper' + +describe Gitlab::OAuth::User do + let(:oauth_user) { Gitlab::OAuth::User.new(auth_hash) } + let(:gl_user) { oauth_user.gl_user } + let(:uid) { 'my-uid' } + let(:provider) { 'my-provider' } + let(:auth_hash) { double(uid: uid, provider: provider, info: double(info_hash)) } + let(:info_hash) do + { + nickname: '-john+gitlab-ETC%.git@gmail.com', + name: 'John', + email: 'john@mail.com' + } + end + + describe :persisted? do + let!(:existing_user) { create(:omniauth_user, extern_uid: 'my-uid', provider: 'my-provider') } + + it "finds an existing user based on uid and provider (facebook)" do + auth = double(info: double(name: 'John'), uid: 'my-uid', provider: 'my-provider') + expect( oauth_user.persisted? ).to be_truthy + end + + it "returns false if use is not found in database" do + auth_hash.stub(uid: 'non-existing') + expect( oauth_user.persisted? ).to be_falsey + end + end + + describe :save do + let(:provider) { 'twitter' } + + describe 'signup' do + context "with allow_single_sign_on enabled" do + before { Gitlab.config.omniauth.stub allow_single_sign_on: true } + + it "creates a user from Omniauth" do + oauth_user.save + + expect(gl_user).to be_valid + identity = gl_user.identities.first + expect(identity.extern_uid).to eql uid + expect(identity.provider).to eql 'twitter' + end + end + + context "with allow_single_sign_on disabled (Default)" do + it "throws an error" do + expect{ oauth_user.save }.to raise_error StandardError + end + end + end + + describe 'blocking' do + let(:provider) { 'twitter' } + before { Gitlab.config.omniauth.stub allow_single_sign_on: true } + + context 'signup' do + context 'dont block on create' do + before { Gitlab.config.omniauth.stub block_auto_created_users: false } + + it do + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end + + context 'block on create' do + before { Gitlab.config.omniauth.stub block_auto_created_users: true } + + it do + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user).to be_blocked + end + end + end + + context 'sign-in' do + before do + oauth_user.save + oauth_user.gl_user.activate + end + + context 'dont block on create' do + before { Gitlab.config.omniauth.stub block_auto_created_users: false } + + it do + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end + + context 'block on create' do + before { Gitlab.config.omniauth.stub block_auto_created_users: true } + + it do + oauth_user.save + expect(gl_user).to be_valid + expect(gl_user).not_to be_blocked + end + end + end + end + end +end diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb index 76d506eb3c..cd9d0456b2 100644 --- a/spec/lib/gitlab/popen_spec.rb +++ b/spec/lib/gitlab/popen_spec.rb @@ -13,8 +13,8 @@ describe 'Gitlab::Popen', no_db: true do @output, @status = @klass.new.popen(%W(ls), path) end - it { @status.should be_zero } - it { @output.should include('cache') } + it { expect(@status).to be_zero } + it { expect(@output).to include('cache') } end context 'non-zero status' do @@ -22,8 +22,8 @@ describe 'Gitlab::Popen', no_db: true do @output, @status = @klass.new.popen(%W(cat NOTHING), path) end - it { @status.should == 1 } - it { @output.should include('No such file or directory') } + it { expect(@status).to eq(1) } + it { expect(@output).to include('No such file or directory') } end context 'unsafe string command' do @@ -37,8 +37,8 @@ describe 'Gitlab::Popen', no_db: true do @output, @status = @klass.new.popen(%W(ls)) end - it { @status.should be_zero } - it { @output.should include('spec') } + it { expect(@status).to be_zero } + it { expect(@output).to include('spec') } end end diff --git a/spec/lib/gitlab/push_data_builder_spec.rb b/spec/lib/gitlab/push_data_builder_spec.rb new file mode 100644 index 0000000000..1b8ba7b4d4 --- /dev/null +++ b/spec/lib/gitlab/push_data_builder_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +describe 'Gitlab::PushDataBuilder' do + let(:project) { create(:project) } + let(:user) { create(:user) } + + + describe :build_sample do + let(:data) { Gitlab::PushDataBuilder.build_sample(project, user) } + + it { expect(data).to be_a(Hash) } + it { expect(data[:before]).to eq('6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9') } + it { expect(data[:after]).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') } + it { expect(data[:ref]).to eq('refs/heads/master') } + it { expect(data[:commits].size).to eq(3) } + it { expect(data[:repository][:git_http_url]).to eq(project.http_url_to_repo) } + it { expect(data[:repository][:git_ssh_url]).to eq(project.ssh_url_to_repo) } + it { expect(data[:repository][:visibility_level]).to eq(project.visibility_level) } + it { expect(data[:total_commits_count]).to eq(3) } + end + + describe :build do + let(:data) do + Gitlab::PushDataBuilder.build(project, + user, + Gitlab::Git::BLANK_SHA, + '8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b', + 'refs/tags/v1.1.0') + end + + it { expect(data).to be_a(Hash) } + it { expect(data[:before]).to eq(Gitlab::Git::BLANK_SHA) } + it { expect(data[:checkout_sha]).to eq('5937ac0a7beb003549fc5fd26fc247adbce4a52e') } + it { expect(data[:after]).to eq('8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b') } + it { expect(data[:ref]).to eq('refs/tags/v1.1.0') } + it { expect(data[:commits]).to be_empty } + it { expect(data[:total_commits_count]).to be_zero } + end +end diff --git a/spec/lib/gitlab/reference_extractor_spec.rb b/spec/lib/gitlab/reference_extractor_spec.rb index 99fed27c79..c9fb62b61a 100644 --- a/spec/lib/gitlab/reference_extractor_spec.rb +++ b/spec/lib/gitlab/reference_extractor_spec.rb @@ -1,101 +1,156 @@ require 'spec_helper' describe Gitlab::ReferenceExtractor do + let(:project) { create(:project) } + subject { Gitlab::ReferenceExtractor.new(project, project.creator) } + it 'extracts username references' do - subject.analyze "this contains a @user reference" - subject.users.should == ["user"] + subject.analyze('this contains a @user reference') + expect(subject.references[:user]).to eq([[project, 'user']]) end it 'extracts issue references' do - subject.analyze "this one talks about issue #1234" - subject.issues.should == ["1234"] + subject.analyze('this one talks about issue #1234') + expect(subject.references[:issue]).to eq([[project, '1234']]) end it 'extracts JIRA issue references' do - Gitlab.config.gitlab.stub(:issues_tracker).and_return("jira") - subject.analyze "this one talks about issue JIRA-1234" - subject.issues.should == ["JIRA-1234"] + subject.analyze('this one talks about issue JIRA-1234') + expect(subject.references[:issue]).to eq([[project, 'JIRA-1234']]) end it 'extracts merge request references' do - subject.analyze "and here's !43, a merge request" - subject.merge_requests.should == ["43"] + subject.analyze("and here's !43, a merge request") + expect(subject.references[:merge_request]).to eq([[project, '43']]) end it 'extracts snippet ids' do - subject.analyze "snippets like $12 get extracted as well" - subject.snippets.should == ["12"] + subject.analyze('snippets like $12 get extracted as well') + expect(subject.references[:snippet]).to eq([[project, '12']]) end it 'extracts commit shas' do - subject.analyze "commit shas 98cf0ae3 are pulled out as Strings" - subject.commits.should == ["98cf0ae3"] + subject.analyze('commit shas 98cf0ae3 are pulled out as Strings') + expect(subject.references[:commit]).to eq([[project, '98cf0ae3']]) + end + + it 'extracts commit ranges' do + subject.analyze('here you go, a commit range: 98cf0ae3...98cf0ae4') + expect(subject.references[:commit_range]).to eq([[project, '98cf0ae3...98cf0ae4']]) end it 'extracts multiple references and preserves their order' do - subject.analyze "@me and @you both care about this" - subject.users.should == ["me", "you"] + subject.analyze('@me and @you both care about this') + expect(subject.references[:user]).to eq([ + [project, 'me'], + [project, 'you'] + ]) end it 'leaves the original note unmodified' do - text = "issue #123 is just the worst, @user" - subject.analyze text - text.should == "issue #123 is just the worst, @user" + text = 'issue #123 is just the worst, @user' + subject.analyze(text) + expect(text).to eq('issue #123 is just the worst, @user') + end + + it 'extracts no references for
    ..
    blocks' do + subject.analyze("
    def puts '#1 issue'\nend\n
    ```") + expect(subject.issues).to be_blank + end + + it 'extracts no references for .. blocks' do + subject.analyze("def puts '!1 request'\nend\n```") + expect(subject.merge_requests).to be_blank + end + + it 'extracts no references for code blocks with language' do + subject.analyze("this code:\n```ruby\ndef puts '#1 issue'\nend\n```") + expect(subject.issues).to be_blank + end + + it 'extracts issue references for invalid code blocks' do + subject.analyze('test: ```this one talks about issue #1234```') + expect(subject.references[:issue]).to eq([[project, '1234']]) end it 'handles all possible kinds of references' do accessors = Gitlab::Markdown::TYPES.map { |t| "#{t}s".to_sym } - subject.should respond_to(*accessors) + expect(subject).to respond_to(*accessors) end - context 'with a project' do - let(:project) { create(:project) } + it 'accesses valid user objects' do + @u_foo = create(:user, username: 'foo') + @u_bar = create(:user, username: 'bar') + @u_offteam = create(:user, username: 'offteam') - it 'accesses valid user objects on the project team' do - @u_foo = create(:user, username: 'foo') - @u_bar = create(:user, username: 'bar') - create(:user, username: 'offteam') + project.team << [@u_foo, :reporter] + project.team << [@u_bar, :guest] - project.team << [@u_foo, :reporter] - project.team << [@u_bar, :guest] + subject.analyze('@foo, @baduser, @bar, and @offteam') + expect(subject.users).to eq([@u_foo, @u_bar, @u_offteam]) + end - subject.analyze "@foo, @baduser, @bar, and @offteam" - subject.users_for(project).should == [@u_foo, @u_bar] + it 'accesses valid issue objects' do + @i0 = create(:issue, project: project) + @i1 = create(:issue, project: project) + + subject.analyze("##{@i0.iid}, ##{@i1.iid}, and #999.") + expect(subject.issues).to eq([@i0, @i1]) + end + + it 'accesses valid merge requests' do + @m0 = create(:merge_request, source_project: project, target_project: project, source_branch: 'aaa') + @m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'bbb') + + subject.analyze("!999, !#{@m1.iid}, and !#{@m0.iid}.") + expect(subject.merge_requests).to eq([@m1, @m0]) + end + + it 'accesses valid snippets' do + @s0 = create(:project_snippet, project: project) + @s1 = create(:project_snippet, project: project) + @s2 = create(:project_snippet) + + subject.analyze("$#{@s0.id}, $999, $#{@s2.id}, $#{@s1.id}") + expect(subject.snippets).to eq([@s0, @s1]) + end + + it 'accesses valid commits' do + commit = project.repository.commit('master') + + subject.analyze("this references commits #{commit.sha[0..6]} and 012345") + extracted = subject.commits + expect(extracted.size).to eq(1) + expect(extracted[0].sha).to eq(commit.sha) + expect(extracted[0].message).to eq(commit.message) + end + + it 'accesses valid commit ranges' do + commit = project.repository.commit('master') + earlier_commit = project.repository.commit('master~2') + + subject.analyze("this references commits #{earlier_commit.sha[0..6]}...#{commit.sha[0..6]}") + extracted = subject.commit_ranges + expect(extracted.size).to eq(1) + expect(extracted[0][0].sha).to eq(earlier_commit.sha) + expect(extracted[0][0].message).to eq(earlier_commit.message) + expect(extracted[0][1].sha).to eq(commit.sha) + expect(extracted[0][1].message).to eq(commit.message) + end + + context 'with a project with an underscore' do + let(:other_project) { create(:project, path: 'test_project') } + let(:issue) { create(:issue, project: other_project) } + + before do + other_project.team << [project.creator, :developer] end - it 'accesses valid issue objects' do - @i0 = create(:issue, project: project) - @i1 = create(:issue, project: project) - - subject.analyze "##{@i0.iid}, ##{@i1.iid}, and #999." - subject.issues_for(project).should == [@i0, @i1] - end - - it 'accesses valid merge requests' do - @m0 = create(:merge_request, source_project: project, target_project: project, source_branch: 'aaa') - @m1 = create(:merge_request, source_project: project, target_project: project, source_branch: 'bbb') - - subject.analyze "!999, !#{@m1.iid}, and !#{@m0.iid}." - subject.merge_requests_for(project).should == [@m1, @m0] - end - - it 'accesses valid snippets' do - @s0 = create(:project_snippet, project: project) - @s1 = create(:project_snippet, project: project) - @s2 = create(:project_snippet) - - subject.analyze "$#{@s0.id}, $999, $#{@s2.id}, $#{@s1.id}" - subject.snippets_for(project).should == [@s0, @s1] - end - - it 'accesses valid commits' do - commit = project.repository.commit("master") - - subject.analyze "this references commits #{commit.sha[0..6]} and 012345" - extracted = subject.commits_for(project) - extracted.should have(1).item - extracted[0].sha.should == commit.sha - extracted[0].message.should == commit.message + it 'handles project issue references' do + subject.analyze("this refers issue #{other_project.path_with_namespace}##{issue.iid}") + extracted = subject.issues + expect(extracted.size).to eq(1) + expect(extracted).to eq([issue]) end end end diff --git a/spec/lib/gitlab/regex_spec.rb b/spec/lib/gitlab/regex_spec.rb index a3aae7771b..727884c41c 100644 --- a/spec/lib/gitlab/regex_spec.rb +++ b/spec/lib/gitlab/regex_spec.rb @@ -1,21 +1,21 @@ require 'spec_helper' describe Gitlab::Regex do - describe 'path regex' do - it { 'gitlab-ce'.should match(Gitlab::Regex.path_regex) } - it { 'gitlab_git'.should match(Gitlab::Regex.path_regex) } - it { '_underscore.js'.should match(Gitlab::Regex.path_regex) } - it { '100px.com'.should match(Gitlab::Regex.path_regex) } - it { '?gitlab'.should_not match(Gitlab::Regex.path_regex) } - it { 'git lab'.should_not match(Gitlab::Regex.path_regex) } - it { 'gitlab.git'.should_not match(Gitlab::Regex.path_regex) } + describe 'project path regex' do + it { expect('gitlab-ce').to match(Gitlab::Regex.project_path_regex) } + it { expect('gitlab_git').to match(Gitlab::Regex.project_path_regex) } + it { expect('_underscore.js').to match(Gitlab::Regex.project_path_regex) } + it { expect('100px.com').to match(Gitlab::Regex.project_path_regex) } + it { expect('?gitlab').not_to match(Gitlab::Regex.project_path_regex) } + it { expect('git lab').not_to match(Gitlab::Regex.project_path_regex) } + it { expect('gitlab.git').not_to match(Gitlab::Regex.project_path_regex) } end describe 'project name regex' do - it { 'gitlab-ce'.should match(Gitlab::Regex.project_name_regex) } - it { 'GitLab CE'.should match(Gitlab::Regex.project_name_regex) } - it { '100 lines'.should match(Gitlab::Regex.project_name_regex) } - it { 'gitlab.git'.should match(Gitlab::Regex.project_name_regex) } - it { '?gitlab'.should_not match(Gitlab::Regex.project_name_regex) } + it { expect('gitlab-ce').to match(Gitlab::Regex.project_name_regex) } + it { expect('GitLab CE').to match(Gitlab::Regex.project_name_regex) } + it { expect('100 lines').to match(Gitlab::Regex.project_name_regex) } + it { expect('gitlab.git').to match(Gitlab::Regex.project_name_regex) } + it { expect('?gitlab').not_to match(Gitlab::Regex.project_name_regex) } end end diff --git a/spec/lib/gitlab/satellite/action_spec.rb b/spec/lib/gitlab/satellite/action_spec.rb index 0622caf1e3..28e3d64ee2 100644 --- a/spec/lib/gitlab/satellite/action_spec.rb +++ b/spec/lib/gitlab/satellite/action_spec.rb @@ -6,7 +6,7 @@ describe 'Gitlab::Satellite::Action' do describe '#prepare_satellite!' do it 'should be able to fetch timeout from conf' do - Gitlab::Satellite::Action::DEFAULT_OPTIONS[:git_timeout].should == 30.seconds + expect(Gitlab::Satellite::Action::DEFAULT_OPTIONS[:git_timeout]).to eq(30.seconds) end it 'create a repository with a parking branch and one remote: origin' do @@ -15,22 +15,22 @@ describe 'Gitlab::Satellite::Action' do #now lets dirty it up starting_remote_count = repo.git.list_remotes.size - starting_remote_count.should >= 1 + expect(starting_remote_count).to be >= 1 #kind of hookey way to add a second remote origin_uri = repo.git.remote({v: true}).split(" ")[1] begin repo.git.remote({raise: true}, 'add', 'another-remote', origin_uri) repo.git.branch({raise: true}, 'a-new-branch') - repo.heads.size.should > (starting_remote_count) - repo.git.remote().split(" ").size.should > (starting_remote_count) + expect(repo.heads.size).to be > (starting_remote_count) + expect(repo.git.remote().split(" ").size).to be > (starting_remote_count) rescue end repo.git.config({}, "user.name", "#{user.name} -- foo") repo.git.config({}, "user.email", "#{user.email} -- foo") - repo.config['user.name'].should =="#{user.name} -- foo" - repo.config['user.email'].should =="#{user.email} -- foo" + expect(repo.config['user.name']).to eq("#{user.name} -- foo") + expect(repo.config['user.email']).to eq("#{user.email} -- foo") #These must happen in the context of the satellite directory... @@ -42,13 +42,13 @@ describe 'Gitlab::Satellite::Action' do #verify it's clean heads = repo.heads.map(&:name) - heads.size.should == 1 - heads.include?(Gitlab::Satellite::Satellite::PARKING_BRANCH).should == true + expect(heads.size).to eq(1) + expect(heads.include?(Gitlab::Satellite::Satellite::PARKING_BRANCH)).to eq(true) remotes = repo.git.remote().split(' ') - remotes.size.should == 1 - remotes.include?('origin').should == true - repo.config['user.name'].should ==user.name - repo.config['user.email'].should ==user.email + expect(remotes.size).to eq(1) + expect(remotes.include?('origin')).to eq(true) + expect(repo.config['user.name']).to eq(user.name) + expect(repo.config['user.email']).to eq(user.email) end end @@ -59,18 +59,18 @@ describe 'Gitlab::Satellite::Action' do called = false #set assumptions - File.rm(project.satellite.lock_file) unless !File.exists? project.satellite.lock_file + FileUtils.rm_f(project.satellite.lock_file) - File.exists?(project.satellite.lock_file).should be_false + expect(File.exists?(project.satellite.lock_file)).to be_falsey satellite_action = Gitlab::Satellite::Action.new(user, project) satellite_action.send(:in_locked_and_timed_satellite) do |sat_repo| - repo.should == sat_repo - (File.exists? project.satellite.lock_file).should be_true + expect(repo).to eq(sat_repo) + expect(File.exists? project.satellite.lock_file).to be_truthy called = true end - called.should be_true + expect(called).to be_truthy end @@ -80,24 +80,24 @@ describe 'Gitlab::Satellite::Action' do # Set base assumptions if File.exists? project.satellite.lock_file - FileLockStatusChecker.new(project.satellite.lock_file).flocked?.should be_false + expect(FileLockStatusChecker.new(project.satellite.lock_file).flocked?).to be_falsey end satellite_action = Gitlab::Satellite::Action.new(user, project) satellite_action.send(:in_locked_and_timed_satellite) do |sat_repo| called = true - repo.should == sat_repo - (File.exists? project.satellite.lock_file).should be_true - FileLockStatusChecker.new(project.satellite.lock_file).flocked?.should be_true + expect(repo).to eq(sat_repo) + expect(File.exists? project.satellite.lock_file).to be_truthy + expect(FileLockStatusChecker.new(project.satellite.lock_file).flocked?).to be_truthy end - called.should be_true - FileLockStatusChecker.new(project.satellite.lock_file).flocked?.should be_false + expect(called).to be_truthy + expect(FileLockStatusChecker.new(project.satellite.lock_file).flocked?).to be_falsey end class FileLockStatusChecker < File - def flocked? &block + def flocked?(&block) status = flock LOCK_EX|LOCK_NB case status when false diff --git a/spec/lib/gitlab/satellite/merge_action_spec.rb b/spec/lib/gitlab/satellite/merge_action_spec.rb index 479a73a108..915e3ff0e5 100644 --- a/spec/lib/gitlab/satellite/merge_action_spec.rb +++ b/spec/lib/gitlab/satellite/merge_action_spec.rb @@ -13,9 +13,9 @@ describe 'Gitlab::Satellite::MergeAction' do describe '#commits_between' do def verify_commits(commits, first_commit_sha, last_commit_sha) - commits.each { |commit| commit.class.should == Gitlab::Git::Commit } - commits.first.id.should == first_commit_sha - commits.last.id.should == last_commit_sha + commits.each { |commit| expect(commit.class).to eq(Gitlab::Git::Commit) } + expect(commits.first.id).to eq(first_commit_sha) + expect(commits.last.id).to eq(last_commit_sha) end context 'on fork' do @@ -35,7 +35,7 @@ describe 'Gitlab::Satellite::MergeAction' do describe '#format_patch' do def verify_content(patch) sample_compare.commits.each do |commit| - patch.include?(commit).should be_true + expect(patch.include?(commit)).to be_truthy end end @@ -57,11 +57,11 @@ describe 'Gitlab::Satellite::MergeAction' do describe '#diffs_between_satellite tested against diff_in_satellite' do def is_a_matching_diff(diff, diffs) diff_count = diff.scan('diff --git').size - diff_count.should >= 1 - diffs.size.should == diff_count + expect(diff_count).to be >= 1 + expect(diffs.size).to eq(diff_count) diffs.each do |a_diff| - a_diff.class.should == Gitlab::Git::Diff - (diff.include? a_diff.diff).should be_true + expect(a_diff.class).to eq(Gitlab::Git::Diff) + expect(diff.include? a_diff.diff).to be_truthy end end @@ -82,23 +82,23 @@ describe 'Gitlab::Satellite::MergeAction' do describe '#can_be_merged?' do context 'on fork' do - it { Gitlab::Satellite::MergeAction.new( + it { expect(Gitlab::Satellite::MergeAction.new( merge_request_fork.author, - merge_request_fork).can_be_merged?.should be_true } + merge_request_fork).can_be_merged?).to be_truthy } - it { Gitlab::Satellite::MergeAction.new( + it { expect(Gitlab::Satellite::MergeAction.new( merge_request_fork_with_conflict.author, - merge_request_fork_with_conflict).can_be_merged?.should be_false } + merge_request_fork_with_conflict).can_be_merged?).to be_falsey } end context 'between branches' do - it { Gitlab::Satellite::MergeAction.new( + it { expect(Gitlab::Satellite::MergeAction.new( merge_request.author, - merge_request).can_be_merged?.should be_true } + merge_request).can_be_merged?).to be_truthy } - it { Gitlab::Satellite::MergeAction.new( + it { expect(Gitlab::Satellite::MergeAction.new( merge_request_with_conflict.author, - merge_request_with_conflict).can_be_merged?.should be_false } + merge_request_with_conflict).can_be_merged?).to be_falsey } end end end diff --git a/spec/lib/gitlab/upgrader_spec.rb b/spec/lib/gitlab/upgrader_spec.rb index 2b254d6b3a..ce3ea6c260 100644 --- a/spec/lib/gitlab/upgrader_spec.rb +++ b/spec/lib/gitlab/upgrader_spec.rb @@ -5,20 +5,20 @@ describe Gitlab::Upgrader do let(:current_version) { Gitlab::VERSION } describe 'current_version_raw' do - it { upgrader.current_version_raw.should == current_version } + it { expect(upgrader.current_version_raw).to eq(current_version) } end describe 'latest_version?' do it 'should be true if newest version' do upgrader.stub(latest_version_raw: current_version) - upgrader.latest_version?.should be_true + expect(upgrader.latest_version?).to be_truthy end end describe 'latest_version_raw' do it 'should be latest version for GitLab 5' do upgrader.stub(current_version_raw: "5.3.0") - upgrader.latest_version_raw.should == "v5.4.2" + expect(upgrader.latest_version_raw).to eq("v5.4.2") end end end diff --git a/spec/lib/gitlab/url_builder_spec.rb b/spec/lib/gitlab/url_builder_spec.rb index eb47bee833..5153ed15af 100644 --- a/spec/lib/gitlab/url_builder_spec.rb +++ b/spec/lib/gitlab/url_builder_spec.rb @@ -5,7 +5,73 @@ describe Gitlab::UrlBuilder do it 'returns the issue url' do issue = create(:issue) url = Gitlab::UrlBuilder.new(:issue).build(issue.id) - expect(url).to eq "#{Settings.gitlab['url']}/#{issue.project.to_param}/issues/#{issue.iid}" + expect(url).to eq "#{Settings.gitlab['url']}/#{issue.project.path_with_namespace}/issues/#{issue.iid}" + end + end + + describe 'When asking for an merge request' do + it 'returns the merge request url' do + merge_request = create(:merge_request) + url = Gitlab::UrlBuilder.new(:merge_request).build(merge_request.id) + expect(url).to eq "#{Settings.gitlab['url']}/#{merge_request.project.path_with_namespace}/merge_requests/#{merge_request.iid}" + end + end + + describe 'When asking for a note on commit' do + let(:note) { create(:note_on_commit) } + let(:url) { Gitlab::UrlBuilder.new(:note).build(note.id) } + + it 'returns the note url' do + expect(url).to eq "#{Settings.gitlab['url']}/#{note.project.path_with_namespace}/commit/#{note.commit_id}#note_#{note.id}" + end + end + + describe 'When asking for a note on commit diff' do + let(:note) { create(:note_on_commit_diff) } + let(:url) { Gitlab::UrlBuilder.new(:note).build(note.id) } + + it 'returns the note url' do + expect(url).to eq "#{Settings.gitlab['url']}/#{note.project.path_with_namespace}/commit/#{note.commit_id}#note_#{note.id}" + end + end + + describe 'When asking for a note on issue' do + let(:issue) { create(:issue) } + let(:note) { create(:note_on_issue, noteable_id: issue.id) } + let(:url) { Gitlab::UrlBuilder.new(:note).build(note.id) } + + it 'returns the note url' do + expect(url).to eq "#{Settings.gitlab['url']}/#{issue.project.path_with_namespace}/issues/#{issue.iid}#note_#{note.id}" + end + end + + describe 'When asking for a note on merge request' do + let(:merge_request) { create(:merge_request) } + let(:note) { create(:note_on_merge_request, noteable_id: merge_request.id) } + let(:url) { Gitlab::UrlBuilder.new(:note).build(note.id) } + + it 'returns the note url' do + expect(url).to eq "#{Settings.gitlab['url']}/#{merge_request.project.path_with_namespace}/merge_requests/#{merge_request.iid}#note_#{note.id}" + end + end + + describe 'When asking for a note on merge request diff' do + let(:merge_request) { create(:merge_request) } + let(:note) { create(:note_on_merge_request_diff, noteable_id: merge_request.id) } + let(:url) { Gitlab::UrlBuilder.new(:note).build(note.id) } + + it 'returns the note url' do + expect(url).to eq "#{Settings.gitlab['url']}/#{merge_request.project.path_with_namespace}/merge_requests/#{merge_request.iid}#note_#{note.id}" + end + end + + describe 'When asking for a note on project snippet' do + let(:snippet) { create(:project_snippet) } + let(:note) { create(:note_on_project_snippet, noteable_id: snippet.id) } + let(:url) { Gitlab::UrlBuilder.new(:note).build(note.id) } + + it 'returns the note url' do + expect(url).to eq "#{Settings.gitlab['url']}/#{snippet.project.path_with_namespace}/snippets/#{note.noteable_id}#note_#{note.id}" end end end diff --git a/spec/lib/gitlab/version_info_spec.rb b/spec/lib/gitlab/version_info_spec.rb index 94dccf7a4e..5afeb1c1ec 100644 --- a/spec/lib/gitlab/version_info_spec.rb +++ b/spec/lib/gitlab/version_info_spec.rb @@ -12,58 +12,58 @@ describe 'Gitlab::VersionInfo', no_db: true do end context '>' do - it { @v2_0_0.should > @v1_1_0 } - it { @v1_1_0.should > @v1_0_1 } - it { @v1_0_1.should > @v1_0_0 } - it { @v1_0_0.should > @v0_1_0 } - it { @v0_1_0.should > @v0_0_1 } + it { expect(@v2_0_0).to be > @v1_1_0 } + it { expect(@v1_1_0).to be > @v1_0_1 } + it { expect(@v1_0_1).to be > @v1_0_0 } + it { expect(@v1_0_0).to be > @v0_1_0 } + it { expect(@v0_1_0).to be > @v0_0_1 } end context '>=' do - it { @v2_0_0.should >= Gitlab::VersionInfo.new(2, 0, 0) } - it { @v2_0_0.should >= @v1_1_0 } + it { expect(@v2_0_0).to be >= Gitlab::VersionInfo.new(2, 0, 0) } + it { expect(@v2_0_0).to be >= @v1_1_0 } end context '<' do - it { @v0_0_1.should < @v0_1_0 } - it { @v0_1_0.should < @v1_0_0 } - it { @v1_0_0.should < @v1_0_1 } - it { @v1_0_1.should < @v1_1_0 } - it { @v1_1_0.should < @v2_0_0 } + it { expect(@v0_0_1).to be < @v0_1_0 } + it { expect(@v0_1_0).to be < @v1_0_0 } + it { expect(@v1_0_0).to be < @v1_0_1 } + it { expect(@v1_0_1).to be < @v1_1_0 } + it { expect(@v1_1_0).to be < @v2_0_0 } end context '<=' do - it { @v0_0_1.should <= Gitlab::VersionInfo.new(0, 0, 1) } - it { @v0_0_1.should <= @v0_1_0 } + it { expect(@v0_0_1).to be <= Gitlab::VersionInfo.new(0, 0, 1) } + it { expect(@v0_0_1).to be <= @v0_1_0 } end context '==' do - it { @v0_0_1.should == Gitlab::VersionInfo.new(0, 0, 1) } - it { @v0_1_0.should == Gitlab::VersionInfo.new(0, 1, 0) } - it { @v1_0_0.should == Gitlab::VersionInfo.new(1, 0, 0) } + it { expect(@v0_0_1).to eq(Gitlab::VersionInfo.new(0, 0, 1)) } + it { expect(@v0_1_0).to eq(Gitlab::VersionInfo.new(0, 1, 0)) } + it { expect(@v1_0_0).to eq(Gitlab::VersionInfo.new(1, 0, 0)) } end context '!=' do - it { @v0_0_1.should_not == @v0_1_0 } + it { expect(@v0_0_1).not_to eq(@v0_1_0) } end context 'unknown' do - it { @unknown.should_not be @v0_0_1 } - it { @unknown.should_not be Gitlab::VersionInfo.new } + it { expect(@unknown).not_to be @v0_0_1 } + it { expect(@unknown).not_to be Gitlab::VersionInfo.new } it { expect{@unknown > @v0_0_1}.to raise_error(ArgumentError) } it { expect{@unknown < @v0_0_1}.to raise_error(ArgumentError) } end context 'parse' do - it { Gitlab::VersionInfo.parse("1.0.0").should == @v1_0_0 } - it { Gitlab::VersionInfo.parse("1.0.0.1").should == @v1_0_0 } - it { Gitlab::VersionInfo.parse("git 1.0.0b1").should == @v1_0_0 } - it { Gitlab::VersionInfo.parse("git 1.0b1").should_not be_valid } + it { expect(Gitlab::VersionInfo.parse("1.0.0")).to eq(@v1_0_0) } + it { expect(Gitlab::VersionInfo.parse("1.0.0.1")).to eq(@v1_0_0) } + it { expect(Gitlab::VersionInfo.parse("git 1.0.0b1")).to eq(@v1_0_0) } + it { expect(Gitlab::VersionInfo.parse("git 1.0b1")).not_to be_valid } end context 'to_s' do - it { @v1_0_0.to_s.should == "1.0.0" } - it { @unknown.to_s.should == "Unknown" } + it { expect(@v1_0_0.to_s).to eq("1.0.0") } + it { expect(@unknown.to_s).to eq("Unknown") } end end diff --git a/spec/lib/oauth_spec.rb b/spec/lib/oauth_spec.rb deleted file mode 100644 index 2f15b5e034..0000000000 --- a/spec/lib/oauth_spec.rb +++ /dev/null @@ -1,45 +0,0 @@ -require 'spec_helper' - -describe Gitlab::OAuth::User do - let(:gl_auth) { Gitlab::OAuth::User } - - before do - Gitlab.config.stub(omniauth: {}) - - @info = double( - uid: '12djsak321', - nickname: 'john', - name: 'John', - email: 'john@mail.com' - ) - end - - describe :create do - it "should create user from LDAP" do - @auth = double(info: @info, provider: 'ldap') - user = gl_auth.create(@auth) - - user.should be_valid - user.extern_uid.should == @info.uid - user.provider.should == 'ldap' - end - - it "should create user from Omniauth" do - @auth = double(info: @info, provider: 'twitter') - user = gl_auth.create(@auth) - - user.should be_valid - user.extern_uid.should == @info.uid - user.provider.should == 'twitter' - end - - it "should apply defaults to user" do - @auth = double(info: @info, provider: 'ldap') - user = gl_auth.create(@auth) - - user.should be_valid - user.projects_limit.should == Gitlab.config.gitlab.default_projects_limit - user.can_create_group.should == Gitlab.config.gitlab.default_can_create_group - end - end -end diff --git a/spec/lib/repository_cache_spec.rb b/spec/lib/repository_cache_spec.rb new file mode 100644 index 0000000000..af399f3a73 --- /dev/null +++ b/spec/lib/repository_cache_spec.rb @@ -0,0 +1,34 @@ +require 'rspec' +require_relative '../../lib/repository_cache' + +describe RepositoryCache do + let(:backend) { double('backend').as_null_object } + let(:cache) { RepositoryCache.new('example', backend) } + + describe '#cache_key' do + it 'includes the namespace' do + expect(cache.cache_key(:foo)).to eq 'foo:example' + end + end + + describe '#expire' do + it 'expires the given key from the cache' do + cache.expire(:foo) + expect(backend).to have_received(:delete).with('foo:example') + end + end + + describe '#fetch' do + it 'fetches the given key from the cache' do + cache.fetch(:bar) + expect(backend).to have_received(:fetch).with('bar:example') + end + + it 'accepts a block' do + p = -> {} + + cache.fetch(:baz, &p) + expect(backend).to have_received(:fetch).with('baz:example', &p) + end + end +end diff --git a/spec/lib/votes_spec.rb b/spec/lib/votes_spec.rb index a3c353d5ea..df243a2600 100644 --- a/spec/lib/votes_spec.rb +++ b/spec/lib/votes_spec.rb @@ -5,132 +5,181 @@ describe Issue, 'Votes' do describe "#upvotes" do it "with no notes has a 0/0 score" do - issue.upvotes.should == 0 + expect(issue.upvotes).to eq(0) end it "should recognize non-+1 notes" do add_note "No +1 here" - issue.should have(1).note - issue.notes.first.upvote?.should be_false - issue.upvotes.should == 0 + expect(issue.notes.size).to eq(1) + expect(issue.notes.first.upvote?).to be_falsey + expect(issue.upvotes).to eq(0) end it "should recognize a single +1 note" do add_note "+1 This is awesome" - issue.upvotes.should == 1 + expect(issue.upvotes).to eq(1) end - it "should recognize multiple +1 notes" do - add_note "+1 This is awesome" - add_note "+1 I want this" - issue.upvotes.should == 2 + it 'should recognize multiple +1 notes' do + add_note '+1 This is awesome', create(:user) + add_note '+1 I want this', create(:user) + expect(issue.upvotes).to eq(2) + end + + it 'should not count 2 +1 votes from the same user' do + add_note '+1 This is awesome' + add_note '+1 I want this' + expect(issue.upvotes).to eq(1) end end describe "#downvotes" do it "with no notes has a 0/0 score" do - issue.downvotes.should == 0 + expect(issue.downvotes).to eq(0) end it "should recognize non--1 notes" do add_note "Almost got a -1" - issue.should have(1).note - issue.notes.first.downvote?.should be_false - issue.downvotes.should == 0 + expect(issue.notes.size).to eq(1) + expect(issue.notes.first.downvote?).to be_falsey + expect(issue.downvotes).to eq(0) end it "should recognize a single -1 note" do add_note "-1 This is bad" - issue.downvotes.should == 1 + expect(issue.downvotes).to eq(1) end it "should recognize multiple -1 notes" do - add_note "-1 This is bad" - add_note "-1 Away with this" - issue.downvotes.should == 2 + add_note('-1 This is bad', create(:user)) + add_note('-1 Away with this', create(:user)) + expect(issue.downvotes).to eq(2) end end describe "#votes_count" do it "with no notes has a 0/0 score" do - issue.votes_count.should == 0 + expect(issue.votes_count).to eq(0) end it "should recognize non notes" do add_note "No +1 here" - issue.should have(1).note - issue.votes_count.should == 0 + expect(issue.notes.size).to eq(1) + expect(issue.votes_count).to eq(0) end it "should recognize a single +1 note" do add_note "+1 This is awesome" - issue.votes_count.should == 1 + expect(issue.votes_count).to eq(1) end it "should recognize a single -1 note" do add_note "-1 This is bad" - issue.votes_count.should == 1 + expect(issue.votes_count).to eq(1) end it "should recognize multiple notes" do - add_note "+1 This is awesome" - add_note "-1 This is bad" - add_note "+1 I want this" - issue.votes_count.should == 3 + add_note('+1 This is awesome', create(:user)) + add_note('-1 This is bad', create(:user)) + add_note('+1 I want this', create(:user)) + expect(issue.votes_count).to eq(3) + end + + it 'should not count 2 -1 votes from the same user' do + add_note '-1 This is suspicious' + add_note '-1 This is bad' + expect(issue.votes_count).to eq(1) end end describe "#upvotes_in_percent" do it "with no notes has a 0% score" do - issue.upvotes_in_percent.should == 0 + expect(issue.upvotes_in_percent).to eq(0) end it "should count a single 1 note as 100%" do add_note "+1 This is awesome" - issue.upvotes_in_percent.should == 100 + expect(issue.upvotes_in_percent).to eq(100) end - it "should count multiple +1 notes as 100%" do - add_note "+1 This is awesome" - add_note "+1 I want this" - issue.upvotes_in_percent.should == 100 + it 'should count multiple +1 notes as 100%' do + add_note('+1 This is awesome', create(:user)) + add_note('+1 I want this', create(:user)) + expect(issue.upvotes_in_percent).to eq(100) end - it "should count fractions for multiple +1 and -1 notes correctly" do - add_note "+1 This is awesome" - add_note "+1 I want this" - add_note "-1 This is bad" - add_note "+1 me too" - issue.upvotes_in_percent.should == 75 + it 'should count fractions for multiple +1 and -1 notes correctly' do + add_note('+1 This is awesome', create(:user)) + add_note('+1 I want this', create(:user)) + add_note('-1 This is bad', create(:user)) + add_note('+1 me too', create(:user)) + expect(issue.upvotes_in_percent).to eq(75) end end describe "#downvotes_in_percent" do it "with no notes has a 0% score" do - issue.downvotes_in_percent.should == 0 + expect(issue.downvotes_in_percent).to eq(0) end it "should count a single -1 note as 100%" do add_note "-1 This is bad" - issue.downvotes_in_percent.should == 100 + expect(issue.downvotes_in_percent).to eq(100) end - it "should count multiple -1 notes as 100%" do - add_note "-1 This is bad" - add_note "-1 Away with this" - issue.downvotes_in_percent.should == 100 + it 'should count multiple -1 notes as 100%' do + add_note('-1 This is bad', create(:user)) + add_note('-1 Away with this', create(:user)) + expect(issue.downvotes_in_percent).to eq(100) end - it "should count fractions for multiple +1 and -1 notes correctly" do - add_note "+1 This is awesome" - add_note "+1 I want this" - add_note "-1 This is bad" - add_note "+1 me too" - issue.downvotes_in_percent.should == 25 + it 'should count fractions for multiple +1 and -1 notes correctly' do + add_note('+1 This is awesome', create(:user)) + add_note('+1 I want this', create(:user)) + add_note('-1 This is bad', create(:user)) + add_note('+1 me too', create(:user)) + expect(issue.downvotes_in_percent).to eq(25) end end - def add_note(text) - issue.notes << create(:note, note: text, project: issue.project) + describe '#filter_superceded_votes' do + + it 'should count a users vote only once amongst multiple votes' do + add_note('-1 This needs work before I will accept it') + add_note('+1 I want this', create(:user)) + add_note('+1 This is is awesome', create(:user)) + add_note('+1 this looks good now') + add_note('+1 This is awesome', create(:user)) + add_note('+1 me too', create(:user)) + expect(issue.downvotes).to eq(0) + expect(issue.upvotes).to eq(5) + end + + it 'should count each users vote only once' do + add_note '-1 This needs work before it will be accepted' + add_note '+1 I like this' + add_note '+1 I still like this' + add_note '+1 I really like this' + add_note '+1 Give me this now!!!!' + expect(issue.downvotes).to eq(0) + expect(issue.upvotes).to eq(1) + end + + it 'should count a users vote only once without caring about comments' do + add_note '-1 This needs work before it will be accepted' + add_note 'Comment 1' + add_note 'Another comment' + add_note '+1 vote' + add_note 'final comment' + expect(issue.downvotes).to eq(0) + expect(issue.upvotes).to eq(1) + end + + end + + def add_note(text, author = issue.author) + created_at = Time.now - 1.hour + Note.count.seconds + issue.notes << create(:note, note: text, project: issue.project, + author_id: author.id, created_at: created_at) end end diff --git a/spec/mailers/notify_spec.rb b/spec/mailers/notify_spec.rb index 702431e071..b297fbd511 100644 --- a/spec/mailers/notify_spec.rb +++ b/spec/mailers/notify_spec.rb @@ -5,38 +5,54 @@ describe Notify do include EmailSpec::Matchers include RepoHelpers + let(:gitlab_sender_display_name) { Gitlab.config.gitlab.email_display_name } let(:gitlab_sender) { Gitlab.config.gitlab.email_from } + let(:gitlab_sender_reply_to) { Gitlab.config.gitlab.email_reply_to } let(:recipient) { create(:user, email: 'recipient@example.com') } let(:project) { create(:project) } + around(:each) { ActionMailer::Base.deliveries.clear } + + before(:each) do + email = recipient.emails.create(email: "notifications@example.com") + recipient.update_attribute(:notification_email, email.email) + end + shared_examples 'a multiple recipients email' do it 'is sent to the given recipient' do - should deliver_to recipient.email + is_expected.to deliver_to recipient.notification_email end end shared_examples 'an email sent from GitLab' do it 'is sent from GitLab' do sender = subject.header[:from].addrs[0] - sender.display_name.should eq('GitLab') - sender.address.should eq(gitlab_sender) + expect(sender.display_name).to eq(gitlab_sender_display_name) + expect(sender.address).to eq(gitlab_sender) + end + + it 'has a Reply-To address' do + reply_to = subject.header[:reply_to].addresses + expect(reply_to).to eq([gitlab_sender_reply_to]) end end shared_examples 'an email starting a new thread' do |message_id_prefix| it 'has a discussion identifier' do - should have_header 'Message-ID', /<#{message_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/ + is_expected.to have_header 'Message-ID', /<#{message_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/ + is_expected.to have_header 'X-GitLab-Project', /#{project.name}/ end end shared_examples 'an answer to an existing thread' do |thread_id_prefix| it 'has a subject that begins with Re: ' do - should have_subject /^Re: / + is_expected.to have_subject /^Re: / end it 'has headers that reference an existing thread' do - should have_header 'References', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/ - should have_header 'In-Reply-To', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/ + is_expected.to have_header 'References', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/ + is_expected.to have_header 'In-Reply-To', /<#{thread_id_prefix}(.*)@#{Gitlab.config.gitlab.host}>/ + is_expected.to have_header 'X-GitLab-Project', /#{project.name}/ end end @@ -44,32 +60,37 @@ describe Notify do let(:example_site_path) { root_path } let(:new_user) { create(:user, email: 'newguy@example.com', created_by_id: 1) } - subject { Notify.new_user_email(new_user.id, new_user.password, 'kETLwRaayvigPq_x3SNM') } + token = 'kETLwRaayvigPq_x3SNM' + + subject { Notify.new_user_email(new_user.id, token) } it_behaves_like 'an email sent from GitLab' it 'is sent to the new user' do - should deliver_to new_user.email + is_expected.to deliver_to new_user.email end it 'has the correct subject' do - should have_subject /^Account was created for you$/i + is_expected.to have_subject /^Account was created for you$/i end it 'contains the new user\'s login name' do - should have_body_text /#{new_user.email}/ + is_expected.to have_body_text /#{new_user.email}/ end it 'contains the password text' do - should have_body_text /Click here to set your password/ + is_expected.to have_body_text /Click here to set your password/ end it 'includes a link for user to set password' do - should have_body_text 'http://localhost/users/password/edit?reset_password_token=kETLwRaayvigPq_x3SNM' + params = "reset_password_token=#{token}" + is_expected.to have_body_text( + %r{http://localhost(:\d+)?/users/password/edit\?#{params}} + ) end it 'includes a link to the site' do - should have_body_text /#{example_site_path}/ + is_expected.to have_body_text /#{example_site_path}/ end end @@ -78,28 +99,28 @@ describe Notify do let(:example_site_path) { root_path } let(:new_user) { create(:user, email: 'newguy@example.com', password: "securePassword") } - subject { Notify.new_user_email(new_user.id, new_user.password) } + subject { Notify.new_user_email(new_user.id) } it_behaves_like 'an email sent from GitLab' it 'is sent to the new user' do - should deliver_to new_user.email + is_expected.to deliver_to new_user.email end it 'has the correct subject' do - should have_subject /^Account was created for you$/i + is_expected.to have_subject /^Account was created for you$/i end it 'contains the new user\'s login name' do - should have_body_text /#{new_user.email}/ + is_expected.to have_body_text /#{new_user.email}/ end it 'should not contain the new user\'s password' do - should_not have_body_text /password/ + is_expected.not_to have_body_text /password/ end it 'includes a link to the site' do - should have_body_text /#{example_site_path}/ + is_expected.to have_body_text /#{example_site_path}/ end end @@ -111,19 +132,19 @@ describe Notify do it_behaves_like 'an email sent from GitLab' it 'is sent to the new user' do - should deliver_to key.user.email + is_expected.to deliver_to key.user.email end it 'has the correct subject' do - should have_subject /^SSH key was added to your account$/i + is_expected.to have_subject /^SSH key was added to your account$/i end it 'contains the new ssh key title' do - should have_body_text /#{key.title}/ + is_expected.to have_body_text /#{key.title}/ end it 'includes a link to ssh keys page' do - should have_body_text /#{profile_keys_path}/ + is_expected.to have_body_text /#{profile_keys_path}/ end end @@ -133,19 +154,19 @@ describe Notify do subject { Notify.new_email_email(email.id) } it 'is sent to the new user' do - should deliver_to email.user.email + is_expected.to deliver_to email.user.email end it 'has the correct subject' do - should have_subject /^Email was added to your account$/i + is_expected.to have_subject /^Email was added to your account$/i end it 'contains the new email address' do - should have_body_text /#{email.email}/ + is_expected.to have_body_text /#{email.email}/ end it 'includes a link to emails page' do - should have_body_text /#{profile_emails_path}/ + is_expected.to have_body_text /#{profile_emails_path}/ end end @@ -158,12 +179,12 @@ describe Notify do shared_examples 'an assignee email' do it 'is sent as the author' do sender = subject.header[:from].addrs[0] - sender.display_name.should eq(current_user.name) - sender.address.should eq(gitlab_sender) + expect(sender.display_name).to eq(current_user.name) + expect(sender.address).to eq(gitlab_sender) end it 'is sent to the assignee' do - should deliver_to assignee.email + is_expected.to deliver_to assignee.email end end @@ -178,11 +199,11 @@ describe Notify do it_behaves_like 'an email starting a new thread', 'issue' it 'has the correct subject' do - should have_subject /#{project.name} \| #{issue.title} \(##{issue.iid}\)/ + is_expected.to have_subject /#{project.name} \| #{issue.title} \(##{issue.iid}\)/ end it 'contains a link to the new issue' do - should have_body_text /#{project_issue_path project, issue}/ + is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/ end end @@ -190,7 +211,7 @@ describe Notify do subject { Notify.new_issue_email(issue_with_description.assignee_id, issue_with_description.id) } it 'contains the description' do - should have_body_text /#{issue_with_description.description}/ + is_expected.to have_body_text /#{issue_with_description.description}/ end end @@ -202,24 +223,24 @@ describe Notify do it 'is sent as the author' do sender = subject.header[:from].addrs[0] - sender.display_name.should eq(current_user.name) - sender.address.should eq(gitlab_sender) + expect(sender.display_name).to eq(current_user.name) + expect(sender.address).to eq(gitlab_sender) end it 'has the correct subject' do - should have_subject /#{issue.title} \(##{issue.iid}\)/ + is_expected.to have_subject /#{issue.title} \(##{issue.iid}\)/ end it 'contains the name of the previous assignee' do - should have_body_text /#{previous_assignee.name}/ + is_expected.to have_body_text /#{previous_assignee.name}/ end it 'contains the name of the new assignee' do - should have_body_text /#{assignee.name}/ + is_expected.to have_body_text /#{assignee.name}/ end it 'contains a link to the issue' do - should have_body_text /#{project_issue_path project, issue}/ + is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/ end end @@ -231,24 +252,24 @@ describe Notify do it 'is sent as the author' do sender = subject.header[:from].addrs[0] - sender.display_name.should eq(current_user.name) - sender.address.should eq(gitlab_sender) + expect(sender.display_name).to eq(current_user.name) + expect(sender.address).to eq(gitlab_sender) end it 'has the correct subject' do - should have_subject /#{issue.title} \(##{issue.iid}\)/i + is_expected.to have_subject /#{issue.title} \(##{issue.iid}\)/i end it 'contains the new status' do - should have_body_text /#{status}/i + is_expected.to have_body_text /#{status}/i end it 'contains the user name' do - should have_body_text /#{current_user.name}/i + is_expected.to have_body_text /#{current_user.name}/i end it 'contains a link to the issue' do - should have_body_text /#{project_issue_path project, issue}/ + is_expected.to have_body_text /#{namespace_project_issue_path project.namespace, project, issue}/ end end @@ -266,23 +287,23 @@ describe Notify do it_behaves_like 'an email starting a new thread', 'merge_request' it 'has the correct subject' do - should have_subject /#{merge_request.title} \(##{merge_request.iid}\)/ + is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/ end it 'contains a link to the new merge request' do - should have_body_text /#{project_merge_request_path(project, merge_request)}/ + is_expected.to have_body_text /#{namespace_project_merge_request_path(project.namespace, project, merge_request)}/ end it 'contains the source branch for the merge request' do - should have_body_text /#{merge_request.source_branch}/ + is_expected.to have_body_text /#{merge_request.source_branch}/ end it 'contains the target branch for the merge request' do - should have_body_text /#{merge_request.target_branch}/ + is_expected.to have_body_text /#{merge_request.target_branch}/ end it 'has the correct message-id set' do - should have_header 'Message-ID', "" + is_expected.to have_header 'Message-ID', "" end end @@ -290,7 +311,7 @@ describe Notify do subject { Notify.new_merge_request_email(merge_request_with_description.assignee_id, merge_request_with_description.id) } it 'contains the description' do - should have_body_text /#{merge_request_with_description.description}/ + is_expected.to have_body_text /#{merge_request_with_description.description}/ end end @@ -302,24 +323,24 @@ describe Notify do it 'is sent as the author' do sender = subject.header[:from].addrs[0] - sender.display_name.should eq(current_user.name) - sender.address.should eq(gitlab_sender) + expect(sender.display_name).to eq(current_user.name) + expect(sender.address).to eq(gitlab_sender) end it 'has the correct subject' do - should have_subject /#{merge_request.title} \(##{merge_request.iid}\)/ + is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/ end it 'contains the name of the previous assignee' do - should have_body_text /#{previous_assignee.name}/ + is_expected.to have_body_text /#{previous_assignee.name}/ end it 'contains the name of the new assignee' do - should have_body_text /#{assignee.name}/ + is_expected.to have_body_text /#{assignee.name}/ end it 'contains a link to the merge request' do - should have_body_text /#{project_merge_request_path project, merge_request}/ + is_expected.to have_body_text /#{namespace_project_merge_request_path project.namespace, project, merge_request}/ end end @@ -331,24 +352,24 @@ describe Notify do it 'is sent as the author' do sender = subject.header[:from].addrs[0] - sender.display_name.should eq(current_user.name) - sender.address.should eq(gitlab_sender) + expect(sender.display_name).to eq(current_user.name) + expect(sender.address).to eq(gitlab_sender) end it 'has the correct subject' do - should have_subject /#{merge_request.title} \(##{merge_request.iid}\)/i + is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/i end it 'contains the new status' do - should have_body_text /#{status}/i + is_expected.to have_body_text /#{status}/i end it 'contains the user name' do - should have_body_text /#{current_user.name}/i + is_expected.to have_body_text /#{current_user.name}/i end it 'contains a link to the merge request' do - should have_body_text /#{project_merge_request_path project, merge_request}/ + is_expected.to have_body_text /#{namespace_project_merge_request_path project.namespace, project, merge_request}/ end end @@ -360,20 +381,20 @@ describe Notify do it 'is sent as the merge author' do sender = subject.header[:from].addrs[0] - sender.display_name.should eq(merge_author.name) - sender.address.should eq(gitlab_sender) + expect(sender.display_name).to eq(merge_author.name) + expect(sender.address).to eq(gitlab_sender) end it 'has the correct subject' do - should have_subject /#{merge_request.title} \(##{merge_request.iid}\)/ + is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/ end it 'contains the new status' do - should have_body_text /merged/i + is_expected.to have_body_text /merged/i end it 'contains a link to the merge request' do - should have_body_text /#{project_merge_request_path project, merge_request}/ + is_expected.to have_body_text /#{namespace_project_merge_request_path project.namespace, project, merge_request}/ end end end @@ -387,36 +408,36 @@ describe Notify do it_behaves_like 'an email sent from GitLab' it 'has the correct subject' do - should have_subject /Project was moved/ + is_expected.to have_subject /Project was moved/ end it 'contains name of project' do - should have_body_text /#{project.name_with_namespace}/ + is_expected.to have_body_text /#{project.name_with_namespace}/ end it 'contains new user role' do - should have_body_text /#{project.ssh_url_to_repo}/ + is_expected.to have_body_text /#{project.ssh_url_to_repo}/ end end describe 'project access changed' do let(:project) { create(:project) } let(:user) { create(:user) } - let(:users_project) { create(:users_project, + let(:project_member) { create(:project_member, project: project, user: user) } - subject { Notify.project_access_granted_email(users_project.id) } + subject { Notify.project_access_granted_email(project_member.id) } it_behaves_like 'an email sent from GitLab' it 'has the correct subject' do - should have_subject /Access to project was granted/ + is_expected.to have_subject /Access to project was granted/ end it 'contains name of project' do - should have_body_text /#{project.name}/ + is_expected.to have_body_text /#{project.name}/ end it 'contains new user role' do - should have_body_text /#{users_project.human_access}/ + is_expected.to have_body_text /#{project_member.human_access}/ end end @@ -425,29 +446,29 @@ describe Notify do let(:note) { create(:note, project: project, author: note_author) } before :each do - Note.stub(:find).with(note.id).and_return(note) + allow(Note).to receive(:find).with(note.id).and_return(note) end shared_examples 'a note email' do it 'is sent as the author' do sender = subject.header[:from].addrs[0] - sender.display_name.should eq(note_author.name) - sender.address.should eq(gitlab_sender) + expect(sender.display_name).to eq(note_author.name) + expect(sender.address).to eq(gitlab_sender) end it 'is sent to the given recipient' do - should deliver_to recipient.email + is_expected.to deliver_to recipient.notification_email end it 'contains the message from the note' do - should have_body_text /#{note.note}/ + is_expected.to have_body_text /#{note.note}/ end end describe 'on a commit' do let(:commit) { project.repository.commit } - before(:each) { note.stub(:noteable).and_return(commit) } + before(:each) { allow(note).to receive(:noteable).and_return(commit) } subject { Notify.note_commit_email(recipient.id, note.id) } @@ -455,18 +476,18 @@ describe Notify do it_behaves_like 'an answer to an existing thread', 'commits' it 'has the correct subject' do - should have_subject /#{commit.title} \(#{commit.short_id}\)/ + is_expected.to have_subject /#{commit.title} \(#{commit.short_id}\)/ end it 'contains a link to the commit' do - should have_body_text commit.short_id + is_expected.to have_body_text commit.short_id end end describe 'on a merge request' do let(:merge_request) { create(:merge_request, source_project: project, target_project: project) } - let(:note_on_merge_request_path) { project_merge_request_path(project, merge_request, anchor: "note_#{note.id}") } - before(:each) { note.stub(:noteable).and_return(merge_request) } + let(:note_on_merge_request_path) { namespace_project_merge_request_path(project.namespace, project, merge_request, anchor: "note_#{note.id}") } + before(:each) { allow(note).to receive(:noteable).and_return(merge_request) } subject { Notify.note_merge_request_email(recipient.id, note.id) } @@ -474,18 +495,18 @@ describe Notify do it_behaves_like 'an answer to an existing thread', 'merge_request' it 'has the correct subject' do - should have_subject /#{merge_request.title} \(##{merge_request.iid}\)/ + is_expected.to have_subject /#{merge_request.title} \(##{merge_request.iid}\)/ end it 'contains a link to the merge request note' do - should have_body_text /#{note_on_merge_request_path}/ + is_expected.to have_body_text /#{note_on_merge_request_path}/ end end describe 'on an issue' do let(:issue) { create(:issue, project: project) } - let(:note_on_issue_path) { project_issue_path(project, issue, anchor: "note_#{note.id}") } - before(:each) { note.stub(:noteable).and_return(issue) } + let(:note_on_issue_path) { namespace_project_issue_path(project.namespace, project, issue, anchor: "note_#{note.id}") } + before(:each) { allow(note).to receive(:noteable).and_return(issue) } subject { Notify.note_issue_email(recipient.id, note.id) } @@ -493,11 +514,11 @@ describe Notify do it_behaves_like 'an answer to an existing thread', 'issue' it 'has the correct subject' do - should have_subject /#{issue.title} \(##{issue.iid}\)/ + is_expected.to have_subject /#{issue.title} \(##{issue.iid}\)/ end it 'contains a link to the issue note' do - should have_body_text /#{note_on_issue_path}/ + is_expected.to have_body_text /#{note_on_issue_path}/ end end end @@ -506,22 +527,22 @@ describe Notify do describe 'group access changed' do let(:group) { create(:group) } let(:user) { create(:user) } - let(:membership) { create(:users_group, group: group, user: user) } + let(:membership) { create(:group_member, group: group, user: user) } subject { Notify.group_access_granted_email(membership.id) } it_behaves_like 'an email sent from GitLab' it 'has the correct subject' do - should have_subject /Access to group was granted/ + is_expected.to have_subject /Access to group was granted/ end it 'contains name of project' do - should have_body_text /#{group.name}/ + is_expected.to have_body_text /#{group.name}/ end it 'contains new user role' do - should have_body_text /#{membership.human_access}/ + is_expected.to have_body_text /#{membership.human_access}/ end end @@ -539,15 +560,109 @@ describe Notify do it_behaves_like 'an email sent from GitLab' it 'is sent to the new user' do - should deliver_to 'new-email@mail.com' + is_expected.to deliver_to 'new-email@mail.com' end it 'has the correct subject' do - should have_subject "Confirmation instructions" + is_expected.to have_subject "Confirmation instructions" end it 'includes a link to the site' do - should have_body_text /#{example_site_path}/ + is_expected.to have_body_text /#{example_site_path}/ + end + end + + describe 'email on push for a created branch' do + let(:example_site_path) { root_path } + 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) } + + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(user.name) + 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 + + it 'contains a link to the branch' do + is_expected.to have_body_text /#{tree_path}/ + end + end + + describe 'email on push for a created tag' do + let(:example_site_path) { root_path } + 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) } + + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(user.name) + 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 + + it 'contains a link to the tag' do + is_expected.to have_body_text /#{tree_path}/ + end + end + + describe 'email on push for a deleted branch' 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) } + + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(user.name) + 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 + end + + describe 'email on push for a deleted tag' 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) } + + it 'is sent as the author' do + sender = subject.header[:from].addrs[0] + expect(sender.display_name).to eq(user.name) + 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 end @@ -556,34 +671,102 @@ describe Notify do let(:user) { create(:user) } let(:compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_image_commit.id, sample_commit.id) } let(:commits) { Commit.decorate(compare.commits) } - let(:diff_path) { project_compare_path(project, from: commits.first, to: commits.last) } + let(:diff_path) { namespace_project_compare_path(project.namespace, project, from: Commit.new(compare.base), to: Commit.new(compare.head)) } + let(:send_from_committer_email) { false } - subject { Notify.repository_push_email(project.id, 'devs@company.name', user.id, 'master', compare) } + 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) } it 'is sent as the author' do sender = subject.header[:from].addrs[0] - sender.display_name.should eq(user.name) - sender.address.should eq(gitlab_sender) + expect(sender.display_name).to eq(user.name) + expect(sender.address).to eq(gitlab_sender) end it 'is sent to recipient' do - should deliver_to 'devs@company.name' + is_expected.to deliver_to 'devs@company.name' end it 'has the correct subject' do - should have_subject /#{commits.length} new commits pushed to repository/ + is_expected.to have_subject /\[#{project.path_with_namespace}\]\[master\] #{commits.length} commits:/ end it 'includes commits list' do - should have_body_text /Change some files/ + is_expected.to have_body_text /Change some files/ end it 'includes diffs' do - should have_body_text /def archive_formats_regex/ + is_expected.to have_body_text /def archive_formats_regex/ end it 'contains a link to the diff' do - should have_body_text /#{diff_path}/ + is_expected.to have_body_text /#{diff_path}/ + end + + it 'doesn not contain the misleading footer' do + is_expected.not_to have_body_text /you are a member of/ + end + + context "when set to send from committer email if domain matches" do + + let(:send_from_committer_email) { true } + + before do + allow(Gitlab.config.gitlab).to receive(:host).and_return("gitlab.corp.company.com") + end + + context "when the committer email domain is within the GitLab domain" do + + before do + user.update_attribute(:email, "user@company.com") + user.confirm! + end + + it "is sent from the committer email" do + sender = subject.header[:from].addrs[0] + expect(sender.address).to eq(user.email) + end + + it "is set to reply to the committer email" do + sender = subject.header[:reply_to].addrs[0] + expect(sender.address).to eq(user.email) + end + end + + context "when the committer email domain is not completely within the GitLab domain" do + + before do + user.update_attribute(:email, "user@something.company.com") + user.confirm! + end + + it "is sent from the default email" do + sender = subject.header[:from].addrs[0] + expect(sender.address).to eq(gitlab_sender) + end + + it "is set to reply to the default email" do + sender = subject.header[:reply_to].addrs[0] + expect(sender.address).to eq(gitlab_sender_reply_to) + end + end + + context "when the committer email domain is outside the GitLab domain" do + + before do + user.update_attribute(:email, "user@mpany.com") + user.confirm! + end + + it "is sent from the default email" do + sender = subject.header[:from].addrs[0] + expect(sender.address).to eq(gitlab_sender) + end + + it "is set to reply to the default email" do + sender = subject.header[:reply_to].addrs[0] + expect(sender.address).to eq(gitlab_sender_reply_to) + end + end end end @@ -592,34 +775,34 @@ describe Notify do let(:user) { create(:user) } let(:compare) { Gitlab::Git::Compare.new(project.repository.raw_repository, sample_commit.parent_id, sample_commit.id) } let(:commits) { Commit.decorate(compare.commits) } - let(:diff_path) { project_commit_path(project, commits.first) } + let(:diff_path) { namespace_project_commit_path(project.namespace, project, commits.first) } - subject { Notify.repository_push_email(project.id, 'devs@company.name', user.id, 'master', compare) } + subject { Notify.repository_push_email(project.id, 'devs@company.name', author_id: user.id, ref: 'refs/heads/master', action: :push, compare: compare) } it 'is sent as the author' do sender = subject.header[:from].addrs[0] - sender.display_name.should eq(user.name) - sender.address.should eq(gitlab_sender) + expect(sender.display_name).to eq(user.name) + expect(sender.address).to eq(gitlab_sender) end it 'is sent to recipient' do - should deliver_to 'devs@company.name' + is_expected.to deliver_to 'devs@company.name' end it 'has the correct subject' do - should have_subject /#{commits.first.title}/ + is_expected.to have_subject /#{commits.first.title}/ end it 'includes commits list' do - should have_body_text /Change some files/ + is_expected.to have_body_text /Change some files/ end it 'includes diffs' do - should have_body_text /def archive_formats_regex/ + is_expected.to have_body_text /def archive_formats_regex/ end it 'contains a link to the diff' do - should have_body_text /#{diff_path}/ + is_expected.to have_body_text /#{diff_path}/ end end end diff --git a/spec/models/application_setting_spec.rb b/spec/models/application_setting_spec.rb new file mode 100644 index 0000000000..b4f0b2c201 --- /dev/null +++ b/spec/models/application_setting_spec.rb @@ -0,0 +1,24 @@ +# == Schema Information +# +# Table name: application_settings +# +# id :integer not null, primary key +# default_projects_limit :integer +# default_branch_protection :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) +# twitter_sharing_enabled :boolean default(TRUE) +# restricted_visibility_levels :text +# + +require 'spec_helper' + +describe ApplicationSetting, models: true do + it { expect(ApplicationSetting.create_from_defaults).to be_valid } +end diff --git a/spec/models/assembla_service_spec.rb b/spec/models/assembla_service_spec.rb deleted file mode 100644 index acc08fc4d6..0000000000 --- a/spec/models/assembla_service_spec.rb +++ /dev/null @@ -1,53 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# token :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# project_url :string(255) -# subdomain :string(255) -# room :string(255) -# recipients :text -# api_key :string(255) -# - -require 'spec_helper' - -describe AssemblaService, models: true do - describe "Associations" do - it { should belong_to :project } - it { should have_one :service_hook } - end - - describe "Execute" do - let(:user) { create(:user) } - let(:project) { create(:project) } - - before do - @assembla_service = AssemblaService.new - @assembla_service.stub( - project_id: project.id, - project: project, - service_hook: true, - token: 'verySecret', - subdomain: 'project_name' - ) - @sample_data = GitPushService.new.sample_data(project, user) - @api_url = 'https://atlas.assembla.com/spaces/project_name/github_tool?secret_key=verySecret' - WebMock.stub_request(:post, @api_url) - end - - it "should call Assembla API" do - @assembla_service.execute(@sample_data) - WebMock.should have_requested(:post, @api_url).with( - body: /#{@sample_data[:before]}.*#{@sample_data[:after]}.*#{project.path}/ - ).once - end - end -end diff --git a/spec/models/broadcast_message_spec.rb b/spec/models/broadcast_message_spec.rb index 0f31c407c9..8ab72151a6 100644 --- a/spec/models/broadcast_message_spec.rb +++ b/spec/models/broadcast_message_spec.rb @@ -18,22 +18,22 @@ require 'spec_helper' describe BroadcastMessage do subject { create(:broadcast_message) } - it { should be_valid } + it { is_expected.to be_valid } describe :current do it "should return last message if time match" do broadcast_message = create(:broadcast_message, starts_at: Time.now.yesterday, ends_at: Time.now.tomorrow) - BroadcastMessage.current.should == broadcast_message + expect(BroadcastMessage.current).to eq(broadcast_message) end it "should return nil if time not come" do broadcast_message = create(:broadcast_message, starts_at: Time.now.tomorrow, ends_at: Time.now + 2.days) - BroadcastMessage.current.should be_nil + expect(BroadcastMessage.current).to be_nil end it "should return nil if time has passed" do broadcast_message = create(:broadcast_message, starts_at: Time.now - 2.days, ends_at: Time.now.yesterday) - BroadcastMessage.current.should be_nil + expect(BroadcastMessage.current).to be_nil end end end diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb index 1673184cbe..11cc7762ce 100644 --- a/spec/models/commit_spec.rb +++ b/spec/models/commit_spec.rb @@ -6,22 +6,22 @@ describe Commit do describe '#title' do it "returns no_commit_message when safe_message is blank" do - commit.stub(:safe_message).and_return('') - commit.title.should == "--no commit message" + allow(commit).to receive(:safe_message).and_return('') + expect(commit.title).to eq("--no commit message") end it "truncates a message without a newline at 80 characters" do message = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sodales id felis id blandit. Vivamus egestas lacinia lacus, sed rutrum mauris.' - commit.stub(:safe_message).and_return(message) - commit.title.should == "#{message[0..79]}…" + allow(commit).to receive(:safe_message).and_return(message) + expect(commit.title).to eq("#{message[0..79]}…") end it "truncates a message with a newline before 80 characters at the newline" do message = commit.safe_message.split(" ").first - commit.stub(:safe_message).and_return(message + "\n" + message) - commit.title.should == message + allow(commit).to receive(:safe_message).and_return(message + "\n" + message) + expect(commit.title).to eq(message) end it "does not truncates a message with a newline after 80 but less 100 characters" do @@ -30,40 +30,48 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec sodales id felis Vivamus egestas lacinia lacus, sed rutrum mauris. eos - commit.stub(:safe_message).and_return(message) - commit.title.should == message.split("\n").first + allow(commit).to receive(:safe_message).and_return(message) + expect(commit.title).to eq(message.split("\n").first) end end describe "delegation" do subject { commit } - it { should respond_to(:message) } - it { should respond_to(:authored_date) } - it { should respond_to(:committed_date) } - it { should respond_to(:committer_email) } - it { should respond_to(:author_email) } - it { should respond_to(:parents) } - it { should respond_to(:date) } - it { should respond_to(:diffs) } - it { should respond_to(:tree) } - it { should respond_to(:id) } - it { should respond_to(:to_patch) } + it { is_expected.to respond_to(:message) } + it { is_expected.to respond_to(:authored_date) } + it { is_expected.to respond_to(:committed_date) } + it { is_expected.to respond_to(:committer_email) } + it { is_expected.to respond_to(:author_email) } + it { is_expected.to respond_to(:parents) } + it { is_expected.to respond_to(:date) } + it { is_expected.to respond_to(:diffs) } + it { is_expected.to respond_to(:tree) } + it { is_expected.to respond_to(:id) } + it { is_expected.to respond_to(:to_patch) } end describe '#closes_issues' do let(:issue) { create :issue, project: project } + let(:other_project) { create :project, :public } + let(:other_issue) { create :issue, project: other_project } it 'detects issues that this commit is marked as closing' do - commit.stub(issue_closing_regex: /^([Cc]loses|[Ff]ixes) #\d+/, safe_message: "Fixes ##{issue.iid}") - commit.closes_issues(project).should == [issue] + commit.stub(safe_message: "Fixes ##{issue.iid}") + expect(commit.closes_issues(project)).to eq([issue]) + end + + it 'does not detect issues from other projects' do + ext_ref = "#{other_project.path_with_namespace}##{other_issue.iid}" + commit.stub(safe_message: "Fixes #{ext_ref}") + expect(commit.closes_issues(project)).to be_empty end end it_behaves_like 'a mentionable' do let(:subject) { commit } let(:mauthor) { create :user, email: commit.author_email } - let(:backref_text) { "commit #{subject.sha[0..5]}" } + let(:backref_text) { "commit #{subject.id}" } let(:set_mentionable_text) { ->(txt){ subject.stub(safe_message: txt) } } # Include the subject in the repository stub. diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb index 9cbc899067..557c71b4d2 100644 --- a/spec/models/concerns/issuable_spec.rb +++ b/spec/models/concerns/issuable_spec.rb @@ -4,63 +4,63 @@ describe Issue, "Issuable" do let(:issue) { create(:issue) } describe "Associations" do - it { should belong_to(:project) } - it { should belong_to(:author) } - it { should belong_to(:assignee) } - it { should have_many(:notes).dependent(:destroy) } + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:author) } + it { is_expected.to belong_to(:assignee) } + it { is_expected.to have_many(:notes).dependent(:destroy) } end describe "Validation" do before { subject.stub(set_iid: false) } - it { should validate_presence_of(:project) } - it { should validate_presence_of(:iid) } - it { should validate_presence_of(:author) } - it { should validate_presence_of(:title) } - it { should ensure_length_of(:title).is_at_least(0).is_at_most(255) } + it { is_expected.to validate_presence_of(:project) } + it { is_expected.to validate_presence_of(:iid) } + it { is_expected.to validate_presence_of(:author) } + it { is_expected.to validate_presence_of(:title) } + it { is_expected.to ensure_length_of(:title).is_at_least(0).is_at_most(255) } end describe "Scope" do - it { described_class.should respond_to(:opened) } - it { described_class.should respond_to(:closed) } - it { described_class.should respond_to(:assigned) } + it { expect(described_class).to respond_to(:opened) } + it { expect(described_class).to respond_to(:closed) } + it { expect(described_class).to respond_to(:assigned) } end describe ".search" do let!(:searchable_issue) { create(:issue, title: "Searchable issue") } it "matches by title" do - described_class.search('able').should == [searchable_issue] + expect(described_class.search('able')).to eq([searchable_issue]) end end describe "#today?" do it "returns true when created today" do # Avoid timezone differences and just return exactly what we want - Date.stub(:today).and_return(issue.created_at.to_date) - issue.today?.should be_true + allow(Date).to receive(:today).and_return(issue.created_at.to_date) + expect(issue.today?).to be_truthy end it "returns false when not created today" do - Date.stub(:today).and_return(Date.yesterday) - issue.today?.should be_false + allow(Date).to receive(:today).and_return(Date.yesterday) + expect(issue.today?).to be_falsey end end describe "#new?" do it "returns true when created today and record hasn't been updated" do - issue.stub(:today?).and_return(true) - issue.new?.should be_true + allow(issue).to receive(:today?).and_return(true) + expect(issue.new?).to be_truthy end it "returns false when not created today" do - issue.stub(:today?).and_return(false) - issue.new?.should be_false + allow(issue).to receive(:today?).and_return(false) + expect(issue.new?).to be_falsey end it "returns false when record has been updated" do - issue.stub(:today?).and_return(true) + allow(issue).to receive(:today?).and_return(true) issue.touch - issue.new?.should be_false + expect(issue.new?).to be_falsey end end end diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb new file mode 100644 index 0000000000..eadb941a3f --- /dev/null +++ b/spec/models/concerns/mentionable_spec.rb @@ -0,0 +1,14 @@ +require 'spec_helper' + +describe Issue, "Mentionable" do + describe :mentioned_users do + let!(:user) { create(:user, username: 'stranger') } + let!(:user2) { create(:user, username: 'john') } + let!(:issue) { create(:issue, description: '@stranger mentioned') } + + subject { issue.mentioned_users } + + it { is_expected.to include(user) } + it { is_expected.not_to include(user2) } + end +end diff --git a/spec/models/deploy_key_spec.rb b/spec/models/deploy_key_spec.rb index adbbbac875..b32be8d7a7 100644 --- a/spec/models/deploy_key_spec.rb +++ b/spec/models/deploy_key_spec.rb @@ -19,7 +19,7 @@ describe DeployKey do let(:deploy_key) { create(:deploy_key, projects: [project]) } describe "Associations" do - it { should have_many(:deploy_keys_projects) } - it { should have_many(:projects) } + it { is_expected.to have_many(:deploy_keys_projects) } + it { is_expected.to have_many(:projects) } end end diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb index 3e0e25ee39..7032b77714 100644 --- a/spec/models/deploy_keys_project_spec.rb +++ b/spec/models/deploy_keys_project_spec.rb @@ -13,12 +13,60 @@ require 'spec_helper' describe DeployKeysProject do describe "Associations" do - it { should belong_to(:deploy_key) } - it { should belong_to(:project) } + it { is_expected.to belong_to(:deploy_key) } + it { is_expected.to belong_to(:project) } end describe "Validation" do - it { should validate_presence_of(:project_id) } - it { should validate_presence_of(:deploy_key_id) } + it { is_expected.to validate_presence_of(:project_id) } + it { is_expected.to validate_presence_of(:deploy_key_id) } + end + + describe "Destroying" do + let(:project) { create(:project) } + subject { create(:deploy_keys_project, project: project) } + let(:deploy_key) { subject.deploy_key } + + context "when the deploy key is only used by this project" do + context "when the deploy key is public" do + before do + deploy_key.update_attribute(:public, true) + end + + it "doesn't destroy the deploy key" do + subject.destroy + + expect { + deploy_key.reload + }.not_to raise_error(ActiveRecord::RecordNotFound) + end + end + + context "when the deploy key is private" do + it "destroys the deploy key" do + subject.destroy + + expect { + deploy_key.reload + }.to raise_error(ActiveRecord::RecordNotFound) + end + end + end + + context "when the deploy key is used by more than one project" do + let!(:other_project) { create(:project) } + + before do + other_project.deploy_keys << deploy_key + end + + it "doesn't destroy the deploy key" do + subject.destroy + + expect { + deploy_key.reload + }.not_to raise_error(ActiveRecord::RecordNotFound) + end + end end end diff --git a/spec/models/event_spec.rb b/spec/models/event_spec.rb index 1fdd959da9..0f32f162a1 100644 --- a/spec/models/event_spec.rb +++ b/spec/models/event_spec.rb @@ -18,16 +18,16 @@ require 'spec_helper' describe Event do describe "Associations" do - it { should belong_to(:project) } - it { should belong_to(:target) } + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:target) } end describe "Respond to" do - it { should respond_to(:author_name) } - it { should respond_to(:author_email) } - it { should respond_to(:issue_title) } - it { should respond_to(:merge_request_title) } - it { should respond_to(:commits) } + it { is_expected.to respond_to(:author_name) } + it { is_expected.to respond_to(:author_email) } + it { is_expected.to respond_to(:issue_title) } + it { is_expected.to respond_to(:merge_request_title) } + it { is_expected.to respond_to(:commits) } end describe "Push event" do @@ -36,7 +36,7 @@ describe Event do @user = project.owner data = { - before: "0000000000000000000000000000000000000000", + before: Gitlab::Git::BLANK_SHA, after: "0220c11b9a3e6c69dc8fd35321254ca9a7b98f7e", ref: "refs/heads/master", user_id: @user.id, @@ -58,11 +58,10 @@ describe Event do ) end - it { @event.push?.should be_true } - it { @event.proper?.should be_true } - it { @event.new_branch?.should be_true } - it { @event.tag?.should be_false } - it { @event.branch_name.should == "master" } - it { @event.author.should == @user } + it { expect(@event.push?).to be_truthy } + it { expect(@event.proper?).to be_truthy } + it { expect(@event.tag?).to be_falsey } + it { expect(@event.branch_name).to eq("master") } + it { expect(@event.author).to eq(@user) } end end diff --git a/spec/models/external_wiki_service_spec.rb b/spec/models/external_wiki_service_spec.rb new file mode 100644 index 0000000000..78ef687d29 --- /dev/null +++ b/spec/models/external_wiki_service_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +describe ExternalWikiService do + include ExternalWikiHelper + describe "Associations" do + it { should belong_to :project } + it { should have_one :service_hook } + end + + describe "Validations" do + context "active" do + before do + subject.active = true + end + + it { should validate_presence_of :external_wiki_url } + end + end + + describe 'External wiki' do + let(:project) { create(:project) } + + context 'when it is active' do + before do + properties = { 'external_wiki_url' => 'https://gitlab.com' } + @service = project.create_external_wiki_service(active: true, properties: properties) + end + + after do + @service.destroy! + end + + it 'should replace the wiki url' do + wiki_path = get_project_wiki_path(project) + wiki_path.should match('https://gitlab.com') + end + end + end +end diff --git a/spec/models/flowdock_service_spec.rb b/spec/models/flowdock_service_spec.rb deleted file mode 100644 index 25ad133e12..0000000000 --- a/spec/models/flowdock_service_spec.rb +++ /dev/null @@ -1,52 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# token :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# project_url :string(255) -# subdomain :string(255) -# room :string(255) -# recipients :text -# api_key :string(255) -# - -require 'spec_helper' - -describe FlowdockService do - describe "Associations" do - it { should belong_to :project } - it { should have_one :service_hook } - end - - describe "Execute" do - let(:user) { create(:user) } - let(:project) { create(:project) } - - before do - @flowdock_service = FlowdockService.new - @flowdock_service.stub( - project_id: project.id, - project: project, - service_hook: true, - token: 'verySecret' - ) - @sample_data = GitPushService.new.sample_data(project, user) - @api_url = 'https://api.flowdock.com/v1/git/verySecret' - WebMock.stub_request(:post, @api_url) - end - - it "should call FlowDock API" do - @flowdock_service.execute(@sample_data) - WebMock.should have_requested(:post, @api_url).with( - body: /#{@sample_data[:before]}.*#{@sample_data[:after]}.*#{project.path}/ - ).once - end - end -end diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb index 1845c6103f..7d0ad44a92 100644 --- a/spec/models/forked_project_link_spec.rb +++ b/spec/models/forked_project_link_spec.rb @@ -21,11 +21,11 @@ describe ForkedProjectLink, "add link on fork" do end it "project_to should know it is forked" do - @project_to.forked?.should be_true + expect(@project_to.forked?).to be_truthy end it "project should know who it is forked from" do - @project_to.forked_from_project.should == project_from + expect(@project_to.forked_from_project).to eq(project_from) end end @@ -43,15 +43,15 @@ describe :forked_from_project do it "project_to should know it is forked" do - project_to.forked?.should be_true + expect(project_to.forked?).to be_truthy end it "project_from should not be forked" do - project_from.forked?.should be_false + expect(project_from.forked?).to be_falsey end it "project_to.destroy should destroy fork_link" do - forked_project_link.should_receive(:destroy) + expect(forked_project_link).to receive(:destroy) project_to.destroy end diff --git a/spec/models/gemnasium_service_spec.rb b/spec/models/gemnasium_service_spec.rb deleted file mode 100644 index efdf0dc891..0000000000 --- a/spec/models/gemnasium_service_spec.rb +++ /dev/null @@ -1,48 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# token :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# project_url :string(255) -# subdomain :string(255) -# room :string(255) -# recipients :text -# api_key :string(255) -# - -require 'spec_helper' - -describe GemnasiumService do - describe "Associations" do - it { should belong_to :project } - it { should have_one :service_hook } - end - - describe "Execute" do - let(:user) { create(:user) } - let(:project) { create(:project) } - - before do - @gemnasium_service = GemnasiumService.new - @gemnasium_service.stub( - project_id: project.id, - project: project, - service_hook: true, - token: 'verySecret', - api_key: 'GemnasiumUserApiKey' - ) - @sample_data = GitPushService.new.sample_data(project, user) - end - it "should call Gemnasium service" do - Gemnasium::GitlabService.should_receive(:execute).with(an_instance_of(Hash)).once - @gemnasium_service.execute(@sample_data) - end - end -end diff --git a/spec/models/gitlab_ci_service_spec.rb b/spec/models/gitlab_ci_service_spec.rb deleted file mode 100644 index 439a30869b..0000000000 --- a/spec/models/gitlab_ci_service_spec.rb +++ /dev/null @@ -1,49 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# token :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# project_url :string(255) -# subdomain :string(255) -# room :string(255) -# recipients :text -# api_key :string(255) -# - -require 'spec_helper' - -describe GitlabCiService do - describe "Associations" do - it { should belong_to :project } - it { should have_one :service_hook } - end - - describe "Mass assignment" do - end - - describe 'commits methods' do - before do - @service = GitlabCiService.new - @service.stub( - service_hook: true, - project_url: 'http://ci.gitlab.org/projects/2', - token: 'verySecret' - ) - end - - describe :commit_status_path do - it { @service.commit_status_path("2ab7834c").should == "http://ci.gitlab.org/projects/2/builds/2ab7834c/status.json?token=verySecret"} - end - - describe :build_page do - it { @service.build_page("2ab7834c").should == "http://ci.gitlab.org/projects/2/builds/2ab7834c"} - end - end -end diff --git a/spec/models/group_spec.rb b/spec/models/group_spec.rb index 8259ed88d8..9428224a64 100644 --- a/spec/models/group_spec.rb +++ b/spec/models/group_spec.rb @@ -19,55 +19,55 @@ describe Group do let!(:group) { create(:group) } describe "Associations" do - it { should have_many :projects } - it { should have_many :users_groups } + it { is_expected.to have_many :projects } + it { is_expected.to have_many :group_members } end - it { should validate_presence_of :name } - it { should validate_uniqueness_of(:name) } - it { should validate_presence_of :path } - it { should validate_uniqueness_of(:path) } - it { should_not validate_presence_of :owner } + it { is_expected.to validate_presence_of :name } + it { is_expected.to validate_uniqueness_of(:name) } + it { is_expected.to validate_presence_of :path } + it { is_expected.to validate_uniqueness_of(:path) } + it { is_expected.not_to validate_presence_of :owner } describe :users do - it { group.users.should == group.owners } + it { expect(group.users).to eq(group.owners) } end describe :human_name do - it { group.human_name.should == group.name } + it { expect(group.human_name).to eq(group.name) } end describe :add_users do let(:user) { create(:user) } - before { group.add_user(user, UsersGroup::MASTER) } + before { group.add_user(user, GroupMember::MASTER) } - it { group.users_groups.masters.map(&:user).should include(user) } + it { expect(group.group_members.masters.map(&:user)).to include(user) } end describe :add_users do let(:user) { create(:user) } - before { group.add_users([user.id], UsersGroup::GUEST) } + before { group.add_users([user.id], GroupMember::GUEST) } it "should update the group permission" do - group.users_groups.guests.map(&:user).should include(user) - group.add_users([user.id], UsersGroup::DEVELOPER) - group.users_groups.developers.map(&:user).should include(user) - group.users_groups.guests.map(&:user).should_not include(user) + expect(group.group_members.guests.map(&:user)).to include(user) + group.add_users([user.id], GroupMember::DEVELOPER) + expect(group.group_members.developers.map(&:user)).to include(user) + expect(group.group_members.guests.map(&:user)).not_to include(user) end end describe :avatar_type do let(:user) { create(:user) } - before { group.add_user(user, UsersGroup::MASTER) } + before { group.add_user(user, GroupMember::MASTER) } it "should be true if avatar is image" do group.update_attribute(:avatar, 'uploads/avatar.png') - group.avatar_type.should be_true + expect(group.avatar_type).to be_truthy end it "should be false if avatar is html page" do group.update_attribute(:avatar, 'uploads/avatar.html') - group.avatar_type.should == ["only images allowed"] + expect(group.avatar_type).to eq(["only images allowed"]) end end end diff --git a/spec/models/project_hook_spec.rb b/spec/models/hooks/project_hook_spec.rb similarity index 100% rename from spec/models/project_hook_spec.rb rename to spec/models/hooks/project_hook_spec.rb diff --git a/spec/models/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb similarity index 94% rename from spec/models/service_hook_spec.rb rename to spec/models/hooks/service_hook_spec.rb index 6ec82438df..96bf74d45d 100644 --- a/spec/models/service_hook_spec.rb +++ b/spec/models/hooks/service_hook_spec.rb @@ -19,6 +19,6 @@ require "spec_helper" describe ServiceHook do describe "Associations" do - it { should belong_to :service } + it { is_expected.to belong_to :service } end end diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb new file mode 100644 index 0000000000..810b311a40 --- /dev/null +++ b/spec/models/hooks/system_hook_spec.rb @@ -0,0 +1,100 @@ +# == Schema Information +# +# Table name: web_hooks +# +# id :integer not null, primary key +# url :string(255) +# project_id :integer +# created_at :datetime +# updated_at :datetime +# type :string(255) 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) +# + +require "spec_helper" + +describe SystemHook do + describe "execute" do + before(:each) do + @system_hook = create(:system_hook) + WebMock.stub_request(:post, @system_hook.url) + end + + it "project_create hook" do + Projects::CreateService.new(create(:user), name: 'empty').execute + expect(WebMock).to have_requested(:post, @system_hook.url).with(body: /project_create/).once + end + + it "project_destroy hook" do + user = create(:user) + project = create(:empty_project, namespace: user.namespace) + Projects::DestroyService.new(project, user, {}).execute + expect(WebMock).to have_requested(:post, @system_hook.url).with(body: /project_destroy/).once + end + + it "user_create hook" do + create(:user) + expect(WebMock).to have_requested(:post, @system_hook.url).with(body: /user_create/).once + end + + it "user_destroy hook" do + user = create(:user) + user.destroy + expect(WebMock).to have_requested(:post, @system_hook.url).with(body: /user_destroy/).once + end + + it "project_create hook" do + user = create(:user) + project = create(:project) + project.team << [user, :master] + expect(WebMock).to have_requested(:post, @system_hook.url).with(body: /user_add_to_team/).once + end + + it "project_destroy hook" do + user = create(:user) + project = create(:project) + project.team << [user, :master] + project.project_members.destroy_all + expect(WebMock).to have_requested(:post, @system_hook.url).with(body: /user_remove_from_team/).once + end + + it 'group create hook' do + create(:group) + expect(WebMock).to have_requested(:post, @system_hook.url).with( + body: /group_create/ + ).once + end + + it 'group destroy hook' do + group = create(:group) + group.destroy + expect(WebMock).to have_requested(:post, @system_hook.url).with( + body: /group_destroy/ + ).once + end + + it 'group member create hook' do + group = create(:group) + user = create(:user) + group.add_user(user, Gitlab::Access::MASTER) + expect(WebMock).to have_requested(:post, @system_hook.url).with( + body: /user_add_to_group/ + ).once + end + + it 'group member destroy hook' do + group = create(:group) + user = create(:user) + group.add_user(user, Gitlab::Access::MASTER) + group.group_members.destroy_all + expect(WebMock).to have_requested(:post, @system_hook.url).with( + body: /user_remove_from_group/ + ).once + end + + end +end diff --git a/spec/models/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb similarity index 59% rename from spec/models/web_hook_spec.rb rename to spec/models/hooks/web_hook_spec.rb index e9c04ee89c..67ec9193ad 100644 --- a/spec/models/web_hook_spec.rb +++ b/spec/models/hooks/web_hook_spec.rb @@ -19,25 +19,25 @@ require 'spec_helper' describe ProjectHook do describe "Associations" do - it { should belong_to :project } + it { is_expected.to belong_to :project } end describe "Mass assignment" do end describe "Validations" do - it { should validate_presence_of(:url) } + it { is_expected.to validate_presence_of(:url) } context "url format" do - it { should allow_value("http://example.com").for(:url) } - it { should allow_value("https://excample.com").for(:url) } - it { should allow_value("http://test.com/api").for(:url) } - it { should allow_value("http://test.com/api?key=abc").for(:url) } - it { should allow_value("http://test.com/api?key=abc&type=def").for(:url) } + it { is_expected.to allow_value("http://example.com").for(:url) } + it { is_expected.to allow_value("https://excample.com").for(:url) } + it { is_expected.to allow_value("http://test.com/api").for(:url) } + it { is_expected.to allow_value("http://test.com/api?key=abc").for(:url) } + it { is_expected.to allow_value("http://test.com/api?key=abc&type=def").for(:url) } - it { should_not allow_value("example.com").for(:url) } - it { should_not allow_value("ftp://example.com").for(:url) } - it { should_not allow_value("herp-and-derp").for(:url) } + it { is_expected.not_to allow_value("example.com").for(:url) } + it { is_expected.not_to allow_value("ftp://example.com").for(:url) } + it { is_expected.not_to allow_value("herp-and-derp").for(:url) } end end @@ -53,22 +53,22 @@ describe ProjectHook do it "POSTs to the web hook URL" do @project_hook.execute(@data) - WebMock.should have_requested(:post, @project_hook.url).once + expect(WebMock).to have_requested(:post, @project_hook.url).once end it "POSTs the data as JSON" do json = @data.to_json @project_hook.execute(@data) - WebMock.should have_requested(:post, @project_hook.url).with(body: json).once + expect(WebMock).to have_requested(:post, @project_hook.url).with(body: json).once end it "catches exceptions" do - WebHook.should_receive(:post).and_raise("Some HTTP Post error") + expect(WebHook).to receive(:post).and_raise("Some HTTP Post error") - lambda { + expect { @project_hook.execute(@data) - }.should raise_error + }.to raise_error end end end diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb index 8b299cea67..087e40c3d8 100644 --- a/spec/models/issue_spec.rb +++ b/spec/models/issue_spec.rb @@ -21,14 +21,14 @@ require 'spec_helper' describe Issue do describe "Associations" do - it { should belong_to(:milestone) } + it { is_expected.to belong_to(:milestone) } end describe "Mass assignment" do end describe 'modules' do - it { should include_module(Issuable) } + it { is_expected.to include_module(Issuable) } end subject { create(:issue) } @@ -36,10 +36,10 @@ describe Issue do describe '#is_being_reassigned?' do it 'returns true if the issue assignee has changed' do subject.assignee = create(:user) - subject.is_being_reassigned?.should be_true + expect(subject.is_being_reassigned?).to be_truthy end it 'returns false if the issue assignee has not changed' do - subject.is_being_reassigned?.should be_false + expect(subject.is_being_reassigned?).to be_falsey end end @@ -51,7 +51,7 @@ describe Issue do issue = create :issue, assignee: user end - Issue.open_for(user).count.should eq 2 + expect(Issue.open_for(user).count).to eq 2 end end @@ -60,4 +60,8 @@ describe Issue do let(:backref_text) { "issue ##{subject.iid}" } let(:set_mentionable_text) { ->(txt){ subject.description = txt } } end + + it_behaves_like 'a Taskable' do + let(:subject) { create :issue } + end end diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb index 95c0aed0ff..2fb651bef1 100644 --- a/spec/models/key_spec.rb +++ b/spec/models/key_spec.rb @@ -16,67 +16,72 @@ require 'spec_helper' describe Key do describe "Associations" do - it { should belong_to(:user) } + it { is_expected.to belong_to(:user) } end describe "Mass assignment" do end describe "Validation" do - it { should validate_presence_of(:title) } - it { should validate_presence_of(:key) } - it { should ensure_length_of(:title).is_within(0..255) } - it { should ensure_length_of(:key).is_within(0..5000) } + it { is_expected.to validate_presence_of(:title) } + it { is_expected.to validate_presence_of(:key) } + it { is_expected.to ensure_length_of(:title).is_within(0..255) } + it { is_expected.to ensure_length_of(:key).is_within(0..5000) } end describe "Methods" do - it { should respond_to :projects } + it { is_expected.to respond_to :projects } end context "validation of uniqueness" do let(:user) { create(:user) } it "accepts the key once" do - build(:key, user: user).should be_valid + expect(build(:key, user: user)).to be_valid end it "does not accept the exact same key twice" do create(:key, user: user) - build(:key, user: user).should_not be_valid + expect(build(:key, user: user)).not_to be_valid end it "does not accept a duplicate key with a different comment" do create(:key, user: user) duplicate = build(:key, user: user) duplicate.key << ' extra comment' - duplicate.should_not be_valid + expect(duplicate).not_to be_valid end end context "validate it is a fingerprintable key" do it "accepts the fingerprintable key" do - build(:key).should be_valid + expect(build(:key)).to be_valid end - it "rejects the unfingerprintable key (contains space in middle)" do - build(:key_with_a_space_in_the_middle).should_not be_valid + it 'rejects an unfingerprintable key that contains a space' do + key = build(:key) + + # Not always the middle, but close enough + key.key = key.key[0..100] + ' ' + key.key[100..-1] + + expect(key).not_to be_valid end - it "rejects the unfingerprintable key (not a key)" do - build(:invalid_key).should_not be_valid + it 'rejects the unfingerprintable key (not a key)' do + expect(build(:key, key: 'ssh-rsa an-invalid-key==')).not_to be_valid end end context 'callbacks' do it 'should add new key to authorized_file' do @key = build(:personal_key, id: 7) - GitlabShellWorker.should_receive(:perform_async).with(:add_key, @key.shell_id, @key.key) + expect(GitlabShellWorker).to receive(:perform_async).with(:add_key, @key.shell_id, @key.key) @key.save end it 'should remove key from authorized_file' do @key = create(:personal_key) - GitlabShellWorker.should_receive(:perform_async).with(:remove_key, @key.shell_id, @key.key) + expect(GitlabShellWorker).to receive(:perform_async).with(:remove_key, @key.shell_id, @key.key) @key.destroy end end diff --git a/spec/models/label_link_spec.rb b/spec/models/label_link_spec.rb index 078e61a7d6..8c24082658 100644 --- a/spec/models/label_link_spec.rb +++ b/spec/models/label_link_spec.rb @@ -1,9 +1,21 @@ +# == 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 do let(:label) { create(:label_link) } - it { label.should be_valid } + it { expect(label).to be_valid } - it { should belong_to(:label) } - it { should belong_to(:target) } + it { is_expected.to belong_to(:label) } + it { is_expected.to belong_to(:target) } end diff --git a/spec/models/label_spec.rb b/spec/models/label_spec.rb index 1d273e59bd..8644ac4660 100644 --- a/spec/models/label_spec.rb +++ b/spec/models/label_spec.rb @@ -1,31 +1,43 @@ +# == 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 +# + require 'spec_helper' describe Label do let(:label) { create(:label) } - it { label.should be_valid } + it { expect(label).to be_valid } - it { should belong_to(:project) } + it { is_expected.to belong_to(:project) } describe 'Validation' do it 'should validate color code' do - build(:label, color: 'G-ITLAB').should_not be_valid - build(:label, color: 'AABBCC').should_not be_valid - build(:label, color: '#AABBCCEE').should_not be_valid - build(:label, color: '#GGHHII').should_not be_valid - build(:label, color: '#').should_not be_valid - build(:label, color: '').should_not be_valid + expect(build(:label, color: 'G-ITLAB')).not_to be_valid + expect(build(:label, color: 'AABBCC')).not_to be_valid + expect(build(:label, color: '#AABBCCEE')).not_to be_valid + expect(build(:label, color: '#GGHHII')).not_to be_valid + expect(build(:label, color: '#')).not_to be_valid + expect(build(:label, color: '')).not_to be_valid - build(:label, color: '#AABBCC').should be_valid + expect(build(:label, color: '#AABBCC')).to be_valid end it 'should validate title' do - build(:label, title: 'G,ITLAB').should_not be_valid - build(:label, title: 'G?ITLAB').should_not be_valid - build(:label, title: 'G&ITLAB').should_not be_valid - build(:label, title: '').should_not be_valid + expect(build(:label, title: 'G,ITLAB')).not_to be_valid + expect(build(:label, title: 'G?ITLAB')).not_to be_valid + expect(build(:label, title: 'G&ITLAB')).not_to be_valid + expect(build(:label, title: '')).not_to be_valid - build(:label, title: 'GITLAB').should be_valid - build(:label, title: 'gitlab').should be_valid + expect(build(:label, title: 'GITLAB')).to be_valid + expect(build(:label, title: 'gitlab')).to be_valid end end end diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb new file mode 100644 index 0000000000..56d030a03b --- /dev/null +++ b/spec/models/member_spec.rb @@ -0,0 +1,148 @@ +require 'spec_helper' + +describe Member do + describe "Associations" do + it { is_expected.to belong_to(:user) } + end + + describe "Validation" do + subject { Member.new(access_level: Member::GUEST) } + + it { is_expected.to validate_presence_of(:user) } + it { is_expected.to validate_presence_of(:source) } + it { is_expected.to validate_inclusion_of(:access_level).in_array(Gitlab::Access.values) } + + context "when an invite email is provided" do + let(:member) { build(:project_member, invite_email: "user@example.com", user: nil) } + + it "doesn't require a user" do + expect(member).to be_valid + end + + it "requires a valid invite email" do + member.invite_email = "nope" + + expect(member).not_to be_valid + end + + it "requires a unique invite email scoped to this source" do + create(:project_member, source: member.source, invite_email: member.invite_email) + + expect(member).not_to be_valid + end + + it "is valid otherwise" do + expect(member).to be_valid + end + end + + context "when an invite email is not provided" do + let(:member) { build(:project_member) } + + it "requires a user" do + member.user = nil + + expect(member).not_to be_valid + end + + it "is valid otherwise" do + expect(member).to be_valid + end + end + end + + describe "Delegate methods" do + it { is_expected.to respond_to(:user_name) } + it { is_expected.to respond_to(:user_email) } + end + + describe ".add_user" do + let!(:user) { create(:user) } + let(:project) { create(:project) } + + context "when called with a user id" do + it "adds the user as a member" do + Member.add_user(project.project_members, user.id, ProjectMember::MASTER) + + expect(project.users).to include(user) + end + end + + context "when called with a user object" do + it "adds the user as a member" do + Member.add_user(project.project_members, user, ProjectMember::MASTER) + + expect(project.users).to include(user) + end + end + + context "when called with a known user email" do + it "adds the user as a member" do + Member.add_user(project.project_members, user.email, ProjectMember::MASTER) + + expect(project.users).to include(user) + end + end + + context "when called with an unknown user email" do + it "adds a member invite" do + Member.add_user(project.project_members, "user@example.com", ProjectMember::MASTER) + + expect(project.project_members.invite.pluck(:invite_email)).to include("user@example.com") + end + end + end + + describe "#accept_invite!" do + let!(:member) { create(:project_member, invite_email: "user@example.com", user: nil) } + let(:user) { create(:user) } + + it "resets the invite token" do + member.accept_invite!(user) + + expect(member.invite_token).to be_nil + end + + it "sets the invite accepted timestamp" do + member.accept_invite!(user) + + expect(member.invite_accepted_at).not_to be_nil + end + + it "sets the user" do + member.accept_invite!(user) + + expect(member.user).to eq(user) + end + + it "calls #after_accept_invite" do + expect(member).to receive(:after_accept_invite) + + member.accept_invite!(user) + end + end + + describe "#decline_invite!" do + let!(:member) { create(:project_member, invite_email: "user@example.com", user: nil) } + + it "destroys the member" do + member.decline_invite! + + expect(member).to be_destroyed + end + + it "calls #after_decline_invite" do + expect(member).to receive(:after_decline_invite) + + member.decline_invite! + end + end + + describe "#generate_invite_token" do + let!(:member) { create(:project_member, invite_email: "user@example.com", user: nil) } + + it "sets the invite token" do + expect { member.generate_invite_token }.to change { member.invite_token} + end + end +end diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb new file mode 100644 index 0000000000..e206c11f33 --- /dev/null +++ b/spec/models/members/group_member_spec.rb @@ -0,0 +1,46 @@ +# == 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 not null +# notification_level :integer not null +# type :string(255) +# created_at :datetime +# updated_at :datetime +# + +require 'spec_helper' + +describe GroupMember do + context 'notification' do + describe "#after_create" do + it "should send email to user" do + membership = build(:group_member) + membership.stub(notification_service: double('NotificationService').as_null_object) + expect(membership).to receive(:notification_service) + membership.save + end + end + + describe "#after_update" do + before do + @group_member = create :group_member + @group_member.stub(notification_service: double('NotificationService').as_null_object) + end + + it "should send email to user" do + expect(@group_member).to receive(:notification_service) + @group_member.update_attribute(:access_level, GroupMember::MASTER) + end + + it "does not send an email when the access level has not changed" do + expect(@group_member).not_to receive(:notification_service) + @group_member.update_attribute(:access_level, GroupMember::OWNER) + end + end + end +end diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb new file mode 100644 index 0000000000..521721f357 --- /dev/null +++ b/spec/models/members/project_member_spec.rb @@ -0,0 +1,92 @@ +# == 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 not null +# notification_level :integer not null +# type :string(255) +# created_at :datetime +# updated_at :datetime +# + +require 'spec_helper' + +describe ProjectMember do + describe :import_team do + before do + @abilities = Six.new + @abilities << Ability + + @project_1 = create :project + @project_2 = create :project + + @user_1 = create :user + @user_2 = create :user + + @project_1.team << [ @user_1, :developer ] + @project_2.team << [ @user_2, :reporter ] + + @status = @project_2.team.import(@project_1) + end + + it { expect(@status).to be_truthy } + + describe 'project 2 should get user 1 as developer. user_2 should not be changed' do + it { expect(@project_2.users).to include(@user_1) } + it { expect(@project_2.users).to include(@user_2) } + + it { expect(@abilities.allowed?(@user_1, :write_project, @project_2)).to be_truthy } + it { expect(@abilities.allowed?(@user_2, :read_project, @project_2)).to be_truthy } + end + + describe 'project 1 should not be changed' do + it { expect(@project_1.users).to include(@user_1) } + it { expect(@project_1.users).not_to include(@user_2) } + end + end + + describe :add_users_into_projects do + before do + @project_1 = create :project + @project_2 = create :project + + @user_1 = create :user + @user_2 = create :user + + ProjectMember.add_users_into_projects( + [@project_1.id, @project_2.id], + [@user_1.id, @user_2.id], + ProjectMember::MASTER + ) + end + + it { expect(@project_1.users).to include(@user_1) } + it { expect(@project_1.users).to include(@user_2) } + + + it { expect(@project_2.users).to include(@user_1) } + it { expect(@project_2.users).to include(@user_2) } + end + + describe :truncate_teams do + before do + @project_1 = create :project + @project_2 = create :project + + @user_1 = create :user + @user_2 = create :user + + @project_1.team << [ @user_1, :developer] + @project_2.team << [ @user_2, :reporter] + + ProjectMember.truncate_teams([@project_1.id, @project_2.id]) + end + + it { expect(@project_1.users).to be_empty } + it { expect(@project_2.users).to be_empty } + end +end diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb index ec6d29de82..d40503d791 100644 --- a/spec/models/merge_request_spec.rb +++ b/spec/models/merge_request_spec.rb @@ -17,41 +17,43 @@ # target_project_id :integer not null # iid :integer # description :text +# position :integer default(0) +# locked_at :datetime # require 'spec_helper' describe MergeRequest do describe "Validation" do - it { should validate_presence_of(:target_branch) } - it { should validate_presence_of(:source_branch) } + it { is_expected.to validate_presence_of(:target_branch) } + it { is_expected.to validate_presence_of(:source_branch) } end describe "Mass assignment" do end describe "Respond to" do - it { should respond_to(:unchecked?) } - it { should respond_to(:can_be_merged?) } - it { should respond_to(:cannot_be_merged?) } + it { is_expected.to respond_to(:unchecked?) } + it { is_expected.to respond_to(:can_be_merged?) } + it { is_expected.to respond_to(:cannot_be_merged?) } end describe 'modules' do - it { should include_module(Issuable) } + it { is_expected.to include_module(Issuable) } end describe "#mr_and_commit_notes" do let!(:merge_request) { create(:merge_request) } before do - merge_request.stub(:commits) { [merge_request.source_project.repository.commit] } + 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, noteable: merge_request, project: merge_request.project) end it "should include notes for commits" do - merge_request.commits.should_not be_empty - merge_request.mr_and_commit_notes.count.should == 2 + expect(merge_request.commits).not_to be_empty + expect(merge_request.mr_and_commit_notes.count).to eq(2) end end @@ -60,10 +62,10 @@ describe MergeRequest do describe '#is_being_reassigned?' do it 'returns true if the merge_request assignee has changed' do subject.assignee = create(:user) - subject.is_being_reassigned?.should be_true + expect(subject.is_being_reassigned?).to be_truthy end it 'returns false if the merge request assignee has not changed' do - subject.is_being_reassigned?.should be_false + expect(subject.is_being_reassigned?).to be_falsey end end @@ -72,11 +74,11 @@ describe MergeRequest do subject.source_project = create(:project, namespace: create(:group)) subject.target_project = create(:project, namespace: create(:group)) - subject.for_fork?.should be_true + expect(subject.for_fork?).to be_truthy end it 'returns false if is not for a fork' do - subject.for_fork?.should be_false + expect(subject.for_fork?).to be_falsey end end @@ -94,14 +96,14 @@ describe MergeRequest do it 'accesses the set of issues that will be closed on acceptance' do subject.project.stub(default_branch: subject.target_branch) - subject.closes_issues.should == [issue0, issue1].sort_by(&:id) + expect(subject.closes_issues).to eq([issue0, issue1].sort_by(&:id)) end it 'only lists issues as to be closed if it targets the default branch' do subject.project.stub(default_branch: 'master') subject.target_branch = 'something-else' - subject.closes_issues.should be_empty + expect(subject.closes_issues).to be_empty end it 'detects issues mentioned in the description' do @@ -109,7 +111,7 @@ describe MergeRequest do subject.description = "Closes ##{issue2.iid}" subject.project.stub(default_branch: subject.target_branch) - subject.closes_issues.should include(issue2) + expect(subject.closes_issues).to include(issue2) end end @@ -118,4 +120,8 @@ describe MergeRequest do let(:backref_text) { "merge request !#{subject.iid}" } let(:set_mentionable_text) { ->(txt){ subject.title = txt } } end + + it_behaves_like 'a Taskable' do + let(:subject) { create :merge_request, :simple } + end end diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb index a3071c3251..45171e1bf6 100644 --- a/spec/models/milestone_spec.rb +++ b/spec/models/milestone_spec.rb @@ -17,8 +17,8 @@ require 'spec_helper' describe Milestone do describe "Associations" do - it { should belong_to(:project) } - it { should have_many(:issues) } + it { is_expected.to belong_to(:project) } + it { is_expected.to have_many(:issues) } end describe "Mass assignment" do @@ -26,8 +26,8 @@ describe Milestone do describe "Validation" do before { subject.stub(set_iid: false) } - it { should validate_presence_of(:title) } - it { should validate_presence_of(:project) } + it { is_expected.to validate_presence_of(:title) } + it { is_expected.to validate_presence_of(:project) } end let(:milestone) { create(:milestone) } @@ -36,30 +36,30 @@ describe Milestone do describe "#percent_complete" do it "should not count open issues" do milestone.issues << issue - milestone.percent_complete.should == 0 + expect(milestone.percent_complete).to eq(0) end it "should count closed issues" do issue.close milestone.issues << issue - milestone.percent_complete.should == 100 + expect(milestone.percent_complete).to eq(100) end it "should recover from dividing by zero" do - milestone.issues.should_receive(:count).and_return(0) - milestone.percent_complete.should == 100 + expect(milestone.issues).to receive(:count).and_return(0) + expect(milestone.percent_complete).to eq(100) end end describe "#expires_at" do it "should be nil when due_date is unset" do milestone.update_attributes(due_date: nil) - milestone.expires_at.should be_nil + expect(milestone.expires_at).to be_nil end it "should not be nil when due_date is set" do milestone.update_attributes(due_date: Date.tomorrow) - milestone.expires_at.should be_present + expect(milestone.expires_at).to be_present end end @@ -69,7 +69,7 @@ describe Milestone do milestone.stub(due_date: Date.today.prev_year) end - it { milestone.expired?.should be_true } + it { expect(milestone.expired?).to be_truthy } end context "not expired" do @@ -77,7 +77,7 @@ describe Milestone do milestone.stub(due_date: Date.today.next_year) end - it { milestone.expired?.should be_false } + it { expect(milestone.expired?).to be_falsey } end end @@ -89,7 +89,7 @@ describe Milestone do ) end - it { milestone.percent_complete.should == 75 } + it { expect(milestone.percent_complete).to eq(75) } end describe :items_count do @@ -99,14 +99,14 @@ describe Milestone do milestone.merge_requests << create(:merge_request) end - it { milestone.closed_items_count.should == 1 } - it { milestone.open_items_count.should == 2 } - it { milestone.total_items_count.should == 3 } - it { milestone.is_empty?.should be_false } + it { expect(milestone.closed_items_count).to eq(1) } + it { expect(milestone.open_items_count).to eq(2) } + it { expect(milestone.total_items_count).to eq(3) } + it { expect(milestone.is_empty?).to be_falsey } end describe :can_be_closed? do - it { milestone.can_be_closed?.should be_true } + it { expect(milestone.can_be_closed?).to be_truthy } end describe :is_empty? do @@ -116,7 +116,7 @@ describe Milestone do end it 'Should return total count of issues and merge requests assigned to milestone' do - milestone.total_items_count.should eq 2 + expect(milestone.total_items_count).to eq 2 end end @@ -129,14 +129,14 @@ describe Milestone do end it 'should be true if milestone active and all nested issues closed' do - milestone.can_be_closed?.should be_true + expect(milestone.can_be_closed?).to be_truthy end it 'should be false if milestone active and not all nested issues closed' do issue.milestone = milestone issue.save - milestone.can_be_closed?.should be_false + expect(milestone.can_be_closed?).to be_falsey end end diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb index 3562ebed1f..e87432fdf6 100644 --- a/spec/models/namespace_spec.rb +++ b/spec/models/namespace_spec.rb @@ -18,29 +18,27 @@ require 'spec_helper' describe Namespace do let!(:namespace) { create(:namespace) } - it { should have_many :projects } - it { should validate_presence_of :name } - it { should validate_uniqueness_of(:name) } - it { should validate_presence_of :path } - it { should validate_uniqueness_of(:path) } - it { should validate_presence_of :owner } + it { is_expected.to have_many :projects } + it { is_expected.to validate_presence_of :name } + it { is_expected.to validate_uniqueness_of(:name) } + it { is_expected.to validate_presence_of :path } + it { is_expected.to validate_uniqueness_of(:path) } + it { is_expected.to validate_presence_of :owner } describe "Mass assignment" do end describe "Respond to" do - it { should respond_to(:human_name) } - it { should respond_to(:to_param) } + it { is_expected.to respond_to(:human_name) } + it { is_expected.to respond_to(:to_param) } end - it { Namespace.global_id.should == 'GLN' } - describe :to_param do - it { namespace.to_param.should == namespace.path } + it { expect(namespace.to_param).to eq(namespace.path) } end describe :human_name do - it { namespace.human_name.should == namespace.owner_name } + it { expect(namespace.human_name).to eq(namespace.owner_name) } end describe :search do @@ -48,8 +46,8 @@ describe Namespace do @namespace = create :namespace end - it { Namespace.search(@namespace.path).should == [@namespace] } - it { Namespace.search('unknown').should == [] } + it { expect(Namespace.search(@namespace.path)).to eq([@namespace]) } + it { expect(Namespace.search('unknown')).to eq([]) } end describe :move_dir do @@ -66,13 +64,33 @@ describe Namespace do new_path = @namespace.path + "_new" @namespace.stub(path_was: @namespace.path) @namespace.stub(path: new_path) - @namespace.move_dir.should be_true + expect(@namespace.move_dir).to be_truthy end end describe :rm_dir do it "should remove dir" do - namespace.rm_dir.should be_true + expect(namespace.rm_dir).to be_truthy + end + end + + describe :find_by_path_or_name do + before do + @namespace = create(:namespace, name: 'WoW', path: 'woW') + end + + it { expect(Namespace.find_by_path_or_name('wow')).to eq(@namespace) } + it { expect(Namespace.find_by_path_or_name('WOW')).to eq(@namespace) } + it { expect(Namespace.find_by_path_or_name('unknown')).to eq(nil) } + end + + describe ".clean_path" do + + let!(:user) { create(:user, username: "johngitlab-etc") } + let!(:namespace) { create(:namespace, path: "JohnGitLab-etc1") } + + it "cleans the path and makes sure it's available" do + expect(Namespace.clean_path("-john+gitlab-ETC%.git@gmail.com")).to eq("johngitlab-ETC2") end end end diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb index d06dee6ce9..a7bf5081d5 100644 --- a/spec/models/note_spec.rb +++ b/spec/models/note_spec.rb @@ -21,17 +21,17 @@ require 'spec_helper' describe Note do describe "Associations" do - it { should belong_to(:project) } - it { should belong_to(:noteable) } - it { should belong_to(:author).class_name('User') } + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:noteable) } + it { is_expected.to belong_to(:author).class_name('User') } end describe "Mass assignment" do end describe "Validation" do - it { should validate_presence_of(:note) } - it { should validate_presence_of(:project) } + it { is_expected.to validate_presence_of(:note) } + it { is_expected.to validate_presence_of(:project) } end describe "Voting score" do @@ -39,44 +39,44 @@ describe Note do it "recognizes a neutral note" do note = create(:votable_note, note: "This is not a +1 note") - note.should_not be_upvote - note.should_not be_downvote + expect(note).not_to be_upvote + expect(note).not_to be_downvote end it "recognizes a neutral emoji note" do note = build(:votable_note, note: "I would :+1: this, but I don't want to") - note.should_not be_upvote - note.should_not be_downvote + expect(note).not_to be_upvote + expect(note).not_to be_downvote end it "recognizes a +1 note" do note = create(:votable_note, note: "+1 for this") - note.should be_upvote + expect(note).to be_upvote end it "recognizes a +1 emoji as a vote" do note = build(:votable_note, note: ":+1: for this") - note.should be_upvote + expect(note).to be_upvote end it "recognizes a thumbsup emoji as a vote" do note = build(:votable_note, note: ":thumbsup: for this") - note.should be_upvote + expect(note).to be_upvote end it "recognizes a -1 note" do note = create(:votable_note, note: "-1 for this") - note.should be_downvote + expect(note).to be_downvote end it "recognizes a -1 emoji as a vote" do note = build(:votable_note, note: ":-1: for this") - note.should be_downvote + expect(note).to be_downvote end it "recognizes a thumbsdown emoji as a vote" do note = build(:votable_note, note: ":thumbsdown: for this") - note.should be_downvote + expect(note).to be_downvote end end @@ -87,22 +87,22 @@ describe Note do let!(:commit) { note.noteable } it "should be accessible through #noteable" do - note.commit_id.should == commit.id - note.noteable.should be_a(Commit) - note.noteable.should == commit + expect(note.commit_id).to eq(commit.id) + expect(note.noteable).to be_a(Commit) + expect(note.noteable).to eq(commit) end it "should save a valid note" do - note.commit_id.should == commit.id + expect(note.commit_id).to eq(commit.id) note.noteable == commit end it "should be recognized by #for_commit?" do - note.should be_for_commit + expect(note).to be_for_commit end it "should not be votable" do - note.should_not be_votable + expect(note).not_to be_votable end end @@ -111,20 +111,20 @@ describe Note do let!(:commit) { note.noteable } it "should save a valid note" do - note.commit_id.should == commit.id - note.noteable.id.should == commit.id + 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 - note.should be_for_diff_line + expect(note).to be_for_diff_line end it "should be recognized by #for_commit_diff_line?" do - note.should be_for_commit_diff_line + expect(note).to be_for_commit_diff_line end it "should not be votable" do - note.should_not be_votable + expect(note).not_to be_votable end end @@ -132,7 +132,7 @@ describe Note do let!(:note) { create(:note_on_issue, note: "+1 from me") } it "should not be votable" do - note.should be_votable + expect(note).to be_votable end end @@ -140,7 +140,7 @@ describe Note do let!(:note) { create(:note_on_merge_request, note: "+1 from me") } it "should be votable" do - note.should be_votable + expect(note).to be_votable end end @@ -148,7 +148,7 @@ describe Note do let!(:note) { create(:note_on_merge_request_diff, note: "+1 from me") } it "should not be votable" do - note.should_not be_votable + expect(note).not_to be_votable end end @@ -161,20 +161,35 @@ describe Note do subject { Note.create_status_change_note(thing, project, author, status, nil) } it 'creates and saves a Note' do - should be_a Note - subject.id.should_not be_nil + is_expected.to be_a Note + expect(subject.id).not_to be_nil end - its(:noteable) { should == thing } - its(:project) { should == thing.project } - its(:author) { should == author } - its(:note) { should =~ /Status changed to #{status}/ } + describe '#noteable' do + subject { super().noteable } + it { is_expected.to eq(thing) } + end + + describe '#project' do + subject { super().project } + it { is_expected.to eq(thing.project) } + end + + describe '#author' do + subject { super().author } + it { is_expected.to eq(author) } + end + + describe '#note' do + subject { super().note } + it { is_expected.to eq("Status changed to #{status}") } + end it 'appends a back-reference if a closing mentionable is supplied' do commit = double('commit', gfm_reference: 'commit 123456') n = Note.create_status_change_note(thing, project, author, status, commit) - n.note.should =~ /Status changed to #{status} by commit 123456/ + expect(n.note).to eq("Status changed to #{status} by commit 123456") end end @@ -182,24 +197,130 @@ describe Note do let(:project) { create(:project) } let(:thing) { create(:issue, project: project) } let(:author) { create(:user) } - let(:assignee) { create(:user) } + let(:assignee) { create(:user, username: "assigned_user") } subject { Note.create_assignee_change_note(thing, project, author, assignee) } context 'creates and saves a Note' do - it { should be_a Note } - its(:id) { should_not be_nil } + it { is_expected.to be_a Note } + + describe '#id' do + subject { super().id } + it { is_expected.not_to be_nil } + end end - its(:noteable) { should == thing } - its(:project) { should == thing.project } - its(:author) { should == author } - its(:note) { should =~ /Reassigned to @#{assignee.username}/ } + describe '#noteable' do + subject { super().noteable } + it { is_expected.to eq(thing) } + end + + describe '#project' do + subject { super().project } + it { is_expected.to eq(thing.project) } + end + + describe '#author' do + subject { super().author } + it { is_expected.to eq(author) } + end + + describe '#note' do + subject { super().note } + it { is_expected.to eq('Reassigned to @assigned_user') } + end context 'assignee is removed' do let(:assignee) { nil } - its(:note) { should =~ /Assignee removed/ } + describe '#note' do + subject { super().note } + it { is_expected.to eq('Assignee removed') } + end + end + end + + describe '#create_labels_change_note' do + let(:project) { create(:project) } + let(:thing) { create(:issue, project: project) } + let(:author) { create(:user) } + let(:label1) { create(:label) } + let(:label2) { create(:label) } + let(:added_labels) { [label1, label2] } + let(:removed_labels) { [] } + + subject { Note.create_labels_change_note(thing, project, author, added_labels, removed_labels) } + + context 'creates and saves a Note' do + it { is_expected.to be_a Note } + + describe '#id' do + subject { super().id } + it { is_expected.not_to be_nil } + end + end + + describe '#noteable' do + subject { super().noteable } + it { is_expected.to eq(thing) } + end + + describe '#project' do + subject { super().project } + it { is_expected.to eq(thing.project) } + end + + describe '#author' do + subject { super().author } + it { is_expected.to eq(author) } + end + + describe '#note' do + subject { super().note } + it { is_expected.to eq("Added ~#{label1.id} ~#{label2.id} labels") } + end + + context 'label is removed' do + let(:added_labels) { [label1] } + let(:removed_labels) { [label2] } + + describe '#note' do + subject { super().note } + it { is_expected.to eq("Added ~#{label1.id} and removed ~#{label2.id} labels") } + end + end + end + + describe '#create_milestone_change_note' do + let(:project) { create(:project) } + let(:thing) { create(:issue, project: project) } + let(:milestone) { create(:milestone, project: project, title: "first_milestone") } + let(:author) { create(:user) } + + subject { Note.create_milestone_change_note(thing, project, author, milestone) } + + context 'creates and saves a Note' do + it { is_expected.to be_a Note } + + describe '#id' do + subject { super().id } + it { is_expected.not_to be_nil } + end + end + + describe '#project' do + subject { super().project } + it { is_expected.to eq(thing.project) } + end + + describe '#author' do + subject { super().author } + it { is_expected.to eq(author) } + end + + describe '#note' do + subject { super().note } + it { is_expected.to eq("Milestone changed to first_milestone") } end end @@ -216,47 +337,144 @@ describe Note do context 'issue from a merge request' do subject { Note.create_cross_reference_note(issue, mergereq, author, project) } - it { should be_valid } - its(:noteable) { should == issue } - its(:project) { should == issue.project } - its(:author) { should == author } - its(:note) { should == "_mentioned in merge request !#{mergereq.iid}_" } + it { is_expected.to be_valid } + + describe '#noteable' do + subject { super().noteable } + it { is_expected.to eq(issue) } + end + + describe '#project' do + subject { super().project } + it { is_expected.to eq(issue.project) } + end + + describe '#author' do + subject { super().author } + it { is_expected.to eq(author) } + end + + describe '#note' do + subject { super().note } + it { is_expected.to eq("mentioned in merge request !#{mergereq.iid}") } + end end context 'issue from a commit' do subject { Note.create_cross_reference_note(issue, commit, author, project) } - it { should be_valid } - its(:noteable) { should == issue } - its(:note) { should == "_mentioned in commit #{commit.sha[0..5]}_" } + it { is_expected.to be_valid } + + describe '#noteable' do + subject { super().noteable } + it { is_expected.to eq(issue) } + end + + describe '#note' do + subject { super().note } + it { is_expected.to eq("mentioned in commit #{commit.sha}") } + end end context 'merge request from an issue' do subject { Note.create_cross_reference_note(mergereq, issue, author, project) } - it { should be_valid } - its(:noteable) { should == mergereq } - its(:project) { should == mergereq.project } - its(:note) { should == "_mentioned in issue ##{issue.iid}_" } + it { is_expected.to be_valid } + + describe '#noteable' do + subject { super().noteable } + it { is_expected.to eq(mergereq) } + end + + describe '#project' do + subject { super().project } + it { is_expected.to eq(mergereq.project) } + end + + describe '#note' do + subject { super().note } + it { is_expected.to eq("mentioned in issue ##{issue.iid}") } + end end context 'commit from a merge request' do subject { Note.create_cross_reference_note(commit, mergereq, author, project) } - it { should be_valid } - its(:noteable) { should == commit } - its(:project) { should == project } - its(:note) { should == "_mentioned in merge request !#{mergereq.iid}_" } + it { is_expected.to be_valid } + + describe '#noteable' do + subject { super().noteable } + it { is_expected.to eq(commit) } + end + + describe '#project' do + subject { super().project } + it { is_expected.to eq(project) } + end + + describe '#note' do + subject { super().note } + it { is_expected.to eq("mentioned in merge request !#{mergereq.iid}") } + end + end + + context 'commit contained in a merge request' do + subject { Note.create_cross_reference_note(mergereq.commits.first, mergereq, author, project) } + + it { is_expected.to be_nil } end context 'commit from issue' do subject { Note.create_cross_reference_note(commit, issue, author, project) } - it { should be_valid } - its(:noteable_type) { should == "Commit" } - its(:noteable_id) { should be_nil } - its(:commit_id) { should == commit.id } - its(:note) { should == "_mentioned in issue ##{issue.iid}_" } + it { is_expected.to be_valid } + + describe '#noteable_type' do + subject { super().noteable_type } + it { is_expected.to eq("Commit") } + end + + describe '#noteable_id' do + subject { super().noteable_id } + it { is_expected.to be_nil } + end + + describe '#commit_id' do + subject { super().commit_id } + it { is_expected.to eq(commit.id) } + end + + describe '#note' do + subject { super().note } + it { is_expected.to eq("mentioned in issue ##{issue.iid}") } + end + end + + context 'commit from commit' do + let(:parent_commit) { commit.parents.first } + subject { Note.create_cross_reference_note(commit, parent_commit, author, project) } + + it { is_expected.to be_valid } + + describe '#noteable_type' do + subject { super().noteable_type } + it { is_expected.to eq("Commit") } + end + + describe '#noteable_id' do + subject { super().noteable_id } + it { is_expected.to be_nil } + end + + describe '#commit_id' do + subject { super().commit_id } + it { is_expected.to eq(commit.id) } + end + + describe '#note' do + subject { super().note } + it { is_expected.to eq("mentioned in commit #{parent_commit.id}") } + end end end @@ -264,19 +482,63 @@ describe Note do let(:project) { create :project } let(:author) { create :user } let(:issue) { create :issue } - let(:commit0) { double 'commit0', gfm_reference: 'commit 123456' } - let(:commit1) { double 'commit1', gfm_reference: 'commit 654321' } + let(:commit0) { project.repository.commit } + let(:commit1) { project.repository.commit('HEAD~2') } before do Note.create_cross_reference_note(issue, commit0, author, project) end it 'detects if a mentionable has already been mentioned' do - Note.cross_reference_exists?(issue, commit0).should be_true + expect(Note.cross_reference_exists?(issue, commit0)).to be_truthy end it 'detects if a mentionable has not already been mentioned' do - Note.cross_reference_exists?(issue, commit1).should be_false + expect(Note.cross_reference_exists?(issue, commit1)).to be_falsey + end + + context 'commit on commit' do + before do + Note.create_cross_reference_note(commit0, commit1, author, project) + end + + it { expect(Note.cross_reference_exists?(commit0, commit1)).to be_truthy } + it { expect(Note.cross_reference_exists?(commit1, commit0)).to be_falsey } + end + + context 'legacy note with Markdown emphasis' do + let(:issue2) { create :issue, project: project } + let!(:note) do + create :note, system: true, noteable_id: issue2.id, + noteable_type: "Issue", note: "_mentioned in issue " \ + "#{issue.project.path_with_namespace}##{issue.iid}_" + end + + it 'detects if a mentionable with emphasis has been mentioned' do + expect(Note.cross_reference_exists?(issue2, issue)).to be_truthy + end + end + end + + describe '#cross_references_with_underscores?' do + let(:project) { create :project, path: "first_project" } + let(:second_project) { create :project, path: "second_project" } + + let(:author) { create :user } + let(:issue0) { create :issue, project: project } + let(:issue1) { create :issue, project: second_project } + let!(:note) { Note.create_cross_reference_note(issue0, issue1, author, project) } + + it 'detects if a mentionable has already been mentioned' do + expect(Note.cross_reference_exists?(issue0, issue1)).to be_truthy + end + + it 'detects if a mentionable has not already been mentioned' do + expect(Note.cross_reference_exists?(issue1, issue0)).to be_falsey + end + + it 'detects that text has underscores' do + expect(note.note).to eq("mentioned in issue #{second_project.path_with_namespace}##{issue1.iid}") end end @@ -286,25 +548,37 @@ describe Note do let(:other) { create(:issue, project: project) } let(:author) { create(:user) } let(:assignee) { create(:user) } + let(:label) { create(:label) } + let(:milestone) { create(:milestone) } it 'should recognize user-supplied notes as non-system' do @note = create(:note_on_issue) - @note.should_not be_system + expect(@note).not_to be_system end it 'should identify status-change notes as system notes' do @note = Note.create_status_change_note(issue, project, author, 'closed', nil) - @note.should be_system + expect(@note).to be_system end it 'should identify cross-reference notes as system notes' do @note = Note.create_cross_reference_note(issue, other, author, project) - @note.should be_system + expect(@note).to be_system end it 'should identify assignee-change notes as system notes' do @note = Note.create_assignee_change_note(issue, project, author, assignee) - @note.should be_system + expect(@note).to be_system + end + + it 'should identify label-change notes as system notes' do + @note = Note.create_labels_change_note(issue, project, author, [label], []) + expect(@note).to be_system + end + + it 'should identify milestone-change notes as system notes' do + @note = Note.create_milestone_change_note(issue, project, author, milestone) + expect(@note).to be_system end end @@ -321,36 +595,36 @@ describe Note do describe :read do before do - @p1.users_projects.create(user: @u2, project_access: UsersProject::GUEST) - @p2.users_projects.create(user: @u3, project_access: UsersProject::GUEST) + @p1.project_members.create(user: @u2, access_level: ProjectMember::GUEST) + @p2.project_members.create(user: @u3, access_level: ProjectMember::GUEST) end - it { @abilities.allowed?(@u1, :read_note, @p1).should be_false } - it { @abilities.allowed?(@u2, :read_note, @p1).should be_true } - it { @abilities.allowed?(@u3, :read_note, @p1).should be_false } + it { expect(@abilities.allowed?(@u1, :read_note, @p1)).to be_falsey } + it { expect(@abilities.allowed?(@u2, :read_note, @p1)).to be_truthy } + it { expect(@abilities.allowed?(@u3, :read_note, @p1)).to be_falsey } end describe :write do before do - @p1.users_projects.create(user: @u2, project_access: UsersProject::DEVELOPER) - @p2.users_projects.create(user: @u3, project_access: UsersProject::DEVELOPER) + @p1.project_members.create(user: @u2, access_level: ProjectMember::DEVELOPER) + @p2.project_members.create(user: @u3, access_level: ProjectMember::DEVELOPER) end - it { @abilities.allowed?(@u1, :write_note, @p1).should be_false } - it { @abilities.allowed?(@u2, :write_note, @p1).should be_true } - it { @abilities.allowed?(@u3, :write_note, @p1).should be_false } + it { expect(@abilities.allowed?(@u1, :write_note, @p1)).to be_falsey } + it { expect(@abilities.allowed?(@u2, :write_note, @p1)).to be_truthy } + it { expect(@abilities.allowed?(@u3, :write_note, @p1)).to be_falsey } end describe :admin do before do - @p1.users_projects.create(user: @u1, project_access: UsersProject::REPORTER) - @p1.users_projects.create(user: @u2, project_access: UsersProject::MASTER) - @p2.users_projects.create(user: @u3, project_access: UsersProject::MASTER) + @p1.project_members.create(user: @u1, access_level: ProjectMember::REPORTER) + @p1.project_members.create(user: @u2, access_level: ProjectMember::MASTER) + @p2.project_members.create(user: @u3, access_level: ProjectMember::MASTER) end - it { @abilities.allowed?(@u1, :admin_note, @p1).should be_false } - it { @abilities.allowed?(@u2, :admin_note, @p1).should be_true } - it { @abilities.allowed?(@u3, :admin_note, @p1).should be_false } + it { expect(@abilities.allowed?(@u1, :admin_note, @p1)).to be_falsey } + it { expect(@abilities.allowed?(@u2, :admin_note, @p1)).to be_truthy } + it { expect(@abilities.allowed?(@u3, :admin_note, @p1)).to be_falsey } end end diff --git a/spec/models/project_security_spec.rb b/spec/models/project_security_spec.rb index 1f2bd7a56f..1ee1900354 100644 --- a/spec/models/project_security_spec.rb +++ b/spec/models/project_security_spec.rb @@ -23,88 +23,88 @@ describe Project do describe "Non member rules" do it "should deny for non-project users any actions" do admin_actions.each do |action| - @abilities.allowed?(@u1, action, @p1).should be_false + expect(@abilities.allowed?(@u1, action, @p1)).to be_falsey end end end describe "Guest Rules" do before do - @p1.users_projects.create(project: @p1, user: @u2, project_access: UsersProject::GUEST) + @p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::GUEST) end it "should allow for project user any guest actions" do guest_actions.each do |action| - @abilities.allowed?(@u2, action, @p1).should be_true + expect(@abilities.allowed?(@u2, action, @p1)).to be_truthy end end end describe "Report Rules" do before do - @p1.users_projects.create(project: @p1, user: @u2, project_access: UsersProject::REPORTER) + @p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::REPORTER) end it "should allow for project user any report actions" do report_actions.each do |action| - @abilities.allowed?(@u2, action, @p1).should be_true + expect(@abilities.allowed?(@u2, action, @p1)).to be_truthy end end end describe "Developer Rules" do before do - @p1.users_projects.create(project: @p1, user: @u2, project_access: UsersProject::REPORTER) - @p1.users_projects.create(project: @p1, user: @u3, project_access: UsersProject::DEVELOPER) + @p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::REPORTER) + @p1.project_members.create(project: @p1, user: @u3, access_level: ProjectMember::DEVELOPER) end it "should deny for developer master-specific actions" do [dev_actions - report_actions].each do |action| - @abilities.allowed?(@u2, action, @p1).should be_false + expect(@abilities.allowed?(@u2, action, @p1)).to be_falsey end end it "should allow for project user any dev actions" do dev_actions.each do |action| - @abilities.allowed?(@u3, action, @p1).should be_true + expect(@abilities.allowed?(@u3, action, @p1)).to be_truthy end end end describe "Master Rules" do before do - @p1.users_projects.create(project: @p1, user: @u2, project_access: UsersProject::DEVELOPER) - @p1.users_projects.create(project: @p1, user: @u3, project_access: UsersProject::MASTER) + @p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::DEVELOPER) + @p1.project_members.create(project: @p1, user: @u3, access_level: ProjectMember::MASTER) end it "should deny for developer master-specific actions" do [master_actions - dev_actions].each do |action| - @abilities.allowed?(@u2, action, @p1).should be_false + expect(@abilities.allowed?(@u2, action, @p1)).to be_falsey end end it "should allow for project user any master actions" do master_actions.each do |action| - @abilities.allowed?(@u3, action, @p1).should be_true + expect(@abilities.allowed?(@u3, action, @p1)).to be_truthy end end end describe "Admin Rules" do before do - @p1.users_projects.create(project: @p1, user: @u2, project_access: UsersProject::DEVELOPER) - @p1.users_projects.create(project: @p1, user: @u3, project_access: UsersProject::MASTER) + @p1.project_members.create(project: @p1, user: @u2, access_level: ProjectMember::DEVELOPER) + @p1.project_members.create(project: @p1, user: @u3, access_level: ProjectMember::MASTER) end it "should deny for masters admin-specific actions" do [admin_actions - master_actions].each do |action| - @abilities.allowed?(@u2, action, @p1).should be_false + expect(@abilities.allowed?(@u2, action, @p1)).to be_falsey end end it "should allow for project owner any admin actions" do admin_actions.each do |action| - @abilities.allowed?(@u4, action, @p1).should be_true + expect(@abilities.allowed?(@u4, action, @p1)).to be_truthy end end end diff --git a/spec/models/project_services/asana_service_spec.rb b/spec/models/project_services/asana_service_spec.rb new file mode 100644 index 0000000000..13c8d54a2a --- /dev/null +++ b/spec/models/project_services/asana_service_spec.rb @@ -0,0 +1,65 @@ +# == 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) +# + +require 'spec_helper' + +describe AsanaService, models: true do + describe 'Associations' do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe 'Validations' do + context 'active' do + before do + subject.active = true + end + + it { is_expected.to validate_presence_of :api_key } + end + end + + describe 'Execute' do + let(:user) { create(:user) } + let(:project) { create(:project) } + + before do + @asana = AsanaService.new + @asana.stub( + project: project, + project_id: project.id, + service_hook: true, + api_key: 'verySecret', + restrict_to_branch: 'master' + ) + end + + it 'should call Asana service to created a story' do + expect(Asana::Task).to receive(:find).with('123456').once + + @asana.check_commit('related to #123456', 'pushed') + end + + it 'should call Asana service to created a story and close a task' do + expect(Asana::Task).to receive(:find).with('456789').twice + + @asana.check_commit('fix #456789', 'pushed') + end + end +end diff --git a/spec/models/project_services/assembla_service_spec.rb b/spec/models/project_services/assembla_service_spec.rb new file mode 100644 index 0000000000..91730da1ee --- /dev/null +++ b/spec/models/project_services/assembla_service_spec.rb @@ -0,0 +1,53 @@ +# == 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) +# + +require 'spec_helper' + +describe AssemblaService, models: true do + describe "Associations" do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe "Execute" do + let(:user) { create(:user) } + let(:project) { create(:project) } + + before do + @assembla_service = AssemblaService.new + @assembla_service.stub( + project_id: project.id, + project: project, + service_hook: true, + token: 'verySecret', + subdomain: 'project_name' + ) + @sample_data = Gitlab::PushDataBuilder.build_sample(project, user) + @api_url = 'https://atlas.assembla.com/spaces/project_name/github_tool?secret_key=verySecret' + WebMock.stub_request(:post, @api_url) + end + + it "should call Assembla API" do + @assembla_service.execute(@sample_data) + expect(WebMock).to have_requested(:post, @api_url).with( + body: /#{@sample_data[:before]}.*#{@sample_data[:after]}.*#{project.path}/ + ).once + end + end +end diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb new file mode 100644 index 0000000000..e987241f3c --- /dev/null +++ b/spec/models/project_services/buildkite_service_spec.rb @@ -0,0 +1,82 @@ +# == 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) +# + +require 'spec_helper' + +describe BuildkiteService do + describe 'Associations' do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe 'commits methods' do + before do + @project = Project.new + @project.stub( + default_branch: 'default-brancho' + ) + + @service = BuildkiteService.new + @service.stub( + project: @project, + service_hook: true, + project_url: 'https://buildkite.com/account-name/example-project', + token: 'secret-sauce-webhook-token:secret-sauce-status-token' + ) + end + + describe :webhook_url do + it 'returns the webhook url' do + expect(@service.webhook_url).to eq( + 'https://webhook.buildkite.com/deliver/secret-sauce-webhook-token' + ) + end + end + + describe :commit_status_path do + it 'returns the correct status page' do + expect(@service.commit_status_path('2ab7834c')).to eq( + 'https://gitlab.buildkite.com/status/secret-sauce-status-token.json?commit=2ab7834c' + ) + end + end + + describe :build_page do + it 'returns the correct build page' do + expect(@service.build_page('2ab7834c', nil)).to eq( + 'https://buildkite.com/account-name/example-project/builds?commit=2ab7834c' + ) + end + end + + describe :builds_page do + it 'returns the correct path to the builds page' do + expect(@service.builds_path).to eq( + 'https://buildkite.com/account-name/example-project/builds?branch=default-brancho' + ) + end + end + + describe :status_img_path do + it 'returns the correct path to the status image' do + expect(@service.status_img_path).to eq('https://badge.buildkite.com/secret-sauce-status-token.svg') + end + end + end +end diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb new file mode 100644 index 0000000000..73f68301a3 --- /dev/null +++ b/spec/models/project_services/flowdock_service_spec.rb @@ -0,0 +1,52 @@ +# == 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) +# + +require 'spec_helper' + +describe FlowdockService do + describe "Associations" do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe "Execute" do + let(:user) { create(:user) } + let(:project) { create(:project) } + + before do + @flowdock_service = FlowdockService.new + @flowdock_service.stub( + project_id: project.id, + project: project, + service_hook: true, + token: 'verySecret' + ) + @sample_data = Gitlab::PushDataBuilder.build_sample(project, user) + @api_url = 'https://api.flowdock.com/v1/git/verySecret' + WebMock.stub_request(:post, @api_url) + end + + it "should call FlowDock API" do + @flowdock_service.execute(@sample_data) + expect(WebMock).to have_requested(:post, @api_url).with( + body: /#{@sample_data[:before]}.*#{@sample_data[:after]}.*#{project.path}/ + ).once + end + end +end diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb new file mode 100644 index 0000000000..d44064bbe6 --- /dev/null +++ b/spec/models/project_services/gemnasium_service_spec.rb @@ -0,0 +1,48 @@ +# == 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) +# + +require 'spec_helper' + +describe GemnasiumService do + describe "Associations" do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe "Execute" do + let(:user) { create(:user) } + let(:project) { create(:project) } + + before do + @gemnasium_service = GemnasiumService.new + @gemnasium_service.stub( + project_id: project.id, + project: project, + service_hook: true, + token: 'verySecret', + api_key: 'GemnasiumUserApiKey' + ) + @sample_data = Gitlab::PushDataBuilder.build_sample(project, user) + end + it "should call Gemnasium service" do + expect(Gemnasium::GitlabService).to receive(:execute).with(an_instance_of(Hash)).once + @gemnasium_service.execute(@sample_data) + end + end +end diff --git a/spec/models/project_services/gitlab_ci_service_spec.rb b/spec/models/project_services/gitlab_ci_service_spec.rb new file mode 100644 index 0000000000..6a557d839c --- /dev/null +++ b/spec/models/project_services/gitlab_ci_service_spec.rb @@ -0,0 +1,70 @@ +# == 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) +# + +require 'spec_helper' + +describe GitlabCiService do + describe "Associations" do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe "Mass assignment" do + end + + describe 'commits methods' do + before do + @service = GitlabCiService.new + @service.stub( + service_hook: true, + project_url: 'http://ci.gitlab.org/projects/2', + token: 'verySecret' + ) + end + + describe :commit_status_path do + it { expect(@service.commit_status_path("2ab7834c", 'master')).to eq("http://ci.gitlab.org/projects/2/refs/master/commits/2ab7834c/status.json?token=verySecret")} + end + + describe :build_page do + it { expect(@service.build_page("2ab7834c", 'master')).to eq("http://ci.gitlab.org/projects/2/refs/master/commits/2ab7834c")} + end + end + + describe "Fork registration" do + before do + @old_project = create(:empty_project) + @project = create(:empty_project) + @user = create(:user) + + @service = GitlabCiService.new + @service.stub( + service_hook: true, + project_url: 'http://ci.gitlab.org/projects/2', + token: 'verySecret', + project: @old_project + ) + end + + it "performs http reuquest to ci" do + stub_request(:post, "http://ci.gitlab.org/api/v1/forks") + @service.fork_registration(@project, @user.private_token) + end + end +end diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb new file mode 100644 index 0000000000..f94bef5c36 --- /dev/null +++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb @@ -0,0 +1,66 @@ +# == 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) +# + +require 'spec_helper' + +describe GitlabIssueTrackerService do + describe "Associations" do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + + describe 'project and issue urls' do + let(:project) { create(:project) } + + context 'with absolute urls' do + before do + GitlabIssueTrackerService.default_url_options[:script_name] = "/gitlab/root" + @service = project.create_gitlab_issue_tracker_service(active: true) + end + + after do + @service.destroy! + end + + it 'should give the correct path' do + expect(@service.project_url).to eq("http://localhost/gitlab/root/#{project.path_with_namespace}/issues") + expect(@service.new_issue_url).to eq("http://localhost/gitlab/root/#{project.path_with_namespace}/issues/new") + expect(@service.issue_url(432)).to eq("http://localhost/gitlab/root/#{project.path_with_namespace}/issues/432") + end + end + + context 'with relative urls' do + before do + GitlabIssueTrackerService.default_url_options[:script_name] = "/gitlab/root" + @service = project.create_gitlab_issue_tracker_service(active: true) + end + + after do + @service.destroy! + end + + it 'should give the correct path' do + expect(@service.project_path).to eq("/gitlab/root/#{project.path_with_namespace}/issues") + expect(@service.new_issue_path).to eq("/gitlab/root/#{project.path_with_namespace}/issues/new") + expect(@service.issue_path(432)).to eq("/gitlab/root/#{project.path_with_namespace}/issues/432") + end + end + end +end diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb new file mode 100644 index 0000000000..8ab847e643 --- /dev/null +++ b/spec/models/project_services/hipchat_service_spec.rb @@ -0,0 +1,217 @@ +# == Schema Information +# +# Table name: services +# +# id :integer not null, primary key +# type :string(255) +# title :string(255) +# project_id :integer not null +# created_at :datetime +# updated_at :datetime +# active :boolean default(FALSE), not null +# properties :text +# push_events :boolean default(TRUE) +# issues_events :boolean default(TRUE) +# merge_requests_events :boolean default(TRUE) +# tag_push_events :boolean default(TRUE) +# + +require 'spec_helper' + +describe HipchatService do + describe "Associations" do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe "Execute" do + let(:hipchat) { HipchatService.new } + let(:user) { create(:user, username: 'username') } + let(:project) { create(:project, name: 'project') } + let(:api_url) { 'https://hipchat.example.com/v2/room/123456/notification?auth_token=verySecret' } + let(:project_name) { project.name_with_namespace.gsub(/\s/, '') } + + before(:each) do + hipchat.stub( + project_id: project.id, + project: project, + room: 123456, + server: 'https://hipchat.example.com', + token: 'verySecret' + ) + WebMock.stub_request(:post, api_url) + end + + context 'push events' do + let(:push_sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) } + + it "should call Hipchat API for push events" do + hipchat.execute(push_sample_data) + + expect(WebMock).to have_requested(:post, api_url).once + end + + it "should create a push message" do + message = hipchat.send(:create_push_message, push_sample_data) + + obj_attr = push_sample_data[:object_attributes] + branch = push_sample_data[:ref].gsub('refs/heads/', '') + expect(message).to include("#{user.name} pushed to branch " \ + "#{branch} of " \ + "#{project_name}") + end + end + + context 'tag_push events' do + let(:push_sample_data) { Gitlab::PushDataBuilder.build(project, user, Gitlab::Git::BLANK_SHA, '1' * 40, 'refs/tags/test', []) } + + it "should call Hipchat API for tag push events" do + hipchat.execute(push_sample_data) + + expect(WebMock).to have_requested(:post, api_url).once + end + + it "should create a tag push message" do + message = hipchat.send(:create_push_message, push_sample_data) + + obj_attr = push_sample_data[:object_attributes] + expect(message).to eq("#{user.name} pushed new tag " \ + "test to " \ + "#{project_name}\n") + end + end + + context 'issue events' do + let(:issue) { create(:issue, title: 'Awesome issue', description: 'please fix') } + let(:issue_service) { Issues::CreateService.new(project, user) } + let(:issues_sample_data) { issue_service.hook_data(issue, 'open') } + + it "should call Hipchat API for issue events" do + hipchat.execute(issues_sample_data) + + expect(WebMock).to have_requested(:post, api_url).once + end + + it "should create an issue message" do + message = hipchat.send(:create_issue_message, issues_sample_data) + + obj_attr = issues_sample_data[:object_attributes] + expect(message).to eq("#{user.name} opened " \ + "issue ##{obj_attr["iid"]} in " \ + "#{project_name}: " \ + "Awesome issue" \ + "
    please fix
    ") + end + end + + context 'merge request events' do + let(:merge_request) { create(:merge_request, description: 'please fix', title: 'Awesome merge request', target_project: project, source_project: project) } + let(:merge_service) { MergeRequests::CreateService.new(project, user) } + let(:merge_sample_data) { merge_service.hook_data(merge_request, 'open') } + + it "should call Hipchat API for merge requests events" do + hipchat.execute(merge_sample_data) + + expect(WebMock).to have_requested(:post, api_url).once + end + + it "should create a merge request message" do + message = hipchat.send(:create_merge_request_message, + merge_sample_data) + + obj_attr = merge_sample_data[:object_attributes] + expect(message).to eq("#{user.name} opened " \ + "merge request ##{obj_attr["iid"]} in " \ + "#{project_name}: " \ + "Awesome merge request" \ + "
    please fix
    ") + end + end + + 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 + + message = hipchat.send(:create_message, data) + + 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 " \ + "commit #{commit_id} in " \ + "#{project_name}: " \ + "#{title}" \ + "
    a comment on a commit
    ") + end + + it "should call Hipchat API for merge request comment events" do + data = Gitlab::NoteDataBuilder.build(merge_request_note, user) + hipchat.execute(data) + + expect(WebMock).to have_requested(:post, api_url).once + + 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 " \ + "merge request ##{merge_id} in " \ + "#{project_name}: " \ + "#{title}" \ + "
    merge request note
    ") + end + + it "should call Hipchat API for issue comment events" do + data = Gitlab::NoteDataBuilder.build(issue_note, user) + hipchat.execute(data) + + message = hipchat.send(:create_message, data) + + obj_attr = data[:object_attributes] + issue_id = data[:issue]['iid'] + title = data[:issue]['title'] + + expect(message).to eq("#{user.name} commented on " \ + "issue ##{issue_id} in " \ + "#{project_name}: " \ + "#{title}" \ + "
    issue note
    ") + end + + it "should call Hipchat API for snippet comment events" do + data = Gitlab::NoteDataBuilder.build(snippet_note, user) + hipchat.execute(data) + + expect(WebMock).to have_requested(:post, api_url).once + + message = hipchat.send(:create_message, data) + + obj_attr = data[:object_attributes] + snippet_id = data[:snippet]['id'] + title = data[:snippet]['title'] + + expect(message).to eq("#{user.name} commented on " \ + "snippet ##{snippet_id} in " \ + "#{project_name}: " \ + "#{title}" \ + "
    snippet note
    ") + end + end + end +end diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb new file mode 100644 index 0000000000..d55399bc36 --- /dev/null +++ b/spec/models/project_services/irker_service_spec.rb @@ -0,0 +1,108 @@ +# == 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) +# + +require 'spec_helper' +require 'socket' +require 'json' + +describe IrkerService do + describe 'Associations' do + it { should belong_to :project } + it { should have_one :service_hook } + end + + describe 'Validations' do + before do + subject.active = true + subject.properties['recipients'] = _recipients + end + + context 'active' do + let(:_recipients) { nil } + it { should validate_presence_of :recipients } + end + + context 'too many recipients' do + let(:_recipients) { 'a b c d' } + it 'should add an error if there is too many recipients' do + subject.send :check_recipients_count + subject.errors.should_not be_blank + end + end + + context '3 recipients' do + let(:_recipients) { 'a b c' } + it 'should not add an error if there is 3 recipients' do + subject.send :check_recipients_count + subject.errors.should be_blank + end + end + end + + describe 'Execute' do + let(:irker) { IrkerService.new } + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) } + + let(:recipients) { '#commits' } + let(:colorize_messages) { '1' } + + before do + irker.stub( + active: true, + project: project, + project_id: project.id, + service_hook: true, + properties: { + 'recipients' => recipients, + 'colorize_messages' => colorize_messages + } + ) + irker.settings = { + server_ip: 'localhost', + server_port: 6659, + max_channels: 3, + default_irc_uri: 'irc://chat.freenode.net/' + } + irker.valid? + @irker_server = TCPServer.new 'localhost', 6659 + end + + after do + @irker_server.close + end + + it 'should send valid JSON messages to an Irker listener' do + irker.execute(sample_data) + + conn = @irker_server.accept + conn.readlines.each do |line| + msg = JSON.load(line.chomp("\n")) + msg.keys.should match_array(['to', 'privmsg']) + if msg['to'].is_a?(String) + msg['to'].should == 'irc://chat.freenode.net/#commits' + else + msg['to'].should match_array(['irc://chat.freenode.net/#commits']) + end + end + conn.close + end + end +end diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb new file mode 100644 index 0000000000..355911e637 --- /dev/null +++ b/spec/models/project_services/jira_service_spec.rb @@ -0,0 +1,102 @@ +# == 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) +# + +require 'spec_helper' + +describe JiraService do + describe "Associations" do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe "Validations" do + context "active" do + before do + subject.active = true + end + + it { is_expected.to validate_presence_of :project_url } + it { is_expected.to validate_presence_of :issues_url } + it { is_expected.to validate_presence_of :new_issue_url } + end + end + + describe 'description and title' do + let(:project) { create(:project) } + + context 'when it is not set' do + before do + @service = project.create_jira_service(active: true) + end + + after do + @service.destroy! + end + + it 'should be initialized' do + expect(@service.title).to eq('JIRA') + expect(@service.description).to eq("Jira issue tracker") + end + end + + context 'when it is set' do + before do + properties = { 'title' => 'Jira One', 'description' => 'Jira One issue tracker' } + @service = project.create_jira_service(active: true, properties: properties) + end + + after do + @service.destroy! + end + + it "should be correct" do + expect(@service.title).to eq('Jira One') + expect(@service.description).to eq('Jira One issue tracker') + end + end + end + + describe 'project and issue urls' do + let(:project) { create(:project) } + + context 'when gitlab.yml was initialized' do + before do + settings = { "jira" => { + "title" => "Jira", + "project_url" => "http://jira.sample/projects/project_a", + "issues_url" => "http://jira.sample/issues/:id", + "new_issue_url" => "http://jira.sample/projects/project_a/issues/new" + } + } + allow(Gitlab.config).to receive(:issues_tracker).and_return(settings) + @service = project.create_jira_service(active: true) + end + + after do + @service.destroy! + end + + it 'should be prepopulated with the settings' do + expect(@service.properties[:project_url]).to eq('http://jira.sample/projects/project_a') + expect(@service.properties[:issues_url]).to eq("http://jira.sample/issues/:id") + expect(@service.properties[:new_issue_url]).to eq("http://jira.sample/projects/project_a/issues/new") + end + end + end +end diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb new file mode 100644 index 0000000000..5a18fd09bf --- /dev/null +++ b/spec/models/project_services/pushover_service_spec.rb @@ -0,0 +1,74 @@ +# == 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) +# + +require 'spec_helper' + +describe PushoverService do + describe 'Associations' do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe 'Validations' do + context 'active' do + before do + subject.active = true + end + + it { is_expected.to validate_presence_of :api_key } + it { is_expected.to validate_presence_of :user_key } + it { is_expected.to validate_presence_of :priority } + end + end + + describe 'Execute' do + let(:pushover) { PushoverService.new } + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) } + + let(:api_key) { 'verySecret' } + let(:user_key) { 'verySecret' } + let(:device) { 'myDevice' } + let(:priority) { 0 } + let(:sound) { 'bike' } + let(:api_url) { 'https://api.pushover.net/1/messages.json' } + + before do + pushover.stub( + project: project, + project_id: project.id, + service_hook: true, + api_key: api_key, + user_key: user_key, + device: device, + priority: priority, + sound: sound + ) + + WebMock.stub_request(:post, api_url) + end + + it 'should call Pushover API' do + pushover.execute(sample_data) + + expect(WebMock).to have_requested(:post, api_url).once + end + end +end diff --git a/spec/models/project_services/slack_service/issue_message_spec.rb b/spec/models/project_services/slack_service/issue_message_spec.rb new file mode 100644 index 0000000000..8bca1fef44 --- /dev/null +++ b/spec/models/project_services/slack_service/issue_message_spec.rb @@ -0,0 +1,56 @@ +require 'spec_helper' + +describe SlackService::IssueMessage do + subject { SlackService::IssueMessage.new(args) } + + let(:args) { + { + user: { + name: 'Test User', + username: 'Test User' + }, + project_name: 'project_name', + project_url: 'somewhere.com', + + object_attributes: { + title: 'Issue title', + id: 10, + iid: 100, + assignee_id: 1, + url: 'url', + action: 'open', + state: 'opened', + description: 'issue description' + } + } + } + + let(:color) { '#345' } + + context 'open' do + it 'returns a message regarding opening of issues' do + expect(subject.pretext).to eq( + 'Test User opened in : '\ + '*Issue title*') + expect(subject.attachments).to eq([ + { + text: "issue description", + color: color, + } + ]) + end + end + + context 'close' do + before 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 in : '\ + '*Issue title*') + expect(subject.attachments).to be_empty + end + end +end diff --git a/spec/models/project_services/slack_service/merge_message_spec.rb b/spec/models/project_services/slack_service/merge_message_spec.rb new file mode 100644 index 0000000000..aeb408aa76 --- /dev/null +++ b/spec/models/project_services/slack_service/merge_message_spec.rb @@ -0,0 +1,51 @@ +require 'spec_helper' + +describe SlackService::MergeMessage do + subject { SlackService::MergeMessage.new(args) } + + let(:args) { + { + user: { + name: 'Test User', + username: 'Test User' + }, + project_name: 'project_name', + project_url: 'somewhere.com', + + object_attributes: { + title: "Issue title\nSecond line", + id: 10, + iid: 100, + assignee_id: 1, + url: 'url', + state: 'opened', + description: 'issue description', + source_branch: 'source_branch', + target_branch: 'target_branch', + } + } + } + + let(:color) { '#345' } + + context 'open' do + it 'returns a message regarding opening of merge requests' do + expect(subject.pretext).to eq( + 'Test User opened '\ + 'in : *Issue title*') + expect(subject.attachments).to be_empty + end + end + + context 'close' do + before do + args[:object_attributes][:state] = 'closed' + end + it 'returns a message regarding closing of merge requests' do + expect(subject.pretext).to eq( + 'Test User closed '\ + 'in : *Issue title*') + expect(subject.attachments).to be_empty + end + 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 new file mode 100644 index 0000000000..21fb575480 --- /dev/null +++ b/spec/models/project_services/slack_service/note_message_spec.rb @@ -0,0 +1,129 @@ +require 'spec_helper' + +describe SlackService::NoteMessage do + let(:color) { '#345' } + + before do + @args = { + user: { + name: 'Test User', + username: 'username', + avatar_url: 'http://fakeavatar' + }, + project_name: 'project_name', + project_url: 'somewhere.com', + repository: { + name: 'project_name', + url: 'somewhere.com', + }, + object_attributes: { + id: 10, + note: 'comment on a commit', + url: 'url', + noteable_type: 'Commit' + } + } + end + + context 'commit notes' do + before do + @args[:object_attributes][:note] = 'comment on a commit' + @args[:object_attributes][:noteable_type] = 'Commit' + @args[:commit] = { + id: '5f163b2b95e6f53cbd428f5f0b103702a52b9a23', + message: "Added a commit message\ndetails\n123\n" + } + end + + it 'returns a message regarding notes on commits' do + message = SlackService::NoteMessage.new(@args) + expect(message.pretext).to eq("Test User commented on " \ + " in : " \ + "*Added a commit message*") + expected_attachments = [ + { + text: "comment on a commit", + color: color, + } + ] + expect(message.attachments).to eq(expected_attachments) + end + end + + context 'merge request notes' do + before do + @args[:object_attributes][:note] = 'comment on a merge request' + @args[:object_attributes][:noteable_type] = 'MergeRequest' + @args[:merge_request] = { + id: 1, + iid: 30, + title: "merge request title\ndetails\n" + } + end + it 'returns a message regarding notes on a merge request' do + message = SlackService::NoteMessage.new(@args) + expect(message.pretext).to eq("Test User commented on " \ + " in : " \ + "*merge request title*") + expected_attachments = [ + { + text: "comment on a merge request", + color: color, + } + ] + expect(message.attachments).to eq(expected_attachments) + end + end + + context 'issue notes' do + before do + @args[:object_attributes][:note] = 'comment on an issue' + @args[:object_attributes][:noteable_type] = 'Issue' + @args[:issue] = { + id: 1, + iid: 20, + title: "issue title\ndetails\n" + } + end + + it 'returns a message regarding notes on an issue' do + message = SlackService::NoteMessage.new(@args) + expect(message.pretext).to eq( + "Test User commented on " \ + " in : " \ + "*issue title*") + expected_attachments = [ + { + text: "comment on an issue", + color: color, + } + ] + expect(message.attachments).to eq(expected_attachments) + end + end + + context 'project snippet notes' do + before do + @args[:object_attributes][:note] = 'comment on a snippet' + @args[:object_attributes][:noteable_type] = 'Snippet' + @args[:snippet] = { + id: 5, + title: "snippet title\ndetails\n" + } + end + + it 'returns a message regarding notes on a project snippet' do + message = SlackService::NoteMessage.new(@args) + expect(message.pretext).to eq("Test User commented on " \ + " in : " \ + "*snippet title*") + expected_attachments = [ + { + text: "comment on a snippet", + color: color, + } + ] + expect(message.attachments).to eq(expected_attachments) + end + end +end diff --git a/spec/models/slack_message_spec.rb b/spec/models/project_services/slack_service/push_message_spec.rb similarity index 50% rename from spec/models/slack_message_spec.rb rename to spec/models/project_services/slack_service/push_message_spec.rb index 1cd5853470..10963481a1 100644 --- a/spec/models/slack_message_spec.rb +++ b/spec/models/project_services/slack_service/push_message_spec.rb @@ -1,7 +1,7 @@ -require_relative '../../app/models/project_services/slack_message' +require 'spec_helper' -describe SlackMessage do - subject { SlackMessage.new(args) } +describe SlackService::PushMessage do + subject { SlackService::PushMessage.new(args) } let(:args) { { @@ -25,41 +25,64 @@ describe SlackMessage do end it 'returns a message regarding pushes' do - subject.pretext.should == - 'user_name pushed to branch of ' << + expect(subject.pretext).to eq( + 'user_name pushed to branch of '\ ' ()' - subject.attachments.should == [ + ) + expect(subject.attachments).to eq([ { - text: ": message1 - author1\n" << - ": message2 - author2", + text: ": message1 - author1\n"\ + ": message2 - author2", color: color, } - ] + ]) + end + end + + context 'tag push' do + let(:args) { + { + after: 'after', + before: Gitlab::Git::BLANK_SHA, + project_name: 'project_name', + ref: 'refs/tags/new_tag', + user_name: 'user_name', + project_url: 'url' + } + } + + it 'returns a message regarding pushes' do + expect(subject.pretext).to eq('user_name pushed new tag ' \ + ' to ' \ + '') + expect(subject.attachments).to be_empty end end context 'new branch' do before do - args[:before] = '000000' + args[:before] = Gitlab::Git::BLANK_SHA end it 'returns a message regarding a new branch' do - subject.pretext.should == - 'user_name pushed new branch to ' << + expect(subject.pretext).to eq( + 'user_name pushed new branch to '\ '' - subject.attachments.should be_empty + ) + expect(subject.attachments).to be_empty end end context 'removed branch' do before do - args[:after] = '000000' + args[:after] = Gitlab::Git::BLANK_SHA end it 'returns a message regarding a removed branch' do - subject.pretext.should == + expect(subject.pretext).to eq( 'user_name removed branch master from ' - subject.attachments.should be_empty + ) + expect(subject.attachments).to be_empty end end end diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb new file mode 100644 index 0000000000..c36506644b --- /dev/null +++ b/spec/models/project_services/slack_service_spec.rb @@ -0,0 +1,170 @@ +# == 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) +# + +require 'spec_helper' + +describe SlackService do + describe "Associations" do + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } + end + + describe "Validations" do + context "active" do + before do + subject.active = true + end + + it { is_expected.to validate_presence_of :webhook } + end + end + + describe "Execute" do + let(:slack) { SlackService.new } + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:push_sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) } + let(:webhook_url) { 'https://hooks.slack.com/services/SVRWFV0VVAR97N/B02R25XN3/ZBqu7xMupaEEICInN685' } + let(:username) { 'slack_username' } + let(:channel) { 'slack_channel' } + + before do + slack.stub( + project: project, + project_id: project.id, + service_hook: true, + webhook: webhook_url + ) + + WebMock.stub_request(:post, webhook_url) + + opts = { + title: 'Awesome issue', + description: 'please fix' + } + + issue_service = Issues::CreateService.new(project, user, opts) + @issue = issue_service.execute + @issues_sample_data = issue_service.hook_data(@issue, 'open') + + opts = { + title: 'Awesome merge_request', + description: 'please fix', + source_branch: 'stable', + target_branch: 'master' + } + merge_service = MergeRequests::CreateService.new(project, + user, opts) + @merge_request = merge_service.execute + @merge_sample_data = merge_service.hook_data(@merge_request, + 'open') + end + + it "should call Slack API for push events" do + slack.execute(push_sample_data) + + expect(WebMock).to have_requested(:post, webhook_url).once + end + + it "should call Slack API for issue events" do + slack.execute(@issues_sample_data) + + expect(WebMock).to have_requested(:post, webhook_url).once + end + + it "should call Slack API for merge requests events" do + slack.execute(@merge_sample_data) + + expect(WebMock).to have_requested(:post, webhook_url).once + end + + it 'should use the username as an option for slack when configured' do + slack.stub(username: username) + expect(Slack::Notifier).to receive(:new). + with(webhook_url, username: username). + and_return( + double(:slack_service).as_null_object + ) + slack.execute(push_sample_data) + end + + it 'should use the channel as an option when it is configured' do + slack.stub(channel: channel) + expect(Slack::Notifier).to receive(:new). + with(webhook_url, channel: channel). + and_return( + double(:slack_service).as_null_object + ) + slack.execute(push_sample_data) + end + end + + describe "Note events" 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 + slack.stub( + project: project, + project_id: project.id, + service_hook: true, + webhook: webhook_url + ) + + 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) + + expect(WebMock).to have_requested(:post, webhook_url).once + end + + 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 + + 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 + + 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 diff --git a/spec/models/project_snippet_spec.rb b/spec/models/project_snippet_spec.rb index e4df934460..3e8f106d27 100644 --- a/spec/models/project_snippet_spec.rb +++ b/spec/models/project_snippet_spec.rb @@ -2,30 +2,30 @@ # # 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) -# expires_at :datetime -# private :boolean default(TRUE), not null -# type :string(255) +# 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) +# expires_at :datetime +# type :string(255) +# visibility_level :integer default(0), not null # require 'spec_helper' describe ProjectSnippet do describe "Associations" do - it { should belong_to(:project) } + it { is_expected.to belong_to(:project) } end describe "Mass assignment" do end describe "Validation" do - it { should validate_presence_of(:project) } + it { is_expected.to validate_presence_of(:project) } end end diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index 82ab97cdd7..879a63dd9f 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -22,102 +22,109 @@ # visibility_level :integer default(0), not null # archived :boolean default(FALSE), not null # 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) +# avatar :string(255) # require 'spec_helper' describe Project do - describe "Associations" do - it { should belong_to(:group) } - it { should belong_to(:namespace) } - it { should belong_to(:creator).class_name('User') } - it { should have_many(:users) } - it { should have_many(:events).dependent(:destroy) } - it { should have_many(:merge_requests).dependent(:destroy) } - it { should have_many(:issues).dependent(:destroy) } - it { should have_many(:milestones).dependent(:destroy) } - it { should have_many(:users_projects).dependent(:destroy) } - it { should have_many(:notes).dependent(:destroy) } - it { should have_many(:snippets).class_name('ProjectSnippet').dependent(:destroy) } - it { should have_many(:deploy_keys_projects).dependent(:destroy) } - it { should have_many(:deploy_keys) } - it { should have_many(:hooks).dependent(:destroy) } - it { should have_many(:protected_branches).dependent(:destroy) } - it { should have_one(:forked_project_link).dependent(:destroy) } - it { should have_one(:slack_service).dependent(:destroy) } + describe 'Associations' do + it { is_expected.to belong_to(:group) } + it { is_expected.to belong_to(:namespace) } + it { is_expected.to belong_to(:creator).class_name('User') } + it { is_expected.to have_many(:users) } + it { is_expected.to have_many(:events).dependent(:destroy) } + it { is_expected.to have_many(:merge_requests).dependent(:destroy) } + it { is_expected.to have_many(:issues).dependent(:destroy) } + it { is_expected.to have_many(:milestones).dependent(:destroy) } + it { is_expected.to have_many(:project_members).dependent(:destroy) } + it { is_expected.to have_many(:notes).dependent(:destroy) } + it { is_expected.to have_many(:snippets).class_name('ProjectSnippet').dependent(:destroy) } + it { is_expected.to have_many(:deploy_keys_projects).dependent(:destroy) } + it { is_expected.to have_many(:deploy_keys) } + it { is_expected.to have_many(:hooks).dependent(:destroy) } + it { is_expected.to have_many(:protected_branches).dependent(:destroy) } + it { is_expected.to have_one(:forked_project_link).dependent(:destroy) } + it { is_expected.to have_one(:slack_service).dependent(:destroy) } + it { is_expected.to have_one(:pushover_service).dependent(:destroy) } + it { is_expected.to have_one(:asana_service).dependent(:destroy) } end - describe "Mass assignment" do + describe 'Mass assignment' do end - describe "Validation" do + describe 'Validation' do let!(:project) { create(:project) } - it { should validate_presence_of(:name) } - it { should validate_uniqueness_of(:name).scoped_to(:namespace_id) } - it { should ensure_length_of(:name).is_within(0..255) } + it { is_expected.to validate_presence_of(:name) } + it { is_expected.to validate_uniqueness_of(:name).scoped_to(:namespace_id) } + it { is_expected.to ensure_length_of(:name).is_within(0..255) } - it { should validate_presence_of(:path) } - it { should validate_uniqueness_of(:path).scoped_to(:namespace_id) } - it { should ensure_length_of(:path).is_within(0..255) } - it { should ensure_length_of(:description).is_within(0..2000) } - it { should validate_presence_of(:creator) } - it { should ensure_length_of(:issues_tracker_id).is_within(0..255) } - it { should validate_presence_of(:namespace) } + it { is_expected.to validate_presence_of(:path) } + it { is_expected.to validate_uniqueness_of(:path).scoped_to(:namespace_id) } + it { is_expected.to ensure_length_of(:path).is_within(0..255) } + it { is_expected.to ensure_length_of(:description).is_within(0..2000) } + it { is_expected.to validate_presence_of(:creator) } + it { is_expected.to ensure_length_of(:issues_tracker_id).is_within(0..255) } + it { is_expected.to validate_presence_of(:namespace) } - it "should not allow new projects beyond user limits" do + it 'should not allow new projects beyond user limits' do project2 = build(:project) - project2.stub(:creator).and_return(double(can_create_project?: false, projects_limit: 0).as_null_object) - project2.should_not be_valid - project2.errors[:limit_reached].first.should match(/Your project limit is 0/) + 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/) end end - describe "Respond to" do - it { should respond_to(:url_to_repo) } - it { should respond_to(:repo_exists?) } - it { should respond_to(:satellite) } - it { should respond_to(:update_merge_requests) } - it { should respond_to(:execute_hooks) } - it { should respond_to(:name_with_namespace) } - it { should respond_to(:owner) } - it { should respond_to(:path_with_namespace) } + describe 'Respond to' do + it { is_expected.to respond_to(:url_to_repo) } + it { is_expected.to respond_to(:repo_exists?) } + it { is_expected.to respond_to(:satellite) } + it { is_expected.to respond_to(:update_merge_requests) } + it { is_expected.to respond_to(:execute_hooks) } + it { is_expected.to respond_to(:name_with_namespace) } + it { is_expected.to respond_to(:owner) } + it { is_expected.to respond_to(:path_with_namespace) } end - it "should return valid url to repo" do - project = Project.new(path: "somewhere") - project.url_to_repo.should == Gitlab.config.gitlab_shell.ssh_path_prefix + "somewhere.git" + it 'should return valid url to repo' do + project = Project.new(path: 'somewhere') + expect(project.url_to_repo).to eq(Gitlab.config.gitlab_shell.ssh_path_prefix + 'somewhere.git') end - it "returns the full web URL for this repo" do - project = Project.new(path: "somewhere") - project.web_url.should == "#{Gitlab.config.gitlab.url}/somewhere" + it 'returns the full web URL for this repo' do + project = Project.new(path: 'somewhere') + expect(project.web_url).to eq("#{Gitlab.config.gitlab.url}/somewhere") end - it "returns the web URL without the protocol for this repo" do - project = Project.new(path: "somewhere") - project.web_url_without_protocol.should == "#{Gitlab.config.gitlab.url.split("://")[1]}/somewhere" + it 'returns the web URL without the protocol for this repo' do + project = Project.new(path: 'somewhere') + expect(project.web_url_without_protocol).to eq("#{Gitlab.config.gitlab.url.split('://')[1]}/somewhere") end - describe "last_activity methods" do + describe 'last_activity methods' do let(:project) { create(:project) } let(:last_event) { double(created_at: Time.now) } - describe "last_activity" do - it "should alias last_activity to last_event" do + describe 'last_activity' do + it 'should alias last_activity to last_event' do project.stub(last_event: last_event) - project.last_activity.should == last_event + expect(project.last_activity).to eq(last_event) end end describe 'last_activity_date' do it 'returns the creation date of the project\'s last event if present' do last_activity_event = create(:event, project: project) - project.last_activity_at.to_i.should == last_event.created_at.to_i + expect(project.last_activity_at.to_i).to eq(last_event.created_at.to_i) end it 'returns the project\'s last update date if it has no events' do - project.last_activity_date.should == project.updated_at + expect(project.last_activity_date).to eq(project.updated_at) end end end @@ -129,20 +136,19 @@ describe Project do let(:prev_commit_id) { merge_request.commits.last.id } let(:commit_id) { merge_request.commits.first.id } - it "should close merge request if last commit from source branch was pushed to target branch" do + it 'should close merge request if last commit from source branch was pushed to target branch' do project.update_merge_requests(prev_commit_id, commit_id, "refs/heads/#{merge_request.target_branch}", key.user) merge_request.reload - merge_request.merged?.should be_true + expect(merge_request.merged?).to be_truthy end - it "should update merge request commits with new one if pushed to source branch" do + it 'should update merge request commits with new one if pushed to source branch' do project.update_merge_requests(prev_commit_id, commit_id, "refs/heads/#{merge_request.source_branch}", key.user) merge_request.reload - merge_request.last_commit.id.should == commit_id + expect(merge_request.last_commit.id).to eq(commit_id) end end - describe :find_with_namespace do context 'with namespace' do before do @@ -150,8 +156,8 @@ describe Project do @project = create(:project, name: 'gitlabhq', namespace: @group) end - it { Project.find_with_namespace('gitlab/gitlabhq').should == @project } - it { Project.find_with_namespace('gitlab-ci').should be_nil } + it { expect(Project.find_with_namespace('gitlab/gitlabhq')).to eq(@project) } + it { expect(Project.find_with_namespace('gitlab-ci')).to be_nil } end end @@ -162,15 +168,15 @@ describe Project do @project = create(:project, name: 'gitlabhq', namespace: @group) end - it { @project.to_param.should == "gitlab/gitlabhq" } + it { expect(@project.to_param).to eq('gitlabhq') } end end describe :repository do let(:project) { create(:project) } - it "should return valid repo" do - project.repository.should be_kind_of(Repository) + it 'should return valid repo' do + expect(project.repository).to be_kind_of(Repository) end end @@ -180,29 +186,29 @@ describe Project do let(:not_existed_issue) { create(:issue) } let(:ext_project) { create(:redmine_project) } - it "should be true or if used internal tracker and issue exists" do - project.issue_exists?(existed_issue.iid).should be_true + it 'should be true or if used internal tracker and issue exists' do + expect(project.issue_exists?(existed_issue.iid)).to be_truthy end - it "should be false or if used internal tracker and issue not exists" do - project.issue_exists?(not_existed_issue.iid).should be_false + it 'should be false or if used internal tracker and issue not exists' do + expect(project.issue_exists?(not_existed_issue.iid)).to be_falsey end - it "should always be true if used other tracker" do - ext_project.issue_exists?(rand(100)).should be_true + it 'should always be true if used other tracker' do + expect(ext_project.issue_exists?(rand(100))).to be_truthy end end - describe :used_default_issues_tracker? do + describe :default_issues_tracker? do let(:project) { create(:project) } let(:ext_project) { create(:redmine_project) } it "should be true if used internal tracker" do - project.used_default_issues_tracker?.should be_true + expect(project.default_issues_tracker?).to be_truthy end it "should be false if used other tracker" do - ext_project.used_default_issues_tracker?.should be_false + expect(ext_project.default_issues_tracker?).to be_falsey end end @@ -210,20 +216,20 @@ describe Project do let(:project) { create(:project) } let(:ext_project) { create(:redmine_project) } - it "should be true for projects with external issues tracker if issues enabled" do - ext_project.can_have_issues_tracker_id?.should be_true + it 'should be true for projects with external issues tracker if issues enabled' do + expect(ext_project.can_have_issues_tracker_id?).to be_truthy end - it "should be false for projects with internal issue tracker if issues enabled" do - project.can_have_issues_tracker_id?.should be_false + it 'should be false for projects with internal issue tracker if issues enabled' do + expect(project.can_have_issues_tracker_id?).to be_falsey end - it "should be always false if issues disabled" do + it 'should be always false if issues disabled' do project.issues_enabled = false ext_project.issues_enabled = false - project.can_have_issues_tracker_id?.should be_false - ext_project.can_have_issues_tracker_id?.should be_false + expect(project.can_have_issues_tracker_id?).to be_falsey + expect(ext_project.can_have_issues_tracker_id?).to be_falsey end end @@ -234,8 +240,8 @@ describe Project do project.protected_branches.create(name: 'master') end - it { project.open_branches.map(&:name).should include('feature') } - it { project.open_branches.map(&:name).should_not include('master') } + it { expect(project.open_branches.map(&:name)).to include('feature') } + it { expect(project.open_branches.map(&:name)).not_to include('master') } end describe '#star_count' do @@ -306,4 +312,49 @@ describe Project do expect(project.star_count).to eq(0) end end + + describe :avatar_type do + let(:project) { create(:project) } + + it 'should be true if avatar is image' do + project.update_attribute(:avatar, 'uploads/avatar.png') + expect(project.avatar_type).to be_truthy + end + + it 'should be false if avatar is html page' do + project.update_attribute(:avatar, 'uploads/avatar.html') + expect(project.avatar_type).to eq(['only images allowed']) + end + end + + describe :avatar_url do + subject { project.avatar_url } + + let(:project) { create(:project) } + + context 'When avatar file is uploaded' do + before do + project.update_columns(avatar: 'uploads/avatar.png') + allow(project.avatar).to receive(:present?) { true } + end + + let(:avatar_path) do + "/uploads/project/avatar/#{project.id}/uploads/avatar.png" + end + + it { should eq "http://localhost#{avatar_path}" } + end + + context 'When avatar file in git' do + before do + allow(project).to receive(:avatar_in_git) { true } + end + + let(:avatar_path) do + "/#{project.namespace.name}/#{project.path}/avatar" + end + + it { should eq "http://localhost#{avatar_path}" } + end + end end diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb index 34c1a686c9..19201cc15a 100644 --- a/spec/models/project_team_spec.rb +++ b/spec/models/project_team_spec.rb @@ -16,17 +16,19 @@ describe ProjectTeam do end describe 'members collection' do - it { project.team.masters.should include(master) } - it { project.team.masters.should_not include(guest) } - it { project.team.masters.should_not include(reporter) } - it { project.team.masters.should_not include(nonmember) } + it { expect(project.team.masters).to include(master) } + it { expect(project.team.masters).not_to include(guest) } + it { expect(project.team.masters).not_to include(reporter) } + it { expect(project.team.masters).not_to include(nonmember) } end describe 'access methods' do - it { project.team.master?(master).should be_true } - it { project.team.master?(guest).should be_false } - it { project.team.master?(reporter).should be_false } - it { project.team.master?(nonmember).should be_false } + it { expect(project.team.master?(master)).to be_truthy } + it { expect(project.team.master?(guest)).to be_falsey } + it { expect(project.team.master?(reporter)).to be_falsey } + it { expect(project.team.master?(nonmember)).to be_falsey } + it { expect(project.team.member?(nonmember)).to be_falsey } + it { expect(project.team.member?(guest)).to be_truthy } end end @@ -47,19 +49,21 @@ describe ProjectTeam do end describe 'members collection' do - it { project.team.reporters.should include(reporter) } - it { project.team.masters.should include(master) } - it { project.team.masters.should include(guest) } - it { project.team.masters.should_not include(reporter) } - it { project.team.masters.should_not include(nonmember) } + it { expect(project.team.reporters).to include(reporter) } + it { expect(project.team.masters).to include(master) } + it { expect(project.team.masters).to include(guest) } + it { expect(project.team.masters).not_to include(reporter) } + it { expect(project.team.masters).not_to include(nonmember) } end describe 'access methods' do - it { project.team.reporter?(reporter).should be_true } - it { project.team.master?(master).should be_true } - it { project.team.master?(guest).should be_true } - it { project.team.master?(reporter).should be_false } - it { project.team.master?(nonmember).should be_false } + it { expect(project.team.reporter?(reporter)).to be_truthy } + it { expect(project.team.master?(master)).to be_truthy } + it { expect(project.team.master?(guest)).to be_truthy } + it { expect(project.team.master?(reporter)).to be_falsey } + it { expect(project.team.master?(nonmember)).to be_falsey } + it { expect(project.team.member?(nonmember)).to be_falsey } + it { expect(project.team.member?(guest)).to be_truthy } end end end diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb index e4ee2fc5b1..2acdb7dfdd 100644 --- a/spec/models/project_wiki_spec.rb +++ b/spec/models/project_wiki_spec.rb @@ -12,19 +12,19 @@ describe ProjectWiki do describe "#path_with_namespace" do it "returns the project path with namespace with the .wiki extension" do - subject.path_with_namespace.should == project.path_with_namespace + ".wiki" + expect(subject.path_with_namespace).to eq(project.path_with_namespace + ".wiki") end end describe "#url_to_repo" do it "returns the correct ssh url to the repo" do - subject.url_to_repo.should == gitlab_shell.url_to_repo(subject.path_with_namespace) + expect(subject.url_to_repo).to eq(gitlab_shell.url_to_repo(subject.path_with_namespace)) end end describe "#ssh_url_to_repo" do it "equals #url_to_repo" do - subject.ssh_url_to_repo.should == subject.url_to_repo + expect(subject.ssh_url_to_repo).to eq(subject.url_to_repo) end end @@ -32,21 +32,21 @@ describe ProjectWiki do it "provides the full http url to the repo" do gitlab_url = Gitlab.config.gitlab.url repo_http_url = "#{gitlab_url}/#{subject.path_with_namespace}.git" - subject.http_url_to_repo.should == repo_http_url + expect(subject.http_url_to_repo).to eq(repo_http_url) end end describe "#wiki" do it "contains a Gollum::Wiki instance" do - subject.wiki.should be_a Gollum::Wiki + expect(subject.wiki).to be_a Gollum::Wiki end it "creates a new wiki repo if one does not yet exist" do - project_wiki.create_page("index", "test content").should be_true + expect(project_wiki.create_page("index", "test content")).to be_truthy end it "raises CouldNotCreateWikiError if it can't create the wiki repository" do - project_wiki.stub(:init_repo).and_return(false) + allow(project_wiki).to receive(:init_repo).and_return(false) expect { project_wiki.send(:create_repo!) }.to raise_exception(ProjectWiki::CouldNotCreateWikiError) end end @@ -54,21 +54,27 @@ describe ProjectWiki do describe "#empty?" do context "when the wiki repository is empty" do before do - Gitlab::Shell.any_instance.stub(:add_repository) do + allow_any_instance_of(Gitlab::Shell).to receive(:add_repository) do create_temp_repo("#{Rails.root}/tmp/test-git-base-path/non-existant.wiki.git") end - project.stub(:path_with_namespace).and_return("non-existant") + allow(project).to receive(:path_with_namespace).and_return("non-existant") end - its(:empty?) { should be_true } + describe '#empty?' do + subject { super().empty? } + it { is_expected.to be_truthy } + end end context "when the wiki has pages" do before do - create_page("index", "This is an awesome new Gollum Wiki") + project_wiki.create_page("index", "This is an awesome new Gollum Wiki") end - its(:empty?) { should be_false } + describe '#empty?' do + subject { super().empty? } + it { is_expected.to be_falsey } + end end end @@ -83,11 +89,11 @@ describe ProjectWiki do end it "returns an array of WikiPage instances" do - @pages.first.should be_a WikiPage + expect(@pages.first).to be_a WikiPage end it "returns the correct number of pages" do - @pages.count.should == 1 + expect(@pages.count).to eq(1) end end @@ -102,55 +108,55 @@ describe ProjectWiki do it "returns the latest version of the page if it exists" do page = subject.find_page("index page") - page.title.should == "index page" + expect(page.title).to eq("index page") end it "returns nil if the page does not exist" do - subject.find_page("non-existant").should == nil + expect(subject.find_page("non-existant")).to eq(nil) end it "can find a page by slug" do page = subject.find_page("index-page") - page.title.should == "index page" + expect(page.title).to eq("index page") end it "returns a WikiPage instance" do page = subject.find_page("index page") - page.should be_a WikiPage + expect(page).to be_a WikiPage end end describe '#find_file' do before do file = Gollum::File.new(subject.wiki) - Gollum::Wiki.any_instance. - stub(:file).with('image.jpg', 'master', true). + allow_any_instance_of(Gollum::Wiki). + to receive(:file).with('image.jpg', 'master', true). and_return(file) - Gollum::File.any_instance. - stub(:mime_type). + allow_any_instance_of(Gollum::File). + to receive(:mime_type). and_return('image/jpeg') - Gollum::Wiki.any_instance. - stub(:file).with('non-existant', 'master', true). + allow_any_instance_of(Gollum::Wiki). + to receive(:file).with('non-existant', 'master', true). and_return(nil) end after do - Gollum::Wiki.any_instance.unstub(:file) - Gollum::File.any_instance.unstub(:mime_type) + allow_any_instance_of(Gollum::Wiki).to receive(:file).and_call_original + allow_any_instance_of(Gollum::File).to receive(:mime_type).and_call_original end it 'returns the latest version of the file if it exists' do file = subject.find_file('image.jpg') - file.mime_type.should == 'image/jpeg' + expect(file.mime_type).to eq('image/jpeg') end it 'returns nil if the page does not exist' do - subject.find_file('non-existant').should == nil + expect(subject.find_file('non-existant')).to eq(nil) end it 'returns a Gollum::File instance' do file = subject.find_file('image.jpg') - file.should be_a Gollum::File + expect(file).to be_a Gollum::File end end @@ -160,23 +166,23 @@ describe ProjectWiki do end it "creates a new wiki page" do - subject.create_page("test page", "this is content").should_not == false - subject.pages.count.should == 1 + expect(subject.create_page("test page", "this is content")).not_to eq(false) + expect(subject.pages.count).to eq(1) end it "returns false when a duplicate page exists" do subject.create_page("test page", "content") - subject.create_page("test page", "content").should == false + expect(subject.create_page("test page", "content")).to eq(false) end it "stores an error message when a duplicate page exists" do 2.times { subject.create_page("test page", "content") } - subject.error_message.should =~ /Duplicate page:/ + expect(subject.error_message).to match(/Duplicate page:/) end it "sets the correct commit message" do subject.create_page("test page", "some content", :markdown, "commit message") - subject.pages.first.page.version.message.should == "commit message" + expect(subject.pages.first.page.version.message).to eq("commit message") end end @@ -193,11 +199,11 @@ describe ProjectWiki do end it "updates the content of the page" do - @page.raw_data.should == "some other content" + expect(@page.raw_data).to eq("some other content") end it "sets the correct commit message" do - @page.version.message.should == "updated page" + expect(@page.version.message).to eq("updated page") end end @@ -209,7 +215,7 @@ describe ProjectWiki do it "deletes the page" do subject.delete_page(@page) - subject.pages.count.should == 0 + expect(subject.pages.count).to eq(0) end end diff --git a/spec/models/protected_branch_spec.rb b/spec/models/protected_branch_spec.rb index af48c2c6d9..1e6937b536 100644 --- a/spec/models/protected_branch_spec.rb +++ b/spec/models/protected_branch_spec.rb @@ -2,25 +2,26 @@ # # 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 +# 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 do describe 'Associations' do - it { should belong_to(:project) } + it { is_expected.to belong_to(:project) } end describe "Mass assignment" do end describe 'Validation' do - it { should validate_presence_of(:project) } - it { should validate_presence_of(:name) } + it { is_expected.to validate_presence_of(:project) } + it { is_expected.to validate_presence_of(:name) } end end diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb new file mode 100644 index 0000000000..f41e5a97ca --- /dev/null +++ b/spec/models/repository_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +describe Repository do + include RepoHelpers + + let(:repository) { create(:project).repository } + + describe :branch_names_contains do + subject { repository.branch_names_contains(sample_commit.id) } + + it { is_expected.to include('master') } + it { is_expected.not_to include('feature') } + it { is_expected.not_to include('fix') } + end + + describe :tag_names_contains do + subject { repository.tag_names_contains(sample_commit.id) } + + it { is_expected.to include('v1.1.0') } + it { is_expected.not_to include('v1.0.0') } + end + + describe :last_commit_for_path do + subject { repository.last_commit_for_path(sample_commit.id, '.gitignore').id } + + it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') } + end +end diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb index adeeac115c..735652aea7 100644 --- a/spec/models/service_spec.rb +++ b/spec/models/service_spec.rb @@ -2,19 +2,19 @@ # # Table name: services # -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# token :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# project_url :string(255) -# subdomain :string(255) -# room :string(255) -# recipients :text -# api_key :string(255) +# 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) # require 'spec_helper' @@ -22,8 +22,8 @@ require 'spec_helper' describe Service do describe "Associations" do - it { should belong_to :project } - it { should have_one :service_hook } + it { is_expected.to belong_to :project } + it { is_expected.to have_one :service_hook } end describe "Mass assignment" do @@ -45,7 +45,7 @@ describe Service do end describe :can_test do - it { @testable.should == true } + it { expect(@testable).to eq(true) } end end @@ -60,7 +60,32 @@ describe Service do end describe :can_test do - it { @testable.should == true } + it { expect(@testable).to eq(true) } + end + end + end + + describe "Template" do + describe "for pushover service" do + let(:service_template) { + PushoverService.create(template: true, properties: {device: 'MyDevice', sound: 'mic', priority: 4, api_key: '123456789'}) + } + let(:project) { create(:project) } + + describe 'should be prefilled for projects pushover service' do + before do + service_template + project.build_missing_services + end + + it "should have all fields prefilled" do + service = project.pushover_service + expect(service.template).to eq(false) + expect(service.device).to eq('MyDevice') + expect(service.sound).to eq('mic') + expect(service.priority).to eq(4) + expect(service.api_key).to eq('123456789') + end end end end diff --git a/spec/models/slack_service_spec.rb b/spec/models/slack_service_spec.rb deleted file mode 100644 index b00eb30569..0000000000 --- a/spec/models/slack_service_spec.rb +++ /dev/null @@ -1,70 +0,0 @@ -# == Schema Information -# -# Table name: services -# -# id :integer not null, primary key -# type :string(255) -# title :string(255) -# token :string(255) -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# active :boolean default(FALSE), not null -# project_url :string(255) -# subdomain :string(255) -# room :string(255) -# recipients :text -# api_key :string(255) -# - -require 'spec_helper' - -describe SlackService do - describe "Associations" do - it { should belong_to :project } - it { should have_one :service_hook } - end - - describe "Validations" do - context "active" do - before do - subject.active = true - end - - it { should validate_presence_of :room } - it { should validate_presence_of :subdomain } - it { should validate_presence_of :token } - end - end - - describe "Execute" do - let(:slack) { SlackService.new } - let(:user) { create(:user) } - let(:project) { create(:project) } - let(:sample_data) { GitPushService.new.sample_data(project, user) } - let(:subdomain) { 'gitlab' } - let(:token) { 'verySecret' } - let(:api_url) { - "https://#{subdomain}.slack.com/services/hooks/incoming-webhook?token=#{token}" - } - - before do - slack.stub( - project: project, - project_id: project.id, - room: '#gitlab', - service_hook: true, - subdomain: subdomain, - token: token - ) - - WebMock.stub_request(:post, api_url) - end - - it "should call Slack API" do - slack.execute(sample_data) - - WebMock.should have_requested(:post, api_url).once - end - end -end diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index d179e9516e..e37dcc7523 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -2,39 +2,39 @@ # # 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) -# expires_at :datetime -# private :boolean default(TRUE), not null -# type :string(255) +# 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) +# expires_at :datetime +# type :string(255) +# visibility_level :integer default(0), not null # require 'spec_helper' describe Snippet do describe "Associations" do - it { should belong_to(:author).class_name('User') } - it { should have_many(:notes).dependent(:destroy) } + it { is_expected.to belong_to(:author).class_name('User') } + it { is_expected.to have_many(:notes).dependent(:destroy) } end describe "Mass assignment" do end describe "Validation" do - it { should validate_presence_of(:author) } + it { is_expected.to validate_presence_of(:author) } - it { should validate_presence_of(:title) } - it { should ensure_length_of(:title).is_within(0..255) } + it { is_expected.to validate_presence_of(:title) } + it { is_expected.to ensure_length_of(:title).is_within(0..255) } - it { should validate_presence_of(:file_name) } - it { should ensure_length_of(:title).is_within(0..255) } + it { is_expected.to validate_presence_of(:file_name) } + it { is_expected.to ensure_length_of(:title).is_within(0..255) } - it { should validate_presence_of(:content) } + it { is_expected.to validate_presence_of(:content) } end end diff --git a/spec/models/system_hook_spec.rb b/spec/models/system_hook_spec.rb deleted file mode 100644 index 2b98acdeb6..0000000000 --- a/spec/models/system_hook_spec.rb +++ /dev/null @@ -1,65 +0,0 @@ -# == Schema Information -# -# Table name: web_hooks -# -# id :integer not null, primary key -# url :string(255) -# project_id :integer -# created_at :datetime -# updated_at :datetime -# type :string(255) 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) -# - -require "spec_helper" - -describe SystemHook do - describe "execute" do - before(:each) do - @system_hook = create(:system_hook) - WebMock.stub_request(:post, @system_hook.url) - end - - it "project_create hook" do - Projects::CreateService.new(create(:user), name: 'empty').execute - WebMock.should have_requested(:post, @system_hook.url).with(body: /project_create/).once - end - - it "project_destroy hook" do - user = create(:user) - project = create(:empty_project, namespace: user.namespace) - Projects::DestroyService.new(project, user, {}).execute - WebMock.should have_requested(:post, @system_hook.url).with(body: /project_destroy/).once - end - - it "user_create hook" do - create(:user) - WebMock.should have_requested(:post, @system_hook.url).with(body: /user_create/).once - end - - it "user_destroy hook" do - user = create(:user) - user.destroy - WebMock.should have_requested(:post, @system_hook.url).with(body: /user_destroy/).once - end - - it "project_create hook" do - user = create(:user) - project = create(:project) - project.team << [user, :master] - WebMock.should have_requested(:post, @system_hook.url).with(body: /user_add_to_team/).once - end - - it "project_destroy hook" do - user = create(:user) - project = create(:project) - project.team << [user, :master] - project.users_projects.destroy_all - WebMock.should have_requested(:post, @system_hook.url).with(body: /user_remove_from_team/).once - end - end -end diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index 7221328a45..24384e8bf2 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -2,79 +2,85 @@ # # 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 -# extern_uid :string(255) -# provider :string(255) -# 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 +# 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 +# github_access_token :string(255) +# gitlab_access_token :string(255) +# notification_email :string(255) +# hide_no_password :boolean default(FALSE) +# password_automatically_set :boolean default(FALSE) +# bitbucket_access_token :string(255) +# bitbucket_access_token_secret :string(255) # require 'spec_helper' describe User do describe "Associations" do - it { should have_one(:namespace) } - it { should have_many(:snippets).class_name('Snippet').dependent(:destroy) } - it { should have_many(:users_projects).dependent(:destroy) } - it { should have_many(:groups) } - it { should have_many(:keys).dependent(:destroy) } - it { should have_many(:events).class_name('Event').dependent(:destroy) } - it { should have_many(:recent_events).class_name('Event') } - it { should have_many(:issues).dependent(:destroy) } - it { should have_many(:notes).dependent(:destroy) } - it { should have_many(:assigned_issues).dependent(:destroy) } - it { should have_many(:merge_requests).dependent(:destroy) } - it { should have_many(:assigned_merge_requests).dependent(:destroy) } + it { is_expected.to have_one(:namespace) } + it { is_expected.to have_many(:snippets).class_name('Snippet').dependent(:destroy) } + it { is_expected.to have_many(:project_members).dependent(:destroy) } + it { is_expected.to have_many(:groups) } + it { is_expected.to have_many(:keys).dependent(:destroy) } + it { is_expected.to have_many(:events).class_name('Event').dependent(:destroy) } + it { is_expected.to have_many(:recent_events).class_name('Event') } + it { is_expected.to have_many(:issues).dependent(:destroy) } + it { is_expected.to have_many(:notes).dependent(:destroy) } + it { is_expected.to have_many(:assigned_issues).dependent(:destroy) } + it { is_expected.to have_many(:merge_requests).dependent(:destroy) } + it { is_expected.to have_many(:assigned_merge_requests).dependent(:destroy) } + it { is_expected.to have_many(:identities).dependent(:destroy) } end describe "Mass assignment" do end describe 'validations' do - it { should validate_presence_of(:username) } - it { should validate_presence_of(:projects_limit) } - it { should validate_numericality_of(:projects_limit) } - it { should allow_value(0).for(:projects_limit) } - it { should_not allow_value(-1).for(:projects_limit) } + it { is_expected.to validate_presence_of(:username) } + it { is_expected.to validate_presence_of(:projects_limit) } + it { is_expected.to validate_numericality_of(:projects_limit) } + it { is_expected.to allow_value(0).for(:projects_limit) } + it { is_expected.not_to allow_value(-1).for(:projects_limit) } - it { should ensure_length_of(:bio).is_within(0..255) } + it { is_expected.to ensure_length_of(:bio).is_within(0..255) } describe 'email' do it 'accepts info@example.com' do @@ -110,34 +116,34 @@ describe User do end describe "Respond to" do - it { should respond_to(:is_admin?) } - it { should respond_to(:name) } - it { should respond_to(:private_token) } + it { is_expected.to respond_to(:is_admin?) } + it { is_expected.to respond_to(:name) } + it { is_expected.to respond_to(:private_token) } end describe '#generate_password' do it "should execute callback when force_random_password specified" do user = build(:user, force_random_password: true) - user.should_receive(:generate_password) + expect(user).to receive(:generate_password) user.save end it "should not generate password by default" do user = create(:user, password: 'abcdefghe') - user.password.should == 'abcdefghe' + expect(user.password).to eq('abcdefghe') end it "should generate password when forcing random password" do - Devise.stub(:friendly_token).and_return('123456789') + allow(Devise).to receive(:friendly_token).and_return('123456789') user = create(:user, password: 'abcdefg', force_random_password: true) - user.password.should == '12345678' + expect(user.password).to eq('12345678') end end describe 'authentication token' do it "should have authentication token" do user = create(:user) - user.authentication_token.should_not be_blank + expect(user.authentication_token).not_to be_blank end end @@ -152,15 +158,15 @@ describe User do @project_3.team << [@user, :developer] end - it { @user.authorized_projects.should include(@project) } - it { @user.authorized_projects.should include(@project_2) } - it { @user.authorized_projects.should include(@project_3) } - it { @user.owned_projects.should include(@project) } - it { @user.owned_projects.should_not include(@project_2) } - it { @user.owned_projects.should_not include(@project_3) } - it { @user.personal_projects.should include(@project) } - it { @user.personal_projects.should_not include(@project_2) } - it { @user.personal_projects.should_not include(@project_3) } + it { expect(@user.authorized_projects).to include(@project) } + it { expect(@user.authorized_projects).to include(@project_2) } + it { expect(@user.authorized_projects).to include(@project_3) } + it { expect(@user.owned_projects).to include(@project) } + it { expect(@user.owned_projects).not_to include(@project_2) } + it { expect(@user.owned_projects).not_to include(@project_3) } + it { expect(@user.personal_projects).to include(@project) } + it { expect(@user.personal_projects).not_to include(@project_2) } + it { expect(@user.personal_projects).not_to include(@project_3) } end describe 'groups' do @@ -170,9 +176,9 @@ describe User do @group.add_owner(@user) end - it { @user.several_namespaces?.should be_true } - it { @user.authorized_groups.should == [@group] } - it { @user.owned_groups.should == [@group] } + it { expect(@user.several_namespaces?).to be_truthy } + it { expect(@user.authorized_groups).to eq([@group]) } + it { expect(@user.owned_groups).to eq([@group]) } end describe 'group multiple owners' do @@ -182,10 +188,10 @@ describe User do @group = create :group @group.add_owner(@user) - @group.add_user(@user2, UsersGroup::OWNER) + @group.add_user(@user2, GroupMember::OWNER) end - it { @user2.several_namespaces?.should be_true } + it { expect(@user2.several_namespaces?).to be_truthy } end describe 'namespaced' do @@ -194,7 +200,7 @@ describe User do @project = create :project, namespace: @user.namespace end - it { @user.several_namespaces?.should be_false } + it { expect(@user.several_namespaces?).to be_falsey } end describe 'blocking user' do @@ -202,7 +208,7 @@ describe User do it "should block user" do user.block - user.blocked?.should be_true + expect(user.blocked?).to be_truthy end end @@ -214,10 +220,10 @@ describe User do @blocked = create :user, state: :blocked end - it { User.filter("admins").should == [@admin] } - it { User.filter("blocked").should == [@blocked] } - it { User.filter("wop").should include(@user, @admin, @blocked) } - it { User.filter(nil).should include(@user, @admin) } + it { expect(User.filter("admins")).to eq([@admin]) } + it { expect(User.filter("blocked")).to eq([@blocked]) } + it { expect(User.filter("wop")).to include(@user, @admin, @blocked) } + it { expect(User.filter(nil)).to include(@user, @admin) } end describe :not_in_project do @@ -227,27 +233,27 @@ describe User do @project = create :project end - it { User.not_in_project(@project).should include(@user, @project.owner) } + it { expect(User.not_in_project(@project)).to include(@user, @project.owner) } end describe 'user creation' do describe 'normal user' do let(:user) { create(:user, name: 'John Smith') } - it { user.is_admin?.should be_false } - it { user.require_ssh_key?.should be_true } - it { user.can_create_group?.should be_true } - it { user.can_create_project?.should be_true } - it { user.first_name.should == 'John' } + it { expect(user.is_admin?).to be_falsey } + it { expect(user.require_ssh_key?).to be_truthy } + it { expect(user.can_create_group?).to be_truthy } + it { expect(user.can_create_project?).to be_truthy } + it { expect(user.first_name).to eq('John') } end describe 'with defaults' do let(:user) { User.new } it "should apply defaults to user" do - user.projects_limit.should == Gitlab.config.gitlab.default_projects_limit - user.can_create_group.should == Gitlab.config.gitlab.default_can_create_group - user.theme_id.should == Gitlab.config.gitlab.default_theme + expect(user.projects_limit).to eq(Gitlab.config.gitlab.default_projects_limit) + expect(user.can_create_group).to eq(Gitlab.config.gitlab.default_can_create_group) + expect(user.theme_id).to eq(Gitlab.config.gitlab.default_theme) end end @@ -255,9 +261,9 @@ describe User do let(:user) { User.new(projects_limit: 123, can_create_group: false, can_create_team: true, theme_id: Gitlab::Theme::BASIC) } it "should apply defaults to user" do - user.projects_limit.should == 123 - user.can_create_group.should be_false - user.theme_id.should == Gitlab::Theme::BASIC + expect(user.projects_limit).to eq(123) + expect(user.can_create_group).to be_falsey + expect(user.theme_id).to eq(Gitlab::Theme::BASIC) end end end @@ -267,12 +273,12 @@ describe User do let(:user2) { create(:user, username: 'jameson', email: 'jameson@example.com') } it "should be case insensitive" do - User.search(user1.username.upcase).to_a.should == [user1] - User.search(user1.username.downcase).to_a.should == [user1] - User.search(user2.username.upcase).to_a.should == [user2] - User.search(user2.username.downcase).to_a.should == [user2] - User.search(user1.username.downcase).to_a.count.should == 2 - User.search(user2.username.downcase).to_a.count.should == 1 + expect(User.search(user1.username.upcase).to_a).to eq([user1]) + expect(User.search(user1.username.downcase).to_a).to eq([user1]) + expect(User.search(user2.username.upcase).to_a).to eq([user2]) + expect(User.search(user2.username.downcase).to_a).to eq([user2]) + expect(User.search(user1.username.downcase).to_a.count).to eq(2) + expect(User.search(user2.username.downcase).to_a.count).to eq(1) end end @@ -280,21 +286,35 @@ describe User do let(:user1) { create(:user, username: 'foo') } it "should get the correct user" do - User.by_username_or_id(user1.id).should == user1 - User.by_username_or_id('foo').should == user1 - User.by_username_or_id(-1).should be_nil - User.by_username_or_id('bar').should be_nil + expect(User.by_username_or_id(user1.id)).to eq(user1) + expect(User.by_username_or_id('foo')).to eq(user1) + expect(User.by_username_or_id(-1)).to be_nil + expect(User.by_username_or_id('bar')).to be_nil + end + end + + describe '.by_login' do + let(:username) { 'John' } + let!(:user) { create(:user, username: username) } + + it 'should get the correct user' do + expect(User.by_login(user.email.upcase)).to eq user + expect(User.by_login(user.email)).to eq user + expect(User.by_login(username.downcase)).to eq user + expect(User.by_login(username)).to eq user + expect(User.by_login(nil)).to be_nil + expect(User.by_login('')).to be_nil end end describe 'all_ssh_keys' do - it { should have_many(:keys).dependent(:destroy) } + it { is_expected.to have_many(:keys).dependent(:destroy) } it "should have all ssh keys" do user = create :user key = create :key, key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQD33bWLBxu48Sev9Fert1yzEO4WGcWglWF7K/AwblIUFselOt/QdOL9DSjpQGxLagO1s9wl53STIO8qGS4Ms0EJZyIXOEFMjFJ5xmjSy+S37By4sG7SsltQEHMxtbtFOaW5LV2wCrX+rUsRNqLMamZjgjcPO0/EgGCXIGMAYW4O7cwGZdXWYIhQ1Vwy+CsVMDdPkPgBXqK7nR/ey8KMs8ho5fMNgB5hBw/AL9fNGhRw3QTD6Q12Nkhl4VZES2EsZqlpNnJttnPdp847DUsT6yuLRlfiQfz5Cn9ysHFdXObMN5VYIiPFwHeYCZp1X2S4fDZooRE8uOLTfxWHPXwrhqSH", user_id: user.id - user.all_ssh_keys.should include(key.key) + expect(user.all_ssh_keys).to include(key.key) end end @@ -303,12 +323,12 @@ describe User do it "should be true if avatar is image" do user.update_attribute(:avatar, 'uploads/avatar.png') - user.avatar_type.should be_true + expect(user.avatar_type).to be_truthy end it "should be false if avatar is html page" do user.update_attribute(:avatar, 'uploads/avatar.html') - user.avatar_type.should == ["only images allowed"] + expect(user.avatar_type).to eq(["only images allowed"]) end end @@ -319,7 +339,7 @@ describe User do # Create a condition which would otherwise cause 'true' to be returned user.stub(ldap_user?: true) user.last_credential_check_at = nil - expect(user.requires_ldap_check?).to be_false + expect(user.requires_ldap_check?).to be_falsey end context 'when LDAP is enabled' do @@ -327,7 +347,7 @@ describe User do it 'is false for non-LDAP users' do user.stub(ldap_user?: false) - expect(user.requires_ldap_check?).to be_false + expect(user.requires_ldap_check?).to be_falsey end context 'and when the user is an LDAP user' do @@ -335,17 +355,41 @@ describe User do it 'is true when the user has never had an LDAP check before' do user.last_credential_check_at = nil - expect(user.requires_ldap_check?).to be_true + expect(user.requires_ldap_check?).to be_truthy end it 'is true when the last LDAP check happened over 1 hour ago' do user.last_credential_check_at = 2.hours.ago - expect(user.requires_ldap_check?).to be_true + expect(user.requires_ldap_check?).to be_truthy end end end end + describe :ldap_user? do + it "is true if provider name starts with ldap" do + user = create(:omniauth_user, provider: 'ldapmain') + expect( user.ldap_user? ).to be_truthy + end + + it "is false for other providers" do + user = create(:omniauth_user, provider: 'other-provider') + expect( user.ldap_user? ).to be_falsey + end + + it "is false if no extern_uid is provided" do + user = create(:omniauth_user, extern_uid: nil) + expect( user.ldap_user? ).to be_falsey + end + end + + describe :ldap_identity do + it "returns ldap identity" do + user = create :omniauth_user + expect(user.ldap_identity.provider).not_to be_empty + end + end + describe '#full_website_url' do let(:user) { create(:user) } @@ -396,24 +440,24 @@ describe User do project1 = create :project, :public project2 = create :project, :public - expect(user.starred?(project1)).to be_false - expect(user.starred?(project2)).to be_false + expect(user.starred?(project1)).to be_falsey + expect(user.starred?(project2)).to be_falsey star1 = UsersStarProject.create!(project: project1, user: user) - expect(user.starred?(project1)).to be_true - expect(user.starred?(project2)).to be_false + expect(user.starred?(project1)).to be_truthy + expect(user.starred?(project2)).to be_falsey star2 = UsersStarProject.create!(project: project2, user: user) - expect(user.starred?(project1)).to be_true - expect(user.starred?(project2)).to be_true + expect(user.starred?(project1)).to be_truthy + expect(user.starred?(project2)).to be_truthy star1.destroy - expect(user.starred?(project1)).to be_false - expect(user.starred?(project2)).to be_true + expect(user.starred?(project1)).to be_falsey + expect(user.starred?(project2)).to be_truthy star2.destroy - expect(user.starred?(project1)).to be_false - expect(user.starred?(project2)).to be_false + expect(user.starred?(project1)).to be_falsey + expect(user.starred?(project2)).to be_falsey end end @@ -422,11 +466,67 @@ describe User do user = create :user project = create :project, :public - expect(user.starred?(project)).to be_false + expect(user.starred?(project)).to be_falsey user.toggle_star(project) - expect(user.starred?(project)).to be_true + expect(user.starred?(project)).to be_truthy user.toggle_star(project) - expect(user.starred?(project)).to be_false + expect(user.starred?(project)).to be_falsey + end + end + + describe "#sort" do + before do + User.delete_all + @user = create :user, created_at: Date.today, last_sign_in_at: Date.today, name: 'Alpha' + @user1 = create :user, created_at: Date.today - 1, last_sign_in_at: Date.today - 1, name: 'Omega' + end + + it "sorts users as recently_signed_in" do + expect(User.sort('recent_sign_in').first).to eq(@user) + end + + it "sorts users as late_signed_in" do + expect(User.sort('oldest_sign_in').first).to eq(@user1) + end + + it "sorts users as recently_created" do + expect(User.sort('created_desc').first).to eq(@user) + end + + it "sorts users as late_created" do + expect(User.sort('created_asc').first).to eq(@user1) + end + + it "sorts users by name when nil is passed" do + expect(User.sort(nil).first).to eq(@user) + end + end + + describe "#contributed_projects_ids" do + + subject { create(:user) } + let!(:project1) { create(:project) } + let!(:project2) { create(:project, forked_from_project: project3) } + let!(:project3) { create(:project) } + let!(:merge_request) { create(:merge_request, source_project: project2, target_project: project3, author: subject) } + let!(:push_event) { create(:event, action: Event::PUSHED, project: project1, target: project1, author: subject) } + let!(:merge_event) { create(:event, action: Event::CREATED, project: project3, target: merge_request, author: subject) } + + before do + project1.team << [subject, :master] + project2.team << [subject, :master] + end + + it "includes IDs for projects the user has pushed to" do + expect(subject.contributed_projects_ids).to include(project1.id) + end + + it "includes IDs for projects the user has had merge requests merged into" do + expect(subject.contributed_projects_ids).to include(project3.id) + end + + it "doesn't include IDs for unrelated projects" do + expect(subject.contributed_projects_ids).not_to include(project2.id) end end end diff --git a/spec/models/users_group_spec.rb b/spec/models/users_group_spec.rb deleted file mode 100644 index 0b6f7a0819..0000000000 --- a/spec/models/users_group_spec.rb +++ /dev/null @@ -1,67 +0,0 @@ -# == Schema Information -# -# Table name: users_groups -# -# id :integer not null, primary key -# group_access :integer not null -# group_id :integer not null -# user_id :integer not null -# created_at :datetime -# updated_at :datetime -# notification_level :integer default(3), not null -# - -require 'spec_helper' - -describe UsersGroup do - describe "Associations" do - it { should belong_to(:group) } - it { should belong_to(:user) } - end - - describe "Mass assignment" do - end - - describe "Validation" do - let!(:users_group) { create(:users_group) } - - it { should validate_presence_of(:user_id) } - it { should validate_uniqueness_of(:user_id).scoped_to(:group_id).with_message(/already exists/) } - - it { should validate_presence_of(:group_id) } - it { should ensure_inclusion_of(:group_access).in_array(UsersGroup.group_access_roles.values) } - end - - describe "Delegate methods" do - it { should respond_to(:user_name) } - it { should respond_to(:user_email) } - end - - context 'notification' do - describe "#after_create" do - it "should send email to user" do - membership = build(:users_group) - membership.stub(notification_service: double('NotificationService').as_null_object) - membership.should_receive(:notification_service) - membership.save - end - end - - describe "#after_update" do - before do - @membership = create :users_group - @membership.stub(notification_service: double('NotificationService').as_null_object) - end - - it "should send email to user" do - @membership.should_receive(:notification_service) - @membership.update_attribute(:group_access, UsersGroup::MASTER) - end - - it "does not send an email when the access level has not changed" do - @membership.should_not_receive(:notification_service) - @membership.update_attribute(:group_access, UsersGroup::OWNER) - end - end - end -end diff --git a/spec/models/users_project_spec.rb b/spec/models/users_project_spec.rb deleted file mode 100644 index 3f38164e96..0000000000 --- a/spec/models/users_project_spec.rb +++ /dev/null @@ -1,113 +0,0 @@ -# == Schema Information -# -# Table name: users_projects -# -# id :integer not null, primary key -# user_id :integer not null -# project_id :integer not null -# created_at :datetime -# updated_at :datetime -# project_access :integer default(0), not null -# notification_level :integer default(3), not null -# - -require 'spec_helper' - -describe UsersProject do - describe "Associations" do - it { should belong_to(:project) } - it { should belong_to(:user) } - end - - describe "Mass assignment" do - end - - describe "Validation" do - let!(:users_project) { create(:users_project) } - - it { should validate_presence_of(:user) } - it { should validate_uniqueness_of(:user_id).scoped_to(:project_id).with_message(/already exists/) } - - it { should validate_presence_of(:project) } - it { should ensure_inclusion_of(:project_access).in_array(UsersProject.access_roles.values) } - end - - describe "Delegate methods" do - it { should respond_to(:user_name) } - it { should respond_to(:user_email) } - end - - describe :import_team do - before do - @abilities = Six.new - @abilities << Ability - - @project_1 = create :project - @project_2 = create :project - - @user_1 = create :user - @user_2 = create :user - - @project_1.team << [ @user_1, :developer ] - @project_2.team << [ @user_2, :reporter ] - - @status = @project_2.team.import(@project_1) - end - - it { @status.should be_true } - - describe 'project 2 should get user 1 as developer. user_2 should not be changed' do - it { @project_2.users.should include(@user_1) } - it { @project_2.users.should include(@user_2) } - - it { @abilities.allowed?(@user_1, :write_project, @project_2).should be_true } - it { @abilities.allowed?(@user_2, :read_project, @project_2).should be_true } - end - - describe 'project 1 should not be changed' do - it { @project_1.users.should include(@user_1) } - it { @project_1.users.should_not include(@user_2) } - end - end - - describe :add_users_into_projects do - before do - @project_1 = create :project - @project_2 = create :project - - @user_1 = create :user - @user_2 = create :user - - UsersProject.add_users_into_projects( - [@project_1.id, @project_2.id], - [@user_1.id, @user_2.id], - UsersProject::MASTER - ) - end - - it { @project_1.users.should include(@user_1) } - it { @project_1.users.should include(@user_2) } - - - it { @project_2.users.should include(@user_1) } - it { @project_2.users.should include(@user_2) } - end - - describe :truncate_teams do - before do - @project_1 = create :project - @project_2 = create :project - - @user_1 = create :user - @user_2 = create :user - - @project_1.team << [ @user_1, :developer] - @project_2.team << [ @user_2, :reporter] - - UsersProject.truncate_teams([@project_1.id, @project_2.id]) - end - - it { @project_1.users.should be_empty } - it { @project_2.users.should be_empty } - end -end diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb index cb42822b9b..fceb7668ca 100644 --- a/spec/models/wiki_page_spec.rb +++ b/spec/models/wiki_page_spec.rb @@ -16,27 +16,27 @@ describe WikiPage do end it "sets the slug attribute" do - @wiki_page.slug.should == "test-page" + expect(@wiki_page.slug).to eq("test-page") end it "sets the title attribute" do - @wiki_page.title.should == "test page" + expect(@wiki_page.title).to eq("test page") end it "sets the formatted content attribute" do - @wiki_page.content.should == "test content" + expect(@wiki_page.content).to eq("test content") end it "sets the format attribute" do - @wiki_page.format.should == :markdown + expect(@wiki_page.format).to eq(:markdown) end it "sets the message attribute" do - @wiki_page.message.should == "test commit" + expect(@wiki_page.message).to eq("test commit") end it "sets the version attribute" do - @wiki_page.version.should be_a Commit + expect(@wiki_page.version).to be_a Gollum::Git::Commit end end end @@ -48,12 +48,12 @@ describe WikiPage do it "validates presence of title" do subject.attributes.delete(:title) - subject.valid?.should be_false + expect(subject.valid?).to be_falsey end it "validates presence of content" do subject.attributes.delete(:content) - subject.valid?.should be_false + expect(subject.valid?).to be_falsey end end @@ -69,11 +69,52 @@ describe WikiPage do context "with valid attributes" do it "saves the wiki page" do subject.create(@wiki_attr) - wiki.find_page("Index").should_not be_nil + expect(wiki.find_page("Index")).not_to be_nil end it "returns true" do - subject.create(@wiki_attr).should == true + expect(subject.create(@wiki_attr)).to eq(true) + end + end + end + + describe "dot in the title" do + let(:title) { 'Index v1.2.3' } + + before do + @wiki_attr = {title: title, content: "Home Page", format: "markdown"} + end + + describe "#create" do + after do + destroy_page(title) + end + + context "with valid attributes" do + it "saves the wiki page" do + subject.create(@wiki_attr) + expect(wiki.find_page(title)).not_to be_nil + end + + it "returns true" do + expect(subject.create(@wiki_attr)).to eq(true) + end + end + end + + describe "#update" do + before do + create_page(title, "content") + @page = wiki.find_page(title) + end + + it "updates the content of the page" do + @page.update("new content") + @page = wiki.find_page(title) + end + + it "returns true" do + expect(@page.update("more content")).to be_truthy end end end @@ -95,7 +136,7 @@ describe WikiPage do end it "returns true" do - @page.update("more content").should be_true + expect(@page.update("more content")).to be_truthy end end end @@ -108,11 +149,11 @@ describe WikiPage do it "should delete the page" do @page.delete - wiki.pages.should be_empty + expect(wiki.pages).to be_empty end it "should return true" do - @page.delete.should == true + expect(@page.delete).to eq(true) end end @@ -128,7 +169,7 @@ describe WikiPage do it "returns an array of all commits for the page" do 3.times { |i| @page.update("content #{i}") } - @page.versions.count.should == 4 + expect(@page.versions.count).to eq(4) end end @@ -144,7 +185,7 @@ describe WikiPage do it "should be replace a hyphen to a space" do @page.title = "Import-existing-repositories-into-GitLab" - @page.title.should == "Import existing repositories into GitLab" + expect(@page.title).to eq("Import existing repositories into GitLab") end end diff --git a/spec/requests/api/api_helpers_spec.rb b/spec/requests/api/api_helpers_spec.rb index e2f222c0d3..20cb30a39b 100644 --- a/spec/requests/api/api_helpers_spec.rb +++ b/spec/requests/api/api_helpers_spec.rb @@ -41,32 +41,33 @@ describe API, api: true do describe ".current_user" do it "should return nil for an invalid token" do env[API::APIHelpers::PRIVATE_TOKEN_HEADER] = 'invalid token' - current_user.should be_nil + allow_any_instance_of(self.class).to receive(:doorkeeper_guard){ false } + expect(current_user).to be_nil end it "should return nil for a user without access" do env[API::APIHelpers::PRIVATE_TOKEN_HEADER] = user.private_token Gitlab::UserAccess.stub(allowed?: false) - current_user.should be_nil + expect(current_user).to be_nil end it "should leave user as is when sudo not specified" do env[API::APIHelpers::PRIVATE_TOKEN_HEADER] = user.private_token - current_user.should == user + expect(current_user).to eq(user) clear_env params[API::APIHelpers::PRIVATE_TOKEN_PARAM] = user.private_token - current_user.should == user + expect(current_user).to eq(user) end it "should change current user to sudo when admin" do set_env(admin, user.id) - current_user.should == user + expect(current_user).to eq(user) set_param(admin, user.id) - current_user.should == user + expect(current_user).to eq(user) set_env(admin, user.username) - current_user.should == user + expect(current_user).to eq(user) set_param(admin, user.username) - current_user.should == user + expect(current_user).to eq(user) end it "should throw an error when the current user is not an admin and attempting to sudo" do @@ -82,8 +83,8 @@ describe API, api: true do it "should throw an error when the user cannot be found for a given id" do id = user.id + admin.id - user.id.should_not == id - admin.id.should_not == id + expect(user.id).not_to eq(id) + expect(admin.id).not_to eq(id) set_env(admin, id) expect { current_user }.to raise_error @@ -93,8 +94,8 @@ describe API, api: true do it "should throw an error when the user cannot be found for a given username" do username = "#{user.username}#{admin.username}" - user.username.should_not == username - admin.username.should_not == username + expect(user.username).not_to eq(username) + expect(admin.username).not_to eq(username) set_env(admin, username) expect { current_user }.to raise_error @@ -104,69 +105,69 @@ describe API, api: true do it "should handle sudo's to oneself" do set_env(admin, admin.id) - current_user.should == admin + expect(current_user).to eq(admin) set_param(admin, admin.id) - current_user.should == admin + expect(current_user).to eq(admin) set_env(admin, admin.username) - current_user.should == admin + expect(current_user).to eq(admin) set_param(admin, admin.username) - current_user.should == admin + expect(current_user).to eq(admin) end it "should handle multiple sudo's to oneself" do set_env(admin, user.id) - current_user.should == user - current_user.should == user + expect(current_user).to eq(user) + expect(current_user).to eq(user) set_env(admin, user.username) - current_user.should == user - current_user.should == user + expect(current_user).to eq(user) + expect(current_user).to eq(user) set_param(admin, user.id) - current_user.should == user - current_user.should == user + expect(current_user).to eq(user) + expect(current_user).to eq(user) set_param(admin, user.username) - current_user.should == user - current_user.should == user + expect(current_user).to eq(user) + expect(current_user).to eq(user) end it "should handle multiple sudo's to oneself using string ids" do set_env(admin, user.id.to_s) - current_user.should == user - current_user.should == user + expect(current_user).to eq(user) + expect(current_user).to eq(user) set_param(admin, user.id.to_s) - current_user.should == user - current_user.should == user + expect(current_user).to eq(user) + expect(current_user).to eq(user) end end describe '.sudo_identifier' do it "should return integers when input is an int" do set_env(admin, '123') - sudo_identifier.should == 123 + expect(sudo_identifier).to eq(123) set_env(admin, '0001234567890') - sudo_identifier.should == 1234567890 + expect(sudo_identifier).to eq(1234567890) set_param(admin, '123') - sudo_identifier.should == 123 + expect(sudo_identifier).to eq(123) set_param(admin, '0001234567890') - sudo_identifier.should == 1234567890 + expect(sudo_identifier).to eq(1234567890) end it "should return string when input is an is not an int" do set_env(admin, '12.30') - sudo_identifier.should == "12.30" + expect(sudo_identifier).to eq("12.30") set_env(admin, 'hello') - sudo_identifier.should == 'hello' + expect(sudo_identifier).to eq('hello') set_env(admin, ' 123') - sudo_identifier.should == ' 123' + expect(sudo_identifier).to eq(' 123') set_param(admin, '12.30') - sudo_identifier.should == "12.30" + expect(sudo_identifier).to eq("12.30") set_param(admin, 'hello') - sudo_identifier.should == 'hello' + expect(sudo_identifier).to eq('hello') set_param(admin, ' 123') - sudo_identifier.should == ' 123' + expect(sudo_identifier).to eq(' 123') end end end diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb index f3d7ca2ed2..f40d68b75a 100644 --- a/spec/requests/api/branches_spec.rb +++ b/spec/requests/api/branches_spec.rb @@ -7,108 +7,136 @@ describe API::API, api: true do let(:user) { create(:user) } let(:user2) { create(:user) } let!(:project) { create(:project, creator_id: user.id) } - let!(:master) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) } - let!(:guest) { create(:users_project, user: user2, project: project, project_access: UsersProject::GUEST) } + let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) } + let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) } let!(:branch_name) { 'feature' } let!(:branch_sha) { '0b4bc9a49b562e85de7cc9e834518ea6828729b9' } describe "GET /projects/:id/repository/branches" do it "should return an array of project branches" do get api("/projects/#{project.id}/repository/branches", user) - response.status.should == 200 - json_response.should be_an Array - json_response.first['name'].should == project.repo.heads.sort_by(&:name).first.name + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['name']).to eq(project.repository.branch_names.first) end end describe "GET /projects/:id/repository/branches/:branch" do it "should return the branch information for a single branch" do get api("/projects/#{project.id}/repository/branches/#{branch_name}", user) - response.status.should == 200 + expect(response.status).to eq(200) - json_response['name'].should == branch_name - json_response['commit']['id'].should == branch_sha - json_response['protected'].should == false + expect(json_response['name']).to eq(branch_name) + expect(json_response['commit']['id']).to eq(branch_sha) + expect(json_response['protected']).to eq(false) end it "should return a 403 error if guest" do get api("/projects/#{project.id}/repository/branches", user2) - response.status.should == 403 + expect(response.status).to eq(403) end it "should return a 404 error if branch is not available" do get api("/projects/#{project.id}/repository/branches/unknown", user) - response.status.should == 404 + expect(response.status).to eq(404) end end describe "PUT /projects/:id/repository/branches/:branch/protect" do it "should protect a single branch" do put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user) - response.status.should == 200 + expect(response.status).to eq(200) - json_response['name'].should == branch_name - json_response['commit']['id'].should == branch_sha - json_response['protected'].should == true + expect(json_response['name']).to eq(branch_name) + expect(json_response['commit']['id']).to eq(branch_sha) + expect(json_response['protected']).to eq(true) end it "should return a 404 error if branch not found" do put api("/projects/#{project.id}/repository/branches/unknown/protect", user) - response.status.should == 404 + expect(response.status).to eq(404) end it "should return a 403 error if guest" do put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user2) - response.status.should == 403 + expect(response.status).to eq(403) end it "should return success when protect branch again" do put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user) put api("/projects/#{project.id}/repository/branches/#{branch_name}/protect", user) - response.status.should == 200 + expect(response.status).to eq(200) end end describe "PUT /projects/:id/repository/branches/:branch/unprotect" do it "should unprotect a single branch" do put api("/projects/#{project.id}/repository/branches/#{branch_name}/unprotect", user) - response.status.should == 200 + expect(response.status).to eq(200) - json_response['name'].should == branch_name - json_response['commit']['id'].should == branch_sha - json_response['protected'].should == false + expect(json_response['name']).to eq(branch_name) + expect(json_response['commit']['id']).to eq(branch_sha) + expect(json_response['protected']).to eq(false) end it "should return success when unprotect branch" do put api("/projects/#{project.id}/repository/branches/unknown/unprotect", user) - response.status.should == 404 + expect(response.status).to eq(404) end it "should return success when unprotect branch again" do put api("/projects/#{project.id}/repository/branches/#{branch_name}/unprotect", user) put api("/projects/#{project.id}/repository/branches/#{branch_name}/unprotect", user) - response.status.should == 200 + expect(response.status).to eq(200) end end describe "POST /projects/:id/repository/branches" do it "should create a new branch" do post api("/projects/#{project.id}/repository/branches", user), - branch_name: branch_name, - ref: branch_sha + branch_name: 'feature1', + ref: branch_sha - response.status.should == 201 + expect(response.status).to eq(201) - json_response['name'].should == branch_name - json_response['commit']['id'].should == branch_sha + expect(json_response['name']).to eq('feature1') + expect(json_response['commit']['id']).to eq(branch_sha) end it "should deny for user without push access" do post api("/projects/#{project.id}/repository/branches", user2), - branch_name: branch_name, - ref: branch_sha + branch_name: branch_name, + ref: branch_sha + expect(response.status).to eq(403) + end - response.status.should == 403 + it 'should return 400 if branch name is invalid' do + post api("/projects/#{project.id}/repository/branches", user), + branch_name: 'new design', + ref: branch_sha + expect(response.status).to eq(400) + expect(json_response['message']).to eq('Branch name invalid') + end + + it 'should return 400 if branch already exists' do + post api("/projects/#{project.id}/repository/branches", user), + branch_name: 'new_design1', + ref: branch_sha + expect(response.status).to eq(201) + + post api("/projects/#{project.id}/repository/branches", user), + branch_name: 'new_design1', + ref: branch_sha + expect(response.status).to eq(400) + expect(json_response['message']).to eq('Branch already exists') + end + + it 'should return 400 if ref name is invalid' do + post api("/projects/#{project.id}/repository/branches", user), + branch_name: 'new_design3', + ref: 'foo' + expect(response.status).to eq(400) + expect(json_response['message']).to eq('Invalid reference name') end end @@ -117,20 +145,26 @@ describe API::API, api: true do it "should remove branch" do delete api("/projects/#{project.id}/repository/branches/#{branch_name}", user) - response.status.should == 200 + expect(response.status).to eq(200) + expect(json_response['branch_name']).to eq(branch_name) + end + + it 'should return 404 if branch not exists' do + delete api("/projects/#{project.id}/repository/branches/foobar", user) + expect(response.status).to eq(404) end it "should remove protected branch" do project.protected_branches.create(name: branch_name) delete api("/projects/#{project.id}/repository/branches/#{branch_name}", user) - response.status.should == 405 - json_response['message'].should == 'Protected branch cant be removed' + expect(response.status).to eq(405) + expect(json_response['message']).to eq('Protected branch cant be removed') end it "should not remove HEAD branch" do delete api("/projects/#{project.id}/repository/branches/master", user) - response.status.should == 405 - json_response['message'].should == 'Cannot remove HEAD branch' + expect(response.status).to eq(405) + expect(json_response['message']).to eq('Cannot remove HEAD branch') end end end diff --git a/spec/requests/api/commits_spec.rb b/spec/requests/api/commits_spec.rb index b56269d275..9ea60e1a4a 100644 --- a/spec/requests/api/commits_spec.rb +++ b/spec/requests/api/commits_spec.rb @@ -6,8 +6,9 @@ describe API::API, api: true do let(:user) { create(:user) } let(:user2) { create(:user) } let!(:project) { create(:project, creator_id: user.id) } - let!(:master) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) } - let!(:guest) { create(:users_project, user: user2, project: project, project_access: UsersProject::GUEST) } + let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) } + let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) } + let!(:note) { create(:note_on_commit, author: user, project: project, commit_id: project.repository.commit.id, note: 'a comment on a commit') } before { project.team << [user, :reporter] } @@ -17,17 +18,17 @@ describe API::API, api: true do it "should return project commits" do get api("/projects/#{project.id}/repository/commits", user) - response.status.should == 200 + expect(response.status).to eq(200) - json_response.should be_an Array - json_response.first['id'].should == project.repository.commit.id + expect(json_response).to be_an Array + expect(json_response.first['id']).to eq(project.repository.commit.id) end end context "unauthorized user" do it "should not return project commits" do get api("/projects/#{project.id}/repository/commits") - response.status.should == 401 + expect(response.status).to eq(401) end end end @@ -36,21 +37,21 @@ describe API::API, api: true do context "authorized user" do it "should return a commit by sha" do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}", user) - response.status.should == 200 - json_response['id'].should == project.repository.commit.id - json_response['title'].should == project.repository.commit.title + expect(response.status).to eq(200) + expect(json_response['id']).to eq(project.repository.commit.id) + expect(json_response['title']).to eq(project.repository.commit.title) end it "should return a 404 error if not found" do get api("/projects/#{project.id}/repository/commits/invalid_sha", user) - response.status.should == 404 + expect(response.status).to eq(404) end end context "unauthorized user" do it "should not return the selected commit" do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}") - response.status.should == 401 + expect(response.status).to eq(401) end end end @@ -61,23 +62,87 @@ describe API::API, api: true do it "should return the diff of the selected commit" do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff", user) - response.status.should == 200 + expect(response.status).to eq(200) - json_response.should be_an Array - json_response.length.should >= 1 - json_response.first.keys.should include "diff" + expect(json_response).to be_an Array + expect(json_response.length).to be >= 1 + expect(json_response.first.keys).to include "diff" end it "should return a 404 error if invalid commit" do get api("/projects/#{project.id}/repository/commits/invalid_sha/diff", user) - response.status.should == 404 + expect(response.status).to eq(404) end end context "unauthorized user" do it "should not return the diff of the selected commit" do get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/diff") - response.status.should == 401 + expect(response.status).to eq(401) + end + end + end + + describe 'GET /projects:id/repository/commits/:sha/comments' do + context 'authorized user' do + it 'should return merge_request comments' do + get api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['note']).to eq('a comment on a commit') + expect(json_response.first['author']['id']).to eq(user.id) + end + + it 'should return a 404 error if merge_request_id not found' do + get api("/projects/#{project.id}/repository/commits/1234ab/comments", user) + expect(response.status).to eq(404) + end + end + + context 'unauthorized user' do + it 'should not return the diff of the selected commit' do + get api("/projects/#{project.id}/repository/commits/1234ab/comments") + expect(response.status).to eq(401) + end + end + end + + describe 'POST /projects:id/repository/commits/:sha/comments' do + context 'authorized user' do + it 'should return comment' do + post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment' + expect(response.status).to eq(201) + expect(json_response['note']).to eq('My comment') + expect(json_response['path']).to be_nil + expect(json_response['line']).to be_nil + expect(json_response['line_type']).to be_nil + end + + it 'should return the inline comment' do + post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user), note: 'My comment', path: project.repository.commit.diffs.first.new_path, line: 7, line_type: 'new' + expect(response.status).to eq(201) + expect(json_response['note']).to eq('My comment') + expect(json_response['path']).to eq(project.repository.commit.diffs.first.new_path) + expect(json_response['line']).to eq(7) + expect(json_response['line_type']).to eq('new') + end + + it 'should return 400 if note is missing' do + post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments", user) + expect(response.status).to eq(400) + end + + it 'should return 404 if note is attached to non existent commit' do + post api("/projects/#{project.id}/repository/commits/1234ab/comments", user), note: 'My comment' + expect(response.status).to eq(404) + end + end + + context 'unauthorized user' do + it 'should not return the diff of the selected commit' do + post api("/projects/#{project.id}/repository/commits/#{project.repository.commit.id}/comments") + expect(response.status).to eq(401) end end end diff --git a/spec/requests/api/doorkeeper_access_spec.rb b/spec/requests/api/doorkeeper_access_spec.rb new file mode 100644 index 0000000000..39949a9042 --- /dev/null +++ b/spec/requests/api/doorkeeper_access_spec.rb @@ -0,0 +1,31 @@ +require 'spec_helper' + +describe API::API, api: true do + include ApiHelpers + + let!(:user) { create(:user) } + let!(:application) { Doorkeeper::Application.create!(:name => "MyApp", :redirect_uri => "https://app.com", :owner => user) } + let!(:token) { Doorkeeper::AccessToken.create! :application_id => application.id, :resource_owner_id => user.id } + + + describe "when unauthenticated" do + it "returns authentication success" do + get api("/user"), :access_token => token.token + expect(response.status).to eq(200) + end + end + + describe "when token invalid" do + it "returns authentication error" do + get api("/user"), :access_token => "123a" + expect(response.status).to eq(401) + end + end + + describe "authorization by private token" do + it "returns authentication success" do + get api("/user", user) + expect(response.status).to eq(200) + end + end +end diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb index b43a202aec..bab8888a63 100644 --- a/spec/requests/api/files_spec.rb +++ b/spec/requests/api/files_spec.rb @@ -16,15 +16,15 @@ describe API::API, api: true do } get api("/projects/#{project.id}/repository/files", user), params - response.status.should == 200 - json_response['file_path'].should == file_path - json_response['file_name'].should == 'popen.rb' - Base64.decode64(json_response['content']).lines.first.should == "require 'fileutils'\n" + expect(response.status).to eq(200) + expect(json_response['file_path']).to eq(file_path) + expect(json_response['file_name']).to eq('popen.rb') + expect(Base64.decode64(json_response['content']).lines.first).to eq("require 'fileutils'\n") end it "should return a 400 bad request if no params given" do get api("/projects/#{project.id}/repository/files", user) - response.status.should == 400 + expect(response.status).to eq(400) end it "should return a 404 if such file does not exist" do @@ -34,7 +34,7 @@ describe API::API, api: true do } get api("/projects/#{project.id}/repository/files", user), params - response.status.should == 404 + expect(response.status).to eq(404) end end @@ -54,13 +54,13 @@ describe API::API, api: true do ) post api("/projects/#{project.id}/repository/files", user), valid_params - response.status.should == 201 - json_response['file_path'].should == 'newfile.rb' + expect(response.status).to eq(201) + expect(json_response['file_path']).to eq('newfile.rb') end it "should return a 400 bad request if no params given" do post api("/projects/#{project.id}/repository/files", user) - response.status.should == 400 + expect(response.status).to eq(400) end it "should return a 400 if satellite fails to create file" do @@ -69,7 +69,7 @@ describe API::API, api: true do ) post api("/projects/#{project.id}/repository/files", user), valid_params - response.status.should == 400 + expect(response.status).to eq(400) end end @@ -89,22 +89,42 @@ describe API::API, api: true do ) put api("/projects/#{project.id}/repository/files", user), valid_params - response.status.should == 200 - json_response['file_path'].should == file_path + expect(response.status).to eq(200) + expect(json_response['file_path']).to eq(file_path) end it "should return a 400 bad request if no params given" do put api("/projects/#{project.id}/repository/files", user) - response.status.should == 400 + expect(response.status).to eq(400) end - it "should return a 400 if satellite fails to create file" do - Gitlab::Satellite::EditFileAction.any_instance.stub( - commit!: false, - ) + it 'should return a 400 if the checkout fails' do + Gitlab::Satellite::EditFileAction.any_instance.stub(:commit!) + .and_raise(Gitlab::Satellite::CheckoutFailed) put api("/projects/#{project.id}/repository/files", user), valid_params - response.status.should == 400 + expect(response.status).to eq(400) + + ref = valid_params[:branch_name] + expect(response.body).to match("ref '#{ref}' could not be checked out") + end + + it 'should return a 409 if the file was not modified' do + Gitlab::Satellite::EditFileAction.any_instance.stub(:commit!) + .and_raise(Gitlab::Satellite::CommitFailed) + + put api("/projects/#{project.id}/repository/files", user), valid_params + expect(response.status).to eq(409) + expect(response.body).to match("Maybe there was nothing to commit?") + end + + it 'should return a 409 if the push fails' do + Gitlab::Satellite::EditFileAction.any_instance.stub(:commit!) + .and_raise(Gitlab::Satellite::PushFailed) + + put api("/projects/#{project.id}/repository/files", user), valid_params + expect(response.status).to eq(409) + expect(response.body).to match("Maybe the file was changed by another process?") end end @@ -123,13 +143,13 @@ describe API::API, api: true do ) delete api("/projects/#{project.id}/repository/files", user), valid_params - response.status.should == 200 - json_response['file_path'].should == file_path + expect(response.status).to eq(200) + expect(json_response['file_path']).to eq(file_path) end it "should return a 400 bad request if no params given" do delete api("/projects/#{project.id}/repository/files", user) - response.status.should == 400 + expect(response.status).to eq(400) end it "should return a 400 if satellite fails to create file" do @@ -138,7 +158,7 @@ describe API::API, api: true do ) delete api("/projects/#{project.id}/repository/files", user), valid_params - response.status.should == 400 + expect(response.status).to eq(400) end end end diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb new file mode 100644 index 0000000000..fb3ff552c8 --- /dev/null +++ b/spec/requests/api/fork_spec.rb @@ -0,0 +1,73 @@ +require 'spec_helper' + +describe API::API, api: true do + include ApiHelpers + let(:user) { create(:user) } + let(:user2) { create(:user) } + let(:user3) { create(:user) } + let(:admin) { create(:admin) } + let(:project) { + create(:project, creator_id: user.id, + namespace: user.namespace) + } + let(:project_user2) { + create(:project_member, user: user2, + project: project, + access_level: ProjectMember::GUEST) + } + + describe 'POST /projects/fork/:id' do + before { project_user2 } + before { user3 } + + context 'when authenticated' do + it 'should fork if user has sufficient access to project' do + post api("/projects/fork/#{project.id}", user2) + expect(response.status).to eq(201) + expect(json_response['name']).to eq(project.name) + expect(json_response['path']).to eq(project.path) + expect(json_response['owner']['id']).to eq(user2.id) + expect(json_response['namespace']['id']).to eq(user2.namespace.id) + expect(json_response['forked_from_project']['id']).to eq(project.id) + end + + it 'should fork if user is admin' do + post api("/projects/fork/#{project.id}", admin) + expect(response.status).to eq(201) + expect(json_response['name']).to eq(project.name) + expect(json_response['path']).to eq(project.path) + expect(json_response['owner']['id']).to eq(admin.id) + expect(json_response['namespace']['id']).to eq(admin.namespace.id) + expect(json_response['forked_from_project']['id']).to eq(project.id) + end + + it 'should fail on missing project access for the project to fork' do + post api("/projects/fork/#{project.id}", user3) + expect(response.status).to eq(404) + expect(json_response['message']).to eq('404 Project Not Found') + end + + it 'should fail if forked project exists in the user namespace' do + post api("/projects/fork/#{project.id}", user) + expect(response.status).to eq(409) + expect(json_response['message']['base']).to eq(['Invalid fork destination']) + expect(json_response['message']['name']).to eq(['has already been taken']) + expect(json_response['message']['path']).to eq(['has already been taken']) + end + + it 'should fail if project to fork from does not exist' do + post api('/projects/fork/424242', user) + expect(response.status).to eq(404) + expect(json_response['message']).to eq('404 Project Not Found') + end + end + + context 'when unauthenticated' do + it 'should return authentication error' do + post api("/projects/fork/#{project.id}") + expect(response.status).to eq(401) + expect(json_response['message']).to eq('401 Unauthorized') + end + end + end +end diff --git a/spec/requests/api/group_members_spec.rb b/spec/requests/api/group_members_spec.rb new file mode 100644 index 0000000000..8ba6876a95 --- /dev/null +++ b/spec/requests/api/group_members_spec.rb @@ -0,0 +1,199 @@ +require 'spec_helper' + +describe API::API, api: true do + include ApiHelpers + + let(:owner) { create(:user) } + let(:reporter) { create(:user) } + let(:developer) { create(:user) } + let(:master) { create(:user) } + let(:guest) { create(:user) } + let(:stranger) { create(:user) } + + let!(:group_with_members) do + group = create(:group) + group.add_users([reporter.id], GroupMember::REPORTER) + group.add_users([developer.id], GroupMember::DEVELOPER) + group.add_users([master.id], GroupMember::MASTER) + group.add_users([guest.id], GroupMember::GUEST) + group + end + + let!(:group_no_members) { create(:group) } + + before do + group_with_members.add_owner owner + group_no_members.add_owner owner + end + + describe "GET /groups/:id/members" do + context "when authenticated as user that is part or the group" do + it "each user: should return an array of members groups of group3" do + [owner, master, developer, reporter, guest].each do |user| + get api("/groups/#{group_with_members.id}/members", user) + 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) + end + end + + it "users not part of the group should get access error" do + get api("/groups/#{group_with_members.id}/members", stranger) + expect(response.status).to eq(403) + end + end + end + + describe "POST /groups/:id/members" do + context "when not a member of the group" do + it "should not add guest as member of group_no_members when adding being done by person outside the group" do + post api("/groups/#{group_no_members.id}/members", reporter), user_id: guest.id, access_level: GroupMember::MASTER + expect(response.status).to eq(403) + end + end + + context "when a member of the group" do + it "should return ok and add new member" do + new_user = create(:user) + + expect { + post api("/groups/#{group_no_members.id}/members", owner), + user_id: new_user.id, access_level: GroupMember::MASTER + }.to change { group_no_members.members.count }.by(1) + + expect(response.status).to eq(201) + expect(json_response['name']).to eq(new_user.name) + expect(json_response['access_level']).to eq(GroupMember::MASTER) + end + + it "should not allow guest to modify group members" do + new_user = create(:user) + + expect { + post api("/groups/#{group_with_members.id}/members", guest), + user_id: new_user.id, access_level: GroupMember::MASTER + }.not_to change { group_with_members.members.count } + + expect(response.status).to eq(403) + end + + it "should return error if member already exists" do + post api("/groups/#{group_with_members.id}/members", owner), user_id: master.id, access_level: GroupMember::MASTER + expect(response.status).to eq(409) + end + + it "should return a 400 error when user id is not given" do + post api("/groups/#{group_no_members.id}/members", owner), access_level: GroupMember::MASTER + expect(response.status).to eq(400) + end + + it "should return a 400 error when access level is not given" do + post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id + expect(response.status).to eq(400) + end + + it "should return a 422 error when access level is not known" do + post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id, access_level: 1234 + expect(response.status).to eq(422) + end + end + end + + describe 'PUT /groups/:id/members/:user_id' do + context 'when not a member of the group' do + it 'should return a 409 error if the user is not a group member' do + put( + api("/groups/#{group_no_members.id}/members/#{developer.id}", + owner), access_level: GroupMember::MASTER + ) + expect(response.status).to eq(404) + end + end + + context 'when a member of the group' do + it 'should return ok and update member access level' do + put( + api("/groups/#{group_with_members.id}/members/#{reporter.id}", + owner), + access_level: GroupMember::MASTER + ) + + expect(response.status).to eq(200) + + get api("/groups/#{group_with_members.id}/members", owner) + json_reporter = json_response.find do |e| + e['id'] == reporter.id + end + + expect(json_reporter['access_level']).to eq(GroupMember::MASTER) + end + + it 'should not allow guest to modify group members' do + put( + api("/groups/#{group_with_members.id}/members/#{developer.id}", + guest), + access_level: GroupMember::MASTER + ) + + expect(response.status).to eq(403) + + get api("/groups/#{group_with_members.id}/members", owner) + json_developer = json_response.find do |e| + e['id'] == developer.id + end + + expect(json_developer['access_level']).to eq(GroupMember::DEVELOPER) + end + + it 'should return a 400 error when access level is not given' do + put( + api("/groups/#{group_with_members.id}/members/#{master.id}", owner) + ) + expect(response.status).to eq(400) + end + + it 'should return a 422 error when access level is not known' do + put( + api("/groups/#{group_with_members.id}/members/#{master.id}", owner), + access_level: 1234 + ) + expect(response.status).to eq(422) + end + end + end + + describe "DELETE /groups/:id/members/:user_id" do + context "when not a member of the group" do + it "should not delete guest's membership of group_with_members" do + random_user = create(:user) + delete api("/groups/#{group_with_members.id}/members/#{owner.id}", random_user) + expect(response.status).to eq(403) + end + end + + context "when a member of the group" do + it "should delete guest's membership of group" do + expect { + delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner) + }.to change { group_with_members.members.count }.by(-1) + + expect(response.status).to eq(200) + end + + it "should return a 404 error when user id is not known" do + delete api("/groups/#{group_with_members.id}/members/1328", owner) + expect(response.status).to eq(404) + end + + it "should not allow guest to modify group members" do + delete api("/groups/#{group_with_members.id}/members/#{master.id}", guest) + expect(response.status).to eq(403) + end + end + end +end diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb index f27a60e4bc..d963dbac9f 100644 --- a/spec/requests/api/groups_spec.rb +++ b/spec/requests/api/groups_spec.rb @@ -18,26 +18,26 @@ describe API::API, api: true do context "when unauthenticated" do it "should return authentication error" do get api("/groups") - response.status.should == 401 + expect(response.status).to eq(401) end end context "when authenticated as user" do it "normal user: should return an array of groups of user1" do get api("/groups", user1) - response.status.should == 200 - json_response.should be_an Array - json_response.length.should == 1 - json_response.first['name'].should == group1.name + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['name']).to eq(group1.name) end end context "when authenticated as admin" do it "admin: should return an array of all groups" do get api("/groups", admin) - response.status.should == 200 - json_response.should be_an Array - json_response.length.should == 2 + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(2) end end end @@ -46,31 +46,49 @@ describe API::API, api: true do context "when authenticated as user" do it "should return one of user1's groups" do get api("/groups/#{group1.id}", user1) - response.status.should == 200 + expect(response.status).to eq(200) json_response['name'] == group1.name end it "should not return a non existing group" do get api("/groups/1328", user1) - response.status.should == 404 + expect(response.status).to eq(404) end it "should not return a group not attached to user1" do get api("/groups/#{group2.id}", user1) - response.status.should == 403 + expect(response.status).to eq(403) end end context "when authenticated as admin" do it "should return any existing group" do get api("/groups/#{group2.id}", admin) - response.status.should == 200 + expect(response.status).to eq(200) json_response['name'] == group2.name end it "should not return a non existing group" do get api("/groups/1328", admin) - response.status.should == 404 + expect(response.status).to eq(404) + end + end + + context 'when using group path in URL' do + it 'should return any existing group' do + get api("/groups/#{group1.path}", admin) + expect(response.status).to eq(200) + json_response['name'] == group2.name + end + + it 'should not return a non existing group' do + get api('/groups/unknown', admin) + expect(response.status).to eq(404) + end + + it 'should not return a group not attached to user1' do + get api("/groups/#{group2.path}", user1) + expect(response.status).to eq(403) end end end @@ -79,29 +97,30 @@ describe API::API, api: true do context "when authenticated as user" do it "should not create group" do post api("/groups", user1), attributes_for(:group) - response.status.should == 403 + expect(response.status).to eq(403) end end context "when authenticated as admin" do it "should create group" do post api("/groups", admin), attributes_for(:group) - response.status.should == 201 + expect(response.status).to eq(201) end it "should not create group, duplicate" do post api("/groups", admin), {name: "Duplicate Test", path: group2.path} - response.status.should == 404 + expect(response.status).to eq(400) + expect(response.message).to eq("Bad Request") end it "should return 400 bad request error if name not given" do post api("/groups", admin), {path: group2.path} - response.status.should == 400 + expect(response.status).to eq(400) end it "should return 400 bad request error if path not given" do post api("/groups", admin), { name: 'test' } - response.status.should == 400 + expect(response.status).to eq(400) end end end @@ -110,36 +129,36 @@ describe API::API, api: true do context "when authenticated as user" do it "should remove group" do delete api("/groups/#{group1.id}", user1) - response.status.should == 200 + expect(response.status).to eq(200) end it "should not remove a group if not an owner" do user3 = create(:user) group1.add_user(user3, Gitlab::Access::MASTER) delete api("/groups/#{group1.id}", user3) - response.status.should == 403 + expect(response.status).to eq(403) end it "should not remove a non existing group" do delete api("/groups/1328", user1) - response.status.should == 404 + expect(response.status).to eq(404) end it "should not remove a group not attached to user1" do delete api("/groups/#{group2.id}", user1) - response.status.should == 403 + expect(response.status).to eq(403) end end context "when authenticated as admin" do it "should remove any existing group" do delete api("/groups/#{group2.id}", admin) - response.status.should == 200 + expect(response.status).to eq(200) end it "should not remove a non existing group" do delete api("/groups/1328", admin) - response.status.should == 404 + expect(response.status).to eq(404) end end end @@ -148,130 +167,20 @@ describe API::API, api: true do let(:project) { create(:project) } before(:each) do Projects::TransferService.any_instance.stub(execute: true) - Project.stub(:find).and_return(project) + allow(Project).to receive(:find).and_return(project) end context "when authenticated as user" do it "should not transfer project to group" do post api("/groups/#{group1.id}/projects/#{project.id}", user2) - response.status.should == 403 + expect(response.status).to eq(403) end end context "when authenticated as admin" do it "should transfer project to group" do post api("/groups/#{group1.id}/projects/#{project.id}", admin) - response.status.should == 201 - end - end - end - - describe "members" do - let(:owner) { create(:user) } - let(:reporter) { create(:user) } - let(:developer) { create(:user) } - let(:master) { create(:user) } - let(:guest) { create(:user) } - let!(:group_with_members) do - group = create(:group) - group.add_users([reporter.id], UsersGroup::REPORTER) - group.add_users([developer.id], UsersGroup::DEVELOPER) - group.add_users([master.id], UsersGroup::MASTER) - group.add_users([guest.id], UsersGroup::GUEST) - group - end - let!(:group_no_members) { create(:group) } - - before do - group_with_members.add_owner owner - group_no_members.add_owner owner - end - - describe "GET /groups/:id/members" do - context "when authenticated as user that is part or the group" do - it "each user: should return an array of members groups of group3" do - [owner, master, developer, reporter, guest].each do |user| - get api("/groups/#{group_with_members.id}/members", user) - response.status.should == 200 - json_response.should be_an Array - json_response.size.should == 5 - json_response.find { |e| e['id']==owner.id }['access_level'].should == UsersGroup::OWNER - json_response.find { |e| e['id']==reporter.id }['access_level'].should == UsersGroup::REPORTER - json_response.find { |e| e['id']==developer.id }['access_level'].should == UsersGroup::DEVELOPER - json_response.find { |e| e['id']==master.id }['access_level'].should == UsersGroup::MASTER - json_response.find { |e| e['id']==guest.id }['access_level'].should == UsersGroup::GUEST - end - end - - it "users not part of the group should get access error" do - get api("/groups/#{group_with_members.id}/members", user1) - response.status.should == 403 - end - end - end - - describe "POST /groups/:id/members" do - context "when not a member of the group" do - it "should not add guest as member of group_no_members when adding being done by person outside the group" do - post api("/groups/#{group_no_members.id}/members", reporter), user_id: guest.id, access_level: UsersGroup::MASTER - response.status.should == 403 - end - end - - context "when a member of the group" do - it "should return ok and add new member" do - count_before=group_no_members.users_groups.count - new_user = create(:user) - post api("/groups/#{group_no_members.id}/members", owner), user_id: new_user.id, access_level: UsersGroup::MASTER - response.status.should == 201 - json_response['name'].should == new_user.name - json_response['access_level'].should == UsersGroup::MASTER - group_no_members.users_groups.count.should == count_before + 1 - end - - it "should return error if member already exists" do - post api("/groups/#{group_with_members.id}/members", owner), user_id: master.id, access_level: UsersGroup::MASTER - response.status.should == 409 - end - - it "should return a 400 error when user id is not given" do - post api("/groups/#{group_no_members.id}/members", owner), access_level: UsersGroup::MASTER - response.status.should == 400 - end - - it "should return a 400 error when access level is not given" do - post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id - response.status.should == 400 - end - - it "should return a 422 error when access level is not known" do - post api("/groups/#{group_no_members.id}/members", owner), user_id: master.id, access_level: 1234 - response.status.should == 422 - end - end - end - - describe "DELETE /groups/:id/members/:user_id" do - context "when not a member of the group" do - it "should not delete guest's membership of group_with_members" do - random_user = create(:user) - delete api("/groups/#{group_with_members.id}/members/#{owner.id}", random_user) - response.status.should == 403 - end - end - - context "when a member of the group" do - it "should delete guest's membership of group" do - count_before=group_with_members.users_groups.count - delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner) - response.status.should == 200 - group_with_members.users_groups.count.should == count_before - 1 - end - - it "should return a 404 error when user id is not known" do - delete api("/groups/#{group_with_members.id}/members/1328", owner) - response.status.should == 404 - end + expect(response.status).to eq(201) end end end diff --git a/spec/requests/api/internal_spec.rb b/spec/requests/api/internal_spec.rb index dbe8043c63..4c7d15d659 100644 --- a/spec/requests/api/internal_spec.rb +++ b/spec/requests/api/internal_spec.rb @@ -5,27 +5,50 @@ describe API::API, api: true do let(:user) { create(:user) } let(:key) { create(:key, user: user) } let(:project) { create(:project) } + let(:secret_token) { File.read Rails.root.join('.gitlab_shell_secret') } describe "GET /internal/check", no_db: true do it do - get api("/internal/check") + get api("/internal/check"), secret_token: secret_token - response.status.should == 200 - json_response['api_version'].should == API::API.version + expect(response.status).to eq(200) + expect(json_response['api_version']).to eq(API::API.version) + end + end + + describe "GET /internal/broadcast_message" do + context "broadcast message exists" do + let!(:broadcast_message) { create(:broadcast_message, starts_at: Time.now.yesterday, ends_at: Time.now.tomorrow ) } + + it do + get api("/internal/broadcast_message"), secret_token: secret_token + + expect(response.status).to eq(200) + expect(json_response["message"]).to eq(broadcast_message.message) + end + end + + context "broadcast message doesn't exist" do + it do + get api("/internal/broadcast_message"), secret_token: secret_token + + expect(response.status).to eq(200) + expect(json_response).to be_empty + end end end describe "GET /internal/discover" do it do - get(api("/internal/discover"), key_id: key.id) + get(api("/internal/discover"), key_id: key.id, secret_token: secret_token) - response.status.should == 200 + expect(response.status).to eq(200) - json_response['name'].should == user.name + expect(json_response['name']).to eq(user.name) end end - describe "GET /internal/allowed" do + describe "POST /internal/allowed" do context "access granted" do before do project.team << [user, :developer] @@ -35,8 +58,8 @@ describe API::API, api: true do it do pull(key, project) - response.status.should == 200 - response.body.should == 'true' + expect(response.status).to eq(200) + expect(json_response["status"]).to be_truthy end end @@ -44,8 +67,8 @@ describe API::API, api: true do it do push(key, project) - response.status.should == 200 - response.body.should == 'true' + expect(response.status).to eq(200) + expect(json_response["status"]).to be_truthy end end end @@ -59,8 +82,8 @@ describe API::API, api: true do it do pull(key, project) - response.status.should == 200 - response.body.should == 'false' + expect(response.status).to eq(200) + expect(json_response["status"]).to be_falsey end end @@ -68,8 +91,8 @@ describe API::API, api: true do it do push(key, project) - response.status.should == 200 - response.body.should == 'false' + expect(response.status).to eq(200) + expect(json_response["status"]).to be_falsey end end end @@ -85,8 +108,8 @@ describe API::API, api: true do it do pull(key, personal_project) - response.status.should == 200 - response.body.should == 'false' + expect(response.status).to eq(200) + expect(json_response["status"]).to be_falsey end end @@ -94,8 +117,8 @@ describe API::API, api: true do it do push(key, personal_project) - response.status.should == 200 - response.body.should == 'false' + expect(response.status).to eq(200) + expect(json_response["status"]).to be_falsey end end end @@ -112,8 +135,8 @@ describe API::API, api: true do it do pull(key, project) - response.status.should == 200 - response.body.should == 'true' + expect(response.status).to eq(200) + expect(json_response["status"]).to be_truthy end end @@ -121,8 +144,8 @@ describe API::API, api: true do it do push(key, project) - response.status.should == 200 - response.body.should == 'false' + expect(response.status).to eq(200) + expect(json_response["status"]).to be_falsey end end end @@ -138,8 +161,8 @@ describe API::API, api: true do it do archive(key, project) - response.status.should == 200 - response.body.should == 'true' + expect(response.status).to eq(200) + expect(json_response["status"]).to be_truthy end end @@ -147,40 +170,60 @@ describe API::API, api: true do it do archive(key, project) - response.status.should == 200 - response.body.should == 'false' + expect(response.status).to eq(200) + expect(json_response["status"]).to be_falsey end end end + + context 'project does not exist' do + it do + pull(key, OpenStruct.new(path_with_namespace: 'gitlab/notexists')) + + expect(response.status).to eq(200) + expect(json_response["status"]).to be_falsey + end + end + + context 'user does not exist' do + it do + pull(OpenStruct.new(id: 0), project) + + expect(response.status).to eq(200) + expect(json_response["status"]).to be_falsey + end + end end def pull(key, project) - get( + post( api("/internal/allowed"), - ref: 'master', key_id: key.id, project: project.path_with_namespace, - action: 'git-upload-pack' + action: 'git-upload-pack', + secret_token: secret_token ) end def push(key, project) - get( + post( api("/internal/allowed"), - ref: 'master', + changes: 'd14d6c0abdd253381df51a723d58691b2ee1ab08 570e7b2abdd848b95f2f578043fc23bd6f6fd24d refs/heads/master', key_id: key.id, project: project.path_with_namespace, - action: 'git-receive-pack' + action: 'git-receive-pack', + secret_token: secret_token ) end def archive(key, project) - get( + post( api("/internal/allowed"), ref: 'master', key_id: key.id, project: project.path_with_namespace, - action: 'git-upload-archive' + action: 'git-upload-archive', + secret_token: secret_token ) end end diff --git a/spec/requests/api/issues_spec.rb b/spec/requests/api/issues_spec.rb index 08dc94ebdf..b6b0427deb 100644 --- a/spec/requests/api/issues_spec.rb +++ b/spec/requests/api/issues_spec.rb @@ -4,10 +4,29 @@ describe API::API, api: true do include ApiHelpers let(:user) { create(:user) } let!(:project) { create(:project, namespace: user.namespace ) } - let!(:issue) { create(:issue, author: user, assignee: user, project: project) } + let!(:closed_issue) do + create :closed_issue, + author: user, + assignee: user, + project: project, + state: :closed, + milestone: milestone + end + let!(:issue) do + create :issue, + author: user, + assignee: user, + project: project, + milestone: milestone + end let!(:label) do create(:label, title: 'label', color: '#FFAABB', project: project) end + let!(:label_link) { create(:label_link, label: label, target: issue) } + let!(:milestone) { create(:milestone, title: '1.0.0', project: project) } + let!(:empty_milestone) do + create(:milestone, title: '2.0.0', project: project) + end before { project.team << [user, :reporter] } @@ -15,46 +34,169 @@ describe API::API, api: true do context "when unauthenticated" do it "should return authentication error" do get api("/issues") - response.status.should == 401 + expect(response.status).to eq(401) end end context "when authenticated" do it "should return an array of issues" do get api("/issues", user) - response.status.should == 200 - json_response.should be_an Array - json_response.first['title'].should == issue.title + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['title']).to eq(issue.title) end it "should add pagination headers" do get api("/issues?per_page=3", user) - response.headers['Link'].should == + expect(response.headers['Link']).to eq( '; rel="first", ; rel="last"' + ) + end + + it 'should return an array of closed issues' do + get api('/issues?state=closed', user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(closed_issue.id) + end + + it 'should return an array of opened issues' do + get api('/issues?state=opened', user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(issue.id) + end + + it 'should return an array of all issues' do + get api('/issues?state=all', user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(2) + expect(json_response.first['id']).to eq(issue.id) + expect(json_response.second['id']).to eq(closed_issue.id) + end + + it 'should return an array of labeled issues' do + get api("/issues?labels=#{label.title}", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['labels']).to eq([label.title]) + end + + it 'should return an array of labeled issues when at least one label matches' do + get api("/issues?labels=#{label.title},foo,bar", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['labels']).to eq([label.title]) + end + + it 'should return an empty array if no issue matches labels' do + get api('/issues?labels=foo,bar', user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(0) + end + + it 'should return an array of labeled issues matching given state' do + get api("/issues?labels=#{label.title}&state=opened", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['labels']).to eq([label.title]) + expect(json_response.first['state']).to eq('opened') + end + + it 'should return an empty array if no issue matches labels and state filters' do + get api("/issues?labels=#{label.title}&state=closed", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(0) end end end describe "GET /projects/:id/issues" do + let(:base_url) { "/projects/#{project.id}" } + let(:title) { milestone.title } + it "should return project issues" do - get api("/projects/#{project.id}/issues", user) - response.status.should == 200 - json_response.should be_an Array - json_response.first['title'].should == issue.title + get api("#{base_url}/issues", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['title']).to eq(issue.title) + end + + it 'should return an array of labeled project issues' do + get api("#{base_url}/issues?labels=#{label.title}", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['labels']).to eq([label.title]) + end + + it 'should return an array of labeled project issues when at least one label matches' do + get api("#{base_url}/issues?labels=#{label.title},foo,bar", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['labels']).to eq([label.title]) + end + + it 'should return an empty array if no project issue matches labels' do + get api("#{base_url}/issues?labels=foo,bar", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(0) + end + + it 'should return an empty array if no issue matches milestone' do + get api("#{base_url}/issues?milestone=#{empty_milestone.title}", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(0) + end + + it 'should return an empty array if milestone does not exist' do + get api("#{base_url}/issues?milestone=foo", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(0) + end + + it 'should return an array of issues in given milestone' do + get api("#{base_url}/issues?milestone=#{title}", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(2) + expect(json_response.first['id']).to eq(issue.id) + expect(json_response.second['id']).to eq(closed_issue.id) + end + + it 'should return an array of issues matching state in milestone' do + get api("#{base_url}/issues?milestone=#{milestone.title}"\ + '&state=closed', user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['id']).to eq(closed_issue.id) end end describe "GET /projects/:id/issues/:issue_id" do it "should return a project issue by id" do get api("/projects/#{project.id}/issues/#{issue.id}", user) - response.status.should == 200 - json_response['title'].should == issue.title - json_response['iid'].should == issue.iid + expect(response.status).to eq(200) + expect(json_response['title']).to eq(issue.title) + expect(json_response['iid']).to eq(issue.iid) end it "should return 404 if issue id not found" do get api("/projects/#{project.id}/issues/54321", user) - response.status.should == 404 + expect(response.status).to eq(404) end end @@ -62,23 +204,32 @@ describe API::API, api: true do it "should create a new project issue" do post api("/projects/#{project.id}/issues", user), title: 'new issue', labels: 'label, label2' - response.status.should == 201 - json_response['title'].should == 'new issue' - json_response['description'].should be_nil - json_response['labels'].should == ['label', 'label2'] + expect(response.status).to eq(201) + expect(json_response['title']).to eq('new issue') + expect(json_response['description']).to be_nil + expect(json_response['labels']).to eq(['label', 'label2']) end it "should return a 400 bad request if title not given" do post api("/projects/#{project.id}/issues", user), labels: 'label, label2' - response.status.should == 400 + expect(response.status).to eq(400) end it 'should return 400 on invalid label names' do post api("/projects/#{project.id}/issues", user), title: 'new issue', labels: 'label, ?' - response.status.should == 400 - json_response['message']['labels']['?']['title'].should == ['is invalid'] + expect(response.status).to eq(400) + expect(json_response['message']['labels']['?']['title']).to eq(['is invalid']) + end + + it 'should return 400 if title is too long' do + post api("/projects/#{project.id}/issues", user), + title: 'g' * 256 + expect(response.status).to eq(400) + expect(json_response['message']['title']).to eq([ + 'is too long (maximum is 255 characters)' + ]) end end @@ -86,23 +237,23 @@ describe API::API, api: true do it "should update a project issue" do put api("/projects/#{project.id}/issues/#{issue.id}", user), title: 'updated title' - response.status.should == 200 + expect(response.status).to eq(200) - json_response['title'].should == 'updated title' + expect(json_response['title']).to eq('updated title') end it "should return 404 error if issue id not found" do put api("/projects/#{project.id}/issues/44444", user), title: 'updated title' - response.status.should == 404 + expect(response.status).to eq(404) end it 'should return 400 on invalid label names' do put api("/projects/#{project.id}/issues/#{issue.id}", user), title: 'updated title', labels: 'label, ?' - response.status.should == 400 - json_response['message']['labels']['?']['title'].should == ['is invalid'] + expect(response.status).to eq(400) + expect(json_response['message']['labels']['?']['title']).to eq(['is invalid']) end end @@ -113,40 +264,49 @@ describe API::API, api: true do it 'should not update labels if not present' do put api("/projects/#{project.id}/issues/#{issue.id}", user), title: 'updated title' - response.status.should == 200 - json_response['labels'].should == [label.title] + expect(response.status).to eq(200) + expect(json_response['labels']).to eq([label.title]) end it 'should remove all labels' do put api("/projects/#{project.id}/issues/#{issue.id}", user), labels: '' - response.status.should == 200 - json_response['labels'].should == [] + expect(response.status).to eq(200) + expect(json_response['labels']).to eq([]) end it 'should update labels' do put api("/projects/#{project.id}/issues/#{issue.id}", user), labels: 'foo,bar' - response.status.should == 200 - json_response['labels'].should include 'foo' - json_response['labels'].should include 'bar' + expect(response.status).to eq(200) + expect(json_response['labels']).to include 'foo' + expect(json_response['labels']).to include 'bar' end it 'should return 400 on invalid label names' do put api("/projects/#{project.id}/issues/#{issue.id}", user), labels: 'label, ?' - response.status.should == 400 - json_response['message']['labels']['?']['title'].should == ['is invalid'] + expect(response.status).to eq(400) + expect(json_response['message']['labels']['?']['title']).to eq(['is invalid']) end it 'should allow special label names' do put api("/projects/#{project.id}/issues/#{issue.id}", user), labels: 'label:foo, label-bar,label_bar,label/bar' - response.status.should == 200 - json_response['labels'].should include 'label:foo' - json_response['labels'].should include 'label-bar' - json_response['labels'].should include 'label_bar' - json_response['labels'].should include 'label/bar' + expect(response.status).to eq(200) + expect(json_response['labels']).to include 'label:foo' + expect(json_response['labels']).to include 'label-bar' + expect(json_response['labels']).to include 'label_bar' + expect(json_response['labels']).to include 'label/bar' + end + + it 'should return 400 if title is too long' do + put api("/projects/#{project.id}/issues/#{issue.id}", user), + title: 'g' * 256 + expect(response.status).to eq(400) + expect(json_response['message']['title']).to eq([ + 'is too long (maximum is 255 characters)' + ]) end end @@ -154,17 +314,17 @@ describe API::API, api: true do it "should update a project issue" do put api("/projects/#{project.id}/issues/#{issue.id}", user), labels: 'label2', state_event: "close" - response.status.should == 200 + expect(response.status).to eq(200) - json_response['labels'].should == ['label2'] - json_response['state'].should eq "closed" + expect(json_response['labels']).to include 'label2' + expect(json_response['state']).to eq "closed" end end describe "DELETE /projects/:id/issues/:issue_id" do it "should delete a project issue" do delete api("/projects/#{project.id}/issues/#{issue.id}", user) - response.status.should == 405 + expect(response.status).to eq(405) end end end diff --git a/spec/requests/api/labels_spec.rb b/spec/requests/api/labels_spec.rb index ee9088933a..aff109a942 100644 --- a/spec/requests/api/labels_spec.rb +++ b/spec/requests/api/labels_spec.rb @@ -15,10 +15,10 @@ describe API::API, api: true do describe 'GET /projects/:id/labels' do it 'should return project labels' do get api("/projects/#{project.id}/labels", user) - response.status.should == 200 - json_response.should be_an Array - json_response.size.should == 1 - json_response.first['name'].should == label1.name + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(1) + expect(json_response.first['name']).to eq(label1.name) end end @@ -27,69 +27,69 @@ describe API::API, api: true do post api("/projects/#{project.id}/labels", user), name: 'Foo', color: '#FFAABB' - response.status.should == 201 - json_response['name'].should == 'Foo' - json_response['color'].should == '#FFAABB' + expect(response.status).to eq(201) + expect(json_response['name']).to eq('Foo') + expect(json_response['color']).to eq('#FFAABB') end it 'should return a 400 bad request if name not given' do post api("/projects/#{project.id}/labels", user), color: '#FFAABB' - response.status.should == 400 + expect(response.status).to eq(400) end it 'should return a 400 bad request if color not given' do post api("/projects/#{project.id}/labels", user), name: 'Foobar' - response.status.should == 400 + expect(response.status).to eq(400) end it 'should return 400 for invalid color' do post api("/projects/#{project.id}/labels", user), name: 'Foo', color: '#FFAA' - response.status.should == 400 - json_response['message'].should == 'Color is invalid' + expect(response.status).to eq(400) + expect(json_response['message']['color']).to eq(['is invalid']) end it 'should return 400 for too long color code' do post api("/projects/#{project.id}/labels", user), name: 'Foo', color: '#FFAAFFFF' - response.status.should == 400 - json_response['message'].should == 'Color is invalid' + expect(response.status).to eq(400) + expect(json_response['message']['color']).to eq(['is invalid']) end it 'should return 400 for invalid name' do post api("/projects/#{project.id}/labels", user), name: '?', color: '#FFAABB' - response.status.should == 400 - json_response['message'].should == 'Title is invalid' + expect(response.status).to eq(400) + expect(json_response['message']['title']).to eq(['is invalid']) end it 'should return 409 if label already exists' do post api("/projects/#{project.id}/labels", user), name: 'label1', color: '#FFAABB' - response.status.should == 409 - json_response['message'].should == 'Label already exists' + expect(response.status).to eq(409) + expect(json_response['message']).to eq('Label already exists') end end describe 'DELETE /projects/:id/labels' do it 'should return 200 for existing label' do delete api("/projects/#{project.id}/labels", user), name: 'label1' - response.status.should == 200 + expect(response.status).to eq(200) end it 'should return 404 for non existing label' do delete api("/projects/#{project.id}/labels", user), name: 'label2' - response.status.should == 404 - json_response['message'].should == 'Label not found' + expect(response.status).to eq(404) + expect(json_response['message']).to eq('404 Label Not Found') end it 'should return 400 for wrong parameters' do delete api("/projects/#{project.id}/labels", user) - response.status.should == 400 + expect(response.status).to eq(400) end end @@ -99,44 +99,47 @@ describe API::API, api: true do name: 'label1', new_name: 'New Label', color: '#FFFFFF' - response.status.should == 200 - json_response['name'].should == 'New Label' - json_response['color'].should == '#FFFFFF' + expect(response.status).to eq(200) + expect(json_response['name']).to eq('New Label') + expect(json_response['color']).to eq('#FFFFFF') end it 'should return 200 if name is changed' do put api("/projects/#{project.id}/labels", user), name: 'label1', new_name: 'New Label' - response.status.should == 200 - json_response['name'].should == 'New Label' - json_response['color'].should == label1.color + expect(response.status).to eq(200) + expect(json_response['name']).to eq('New Label') + expect(json_response['color']).to eq(label1.color) end it 'should return 200 if colors is changed' do put api("/projects/#{project.id}/labels", user), name: 'label1', color: '#FFFFFF' - response.status.should == 200 - json_response['name'].should == label1.name - json_response['color'].should == '#FFFFFF' + expect(response.status).to eq(200) + expect(json_response['name']).to eq(label1.name) + expect(json_response['color']).to eq('#FFFFFF') end it 'should return 404 if label does not exist' do put api("/projects/#{project.id}/labels", user), name: 'label2', new_name: 'label3' - response.status.should == 404 + expect(response.status).to eq(404) end it 'should return 400 if no label name given' do put api("/projects/#{project.id}/labels", user), new_name: 'label2' - response.status.should == 400 + expect(response.status).to eq(400) + expect(json_response['message']).to eq('400 (Bad request) "name" not given') end it 'should return 400 if no new parameters given' do put api("/projects/#{project.id}/labels", user), name: 'label1' - response.status.should == 400 + expect(response.status).to eq(400) + expect(json_response['message']).to eq('Required parameters '\ + '"new_name" or "color" missing') end it 'should return 400 for invalid name' do @@ -144,24 +147,24 @@ describe API::API, api: true do name: 'label1', new_name: '?', color: '#FFFFFF' - response.status.should == 400 - json_response['message'].should == 'Title is invalid' + expect(response.status).to eq(400) + expect(json_response['message']['title']).to eq(['is invalid']) end it 'should return 400 for invalid name' do put api("/projects/#{project.id}/labels", user), name: 'label1', color: '#FF' - response.status.should == 400 - json_response['message'].should == 'Color is invalid' + expect(response.status).to eq(400) + expect(json_response['message']['color']).to eq(['is invalid']) end it 'should return 400 for too long color code' do post api("/projects/#{project.id}/labels", user), name: 'Foo', color: '#FFAAFFFF' - response.status.should == 400 - json_response['message'].should == 'Color is invalid' + expect(response.status).to eq(400) + expect(json_response['message']['color']).to eq(['is invalid']) end end end diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb index 06a25c5e3a..9e252441a4 100644 --- a/spec/requests/api/merge_requests_spec.rb +++ b/spec/requests/api/merge_requests_spec.rb @@ -16,46 +16,93 @@ describe API::API, api: true do context "when unauthenticated" do it "should return authentication error" do get api("/projects/#{project.id}/merge_requests") - response.status.should == 401 + expect(response.status).to eq(401) end end context "when authenticated" do it "should return an array of all merge_requests" do get api("/projects/#{project.id}/merge_requests", user) - response.status.should == 200 - json_response.should be_an Array - json_response.length.should == 3 - json_response.first['title'].should == merge_request.title + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(3) + expect(json_response.last['title']).to eq(merge_request.title) end + it "should return an array of all merge_requests" do get api("/projects/#{project.id}/merge_requests?state", user) - response.status.should == 200 - json_response.should be_an Array - json_response.length.should == 3 - json_response.first['title'].should == merge_request.title + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(3) + expect(json_response.last['title']).to eq(merge_request.title) end + it "should return an array of open merge_requests" do get api("/projects/#{project.id}/merge_requests?state=opened", user) - response.status.should == 200 - json_response.should be_an Array - json_response.length.should == 1 - json_response.first['title'].should == merge_request.title + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.last['title']).to eq(merge_request.title) end + it "should return an array of closed merge_requests" do get api("/projects/#{project.id}/merge_requests?state=closed", user) - response.status.should == 200 - json_response.should be_an Array - json_response.length.should == 2 - json_response.first['title'].should == merge_request_closed.title - json_response.second['title'].should == merge_request_merged.title + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(2) + expect(json_response.second['title']).to eq(merge_request_closed.title) + expect(json_response.first['title']).to eq(merge_request_merged.title) end + it "should return an array of merged merge_requests" do get api("/projects/#{project.id}/merge_requests?state=merged", user) - response.status.should == 200 - json_response.should be_an Array - json_response.length.should == 1 - json_response.first['title'].should == merge_request_merged.title + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['title']).to eq(merge_request_merged.title) + end + + context "with ordering" do + before do + @mr_later = mr_with_later_created_and_updated_at_time + @mr_earlier = mr_with_earlier_created_and_updated_at_time + end + + it "should return an array of merge_requests in ascending order" do + get api("/projects/#{project.id}/merge_requests?sort=asc", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(3) + expect(json_response.last['id']).to eq(@mr_earlier.id) + expect(json_response.first['id']).to eq(@mr_later.id) + end + + it "should return an array of merge_requests in descending order" do + get api("/projects/#{project.id}/merge_requests?sort=desc", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(3) + expect(json_response.first['id']).to eq(@mr_later.id) + expect(json_response.last['id']).to eq(@mr_earlier.id) + end + + it "should return an array of merge_requests ordered by updated_at" do + get api("/projects/#{project.id}/merge_requests?order_by=updated_at", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(3) + expect(json_response.last['id']).to eq(@mr_earlier.id) + expect(json_response.first['id']).to eq(@mr_later.id) + end + + it "should return an array of merge_requests ordered by created_at" do + get api("/projects/#{project.id}/merge_requests?sort=created_at", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(3) + expect(json_response.last['id']).to eq(@mr_earlier.id) + expect(json_response.first['id']).to eq(@mr_later.id) + end end end end @@ -63,14 +110,27 @@ describe API::API, api: true do describe "GET /projects/:id/merge_request/:merge_request_id" do it "should return merge_request" do get api("/projects/#{project.id}/merge_request/#{merge_request.id}", user) - response.status.should == 200 - json_response['title'].should == merge_request.title - json_response['iid'].should == merge_request.iid + expect(response.status).to eq(200) + expect(json_response['title']).to eq(merge_request.title) + expect(json_response['iid']).to eq(merge_request.iid) end it "should return a 404 error if merge_request_id not found" do get api("/projects/#{project.id}/merge_request/999", user) - response.status.should == 404 + expect(response.status).to eq(404) + end + end + + describe 'GET /projects/:id/merge_request/:merge_request_id/changes' do + it 'should return the change information of the merge_request' do + get api("/projects/#{project.id}/merge_request/#{merge_request.id}/changes", user) + expect(response.status).to eq 200 + expect(json_response['changes'].size).to eq(merge_request.diffs.size) + end + + it 'returns a 404 when merge_request_id not found' do + get api("/projects/#{project.id}/merge_request/999/changes", user) + expect(response.status).to eq(404) end end @@ -83,33 +143,33 @@ describe API::API, api: true do target_branch: 'master', author: user, labels: 'label, label2' - response.status.should == 201 - json_response['title'].should == 'Test merge_request' - json_response['labels'].should == ['label', 'label2'] + expect(response.status).to eq(201) + expect(json_response['title']).to eq('Test merge_request') + expect(json_response['labels']).to eq(['label', 'label2']) end it "should return 422 when source_branch equals target_branch" do post api("/projects/#{project.id}/merge_requests", user), title: "Test merge_request", source_branch: "master", target_branch: "master", author: user - response.status.should == 422 + expect(response.status).to eq(422) end it "should return 400 when source_branch is missing" do post api("/projects/#{project.id}/merge_requests", user), title: "Test merge_request", target_branch: "master", author: user - response.status.should == 400 + expect(response.status).to eq(400) end it "should return 400 when target_branch is missing" do post api("/projects/#{project.id}/merge_requests", user), title: "Test merge_request", source_branch: "stable", author: user - response.status.should == 400 + expect(response.status).to eq(400) end it "should return 400 when title is missing" do post api("/projects/#{project.id}/merge_requests", user), target_branch: 'master', source_branch: 'stable' - response.status.should == 400 + expect(response.status).to eq(400) end it 'should return 400 on invalid label names' do @@ -119,9 +179,32 @@ describe API::API, api: true do target_branch: 'master', author: user, labels: 'label, ?' - response.status.should == 400 - json_response['message']['labels']['?']['title'].should == + expect(response.status).to eq(400) + expect(json_response['message']['labels']['?']['title']).to eq( ['is invalid'] + ) + end + + context 'with existing MR' do + before do + post api("/projects/#{project.id}/merge_requests", user), + title: 'Test merge_request', + source_branch: 'stable', + target_branch: 'master', + author: user + @mr = MergeRequest.all.last + end + + it 'should return 409 when MR already exists for source/target' do + expect do + post api("/projects/#{project.id}/merge_requests", user), + title: 'New test merge_request', + source_branch: 'stable', + target_branch: 'master', + author: user + end.to change { MergeRequest.count }.by(0) + expect(response.status).to eq(409) + end end end @@ -137,55 +220,65 @@ describe API::API, api: true do it "should return merge_request" do post api("/projects/#{fork_project.id}/merge_requests", user2), title: 'Test merge_request', source_branch: "stable", target_branch: "master", author: user2, target_project_id: project.id, description: 'Test description for Test merge_request' - response.status.should == 201 - json_response['title'].should == 'Test merge_request' - json_response['description'].should == 'Test description for Test merge_request' + expect(response.status).to eq(201) + expect(json_response['title']).to eq('Test merge_request') + expect(json_response['description']).to eq('Test description for Test merge_request') end it "should not return 422 when source_branch equals target_branch" do - project.id.should_not == fork_project.id - fork_project.forked?.should be_true - fork_project.forked_from_project.should == project + expect(project.id).not_to eq(fork_project.id) + expect(fork_project.forked?).to be_truthy + expect(fork_project.forked_from_project).to eq(project) post api("/projects/#{fork_project.id}/merge_requests", user2), title: 'Test merge_request', source_branch: "master", target_branch: "master", author: user2, target_project_id: project.id - response.status.should == 201 - json_response['title'].should == 'Test merge_request' + expect(response.status).to eq(201) + expect(json_response['title']).to eq('Test merge_request') end it "should return 400 when source_branch is missing" do post api("/projects/#{fork_project.id}/merge_requests", user2), title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id - response.status.should == 400 + expect(response.status).to eq(400) end it "should return 400 when target_branch is missing" do post api("/projects/#{fork_project.id}/merge_requests", user2), title: 'Test merge_request', target_branch: "master", author: user2, target_project_id: project.id - response.status.should == 400 + expect(response.status).to eq(400) end it "should return 400 when title is missing" do post api("/projects/#{fork_project.id}/merge_requests", user2), target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: project.id - response.status.should == 400 + expect(response.status).to eq(400) end - it "should return 404 when target_branch is specified and not a forked project" do - post api("/projects/#{project.id}/merge_requests", user), - title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user, target_project_id: fork_project.id - response.status.should == 404 - end + context 'when target_branch is specified' do + it 'should return 422 if not a forked project' do + post api("/projects/#{project.id}/merge_requests", user), + title: 'Test merge_request', + target_branch: 'master', + source_branch: 'stable', + author: user, + target_project_id: fork_project.id + expect(response.status).to eq(422) + end - it "should return 404 when target_branch is specified and for a different fork" do - post api("/projects/#{fork_project.id}/merge_requests", user2), - title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: unrelated_project.id - response.status.should == 404 + it 'should return 422 if targeting a different fork' do + post api("/projects/#{fork_project.id}/merge_requests", user2), + title: 'Test merge_request', + target_branch: 'master', + source_branch: 'stable', + author: user2, + target_project_id: unrelated_project.id + expect(response.status).to eq(422) + end end it "should return 201 when target_branch is specified and for the same project" do post api("/projects/#{fork_project.id}/merge_requests", user2), title: 'Test merge_request', target_branch: 'master', source_branch: 'stable', author: user2, target_project_id: fork_project.id - response.status.should == 201 + expect(response.status).to eq(201) end end end @@ -193,8 +286,8 @@ describe API::API, api: true do describe "PUT /projects/:id/merge_request/:merge_request_id to close MR" do it "should return merge_request" do put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), state_event: "close" - response.status.should == 200 - json_response['state'].should == 'closed' + expect(response.status).to eq(200) + expect(json_response['state']).to eq('closed') end end @@ -202,55 +295,55 @@ describe API::API, api: true do it "should return merge_request in case of success" do MergeRequest.any_instance.stub(can_be_merged?: true, automerge!: true) put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user) - response.status.should == 200 + expect(response.status).to eq(200) end it "should return 405 if branch can't be merged" do MergeRequest.any_instance.stub(can_be_merged?: false) put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user) - response.status.should == 405 - json_response['message'].should == 'Branch cannot be merged' + expect(response.status).to eq(405) + expect(json_response['message']).to eq('Branch cannot be merged') end it "should return 405 if merge_request is not open" do merge_request.close put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user) - response.status.should == 405 - json_response['message'].should == 'Method Not Allowed' + expect(response.status).to eq(405) + expect(json_response['message']).to eq('405 Method Not Allowed') end it "should return 401 if user has no permissions to merge" do user2 = create(:user) project.team << [user2, :reporter] put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user2) - response.status.should == 401 - json_response['message'].should == '401 Unauthorized' + expect(response.status).to eq(401) + expect(json_response['message']).to eq('401 Unauthorized') end end describe "PUT /projects/:id/merge_request/:merge_request_id" do it "should return merge_request" do put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), title: "New title" - response.status.should == 200 - json_response['title'].should == 'New title' + expect(response.status).to eq(200) + expect(json_response['title']).to eq('New title') end it "should return merge_request" do put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), description: "New description" - response.status.should == 200 - json_response['description'].should == 'New description' + expect(response.status).to eq(200) + expect(json_response['description']).to eq('New description') end it "should return 422 when source_branch and target_branch are renamed the same" do put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), source_branch: "master", target_branch: "master" - response.status.should == 422 + expect(response.status).to eq(422) end it "should return merge_request with renamed target_branch" do put api("/projects/#{project.id}/merge_request/#{merge_request.id}", user), target_branch: "wiki" - response.status.should == 200 - json_response['target_branch'].should == 'wiki' + expect(response.status).to eq(200) + expect(json_response['target_branch']).to eq('wiki') end it 'should return 400 on invalid label names' do @@ -258,42 +351,59 @@ describe API::API, api: true do user), title: 'new issue', labels: 'label, ?' - response.status.should == 400 - json_response['message']['labels']['?']['title'].should == ['is invalid'] + expect(response.status).to eq(400) + expect(json_response['message']['labels']['?']['title']).to eq(['is invalid']) end end describe "POST /projects/:id/merge_request/:merge_request_id/comments" do it "should return comment" do post api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user), note: "My comment" - response.status.should == 201 - json_response['note'].should == 'My comment' + expect(response.status).to eq(201) + expect(json_response['note']).to eq('My comment') end it "should return 400 if note is missing" do post api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user) - response.status.should == 400 + expect(response.status).to eq(400) end it "should return 404 if note is attached to non existent merge request" do - post api("/projects/#{project.id}/merge_request/111/comments", user), note: "My comment" - response.status.should == 404 + post api("/projects/#{project.id}/merge_request/404/comments", user), + note: 'My comment' + expect(response.status).to eq(404) end end describe "GET :id/merge_request/:merge_request_id/comments" do it "should return merge_request comments" do get api("/projects/#{project.id}/merge_request/#{merge_request.id}/comments", user) - response.status.should == 200 - json_response.should be_an Array - json_response.length.should == 1 - json_response.first['note'].should == "a comment on a MR" - json_response.first['author']['id'].should == user.id + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) + expect(json_response.first['note']).to eq("a comment on a MR") + expect(json_response.first['author']['id']).to eq(user.id) end it "should return a 404 error if merge_request_id not found" do get api("/projects/#{project.id}/merge_request/999/comments", user) - response.status.should == 404 + expect(response.status).to eq(404) end end + + def mr_with_later_created_and_updated_at_time + merge_request + merge_request.created_at += 1.hour + merge_request.updated_at += 30.minutes + merge_request.save + merge_request + end + + def mr_with_earlier_created_and_updated_at_time + merge_request_closed + merge_request_closed.created_at -= 1.hour + merge_request_closed.updated_at -= 30.minutes + merge_request_closed.save + merge_request_closed + end end diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb index f0619a1c80..effb072347 100644 --- a/spec/requests/api/milestones_spec.rb +++ b/spec/requests/api/milestones_spec.rb @@ -8,92 +8,109 @@ describe API::API, api: true do before { project.team << [user, :developer] } - describe "GET /projects/:id/milestones" do - it "should return project milestones" do + describe 'GET /projects/:id/milestones' do + it 'should return project milestones' do get api("/projects/#{project.id}/milestones", user) - response.status.should == 200 - json_response.should be_an Array - json_response.first['title'].should == milestone.title + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['title']).to eq(milestone.title) end - it "should return a 401 error if user not authenticated" do + it 'should return a 401 error if user not authenticated' do get api("/projects/#{project.id}/milestones") - response.status.should == 401 + expect(response.status).to eq(401) end end - describe "GET /projects/:id/milestones/:milestone_id" do - it "should return a project milestone by id" do + describe 'GET /projects/:id/milestones/:milestone_id' do + it 'should return a project milestone by id' do get api("/projects/#{project.id}/milestones/#{milestone.id}", user) - response.status.should == 200 - json_response['title'].should == milestone.title - json_response['iid'].should == milestone.iid + expect(response.status).to eq(200) + expect(json_response['title']).to eq(milestone.title) + expect(json_response['iid']).to eq(milestone.iid) end - it "should return 401 error if user not authenticated" do + it 'should return 401 error if user not authenticated' do get api("/projects/#{project.id}/milestones/#{milestone.id}") - response.status.should == 401 + expect(response.status).to eq(401) end - it "should return a 404 error if milestone id not found" do + it 'should return a 404 error if milestone id not found' do get api("/projects/#{project.id}/milestones/1234", user) - response.status.should == 404 + expect(response.status).to eq(404) end end - describe "POST /projects/:id/milestones" do - it "should create a new project milestone" do + describe 'POST /projects/:id/milestones' do + it 'should create a new project milestone' do post api("/projects/#{project.id}/milestones", user), title: 'new milestone' - response.status.should == 201 - json_response['title'].should == 'new milestone' - json_response['description'].should be_nil + expect(response.status).to eq(201) + expect(json_response['title']).to eq('new milestone') + expect(json_response['description']).to be_nil end - it "should create a new project milestone with description and due date" do + it 'should create a new project milestone with description and due date' do post api("/projects/#{project.id}/milestones", user), title: 'new milestone', description: 'release', due_date: '2013-03-02' - response.status.should == 201 - json_response['description'].should == 'release' - json_response['due_date'].should == '2013-03-02' + expect(response.status).to eq(201) + expect(json_response['description']).to eq('release') + expect(json_response['due_date']).to eq('2013-03-02') end - it "should return a 400 error if title is missing" do + it 'should return a 400 error if title is missing' do post api("/projects/#{project.id}/milestones", user) - response.status.should == 400 + expect(response.status).to eq(400) end end - describe "PUT /projects/:id/milestones/:milestone_id" do - it "should update a project milestone" do + describe 'PUT /projects/:id/milestones/:milestone_id' do + it 'should update a project milestone' do put api("/projects/#{project.id}/milestones/#{milestone.id}", user), title: 'updated title' - response.status.should == 200 - json_response['title'].should == 'updated title' + expect(response.status).to eq(200) + expect(json_response['title']).to eq('updated title') end - it "should return a 404 error if milestone id not found" do + it 'should return a 404 error if milestone id not found' do put api("/projects/#{project.id}/milestones/1234", user), title: 'updated title' - response.status.should == 404 + expect(response.status).to eq(404) end end - describe "PUT /projects/:id/milestones/:milestone_id to close milestone" do - it "should update a project milestone" do + describe 'PUT /projects/:id/milestones/:milestone_id to close milestone' do + it 'should update a project milestone' do put api("/projects/#{project.id}/milestones/#{milestone.id}", user), state_event: 'close' - response.status.should == 200 + expect(response.status).to eq(200) - json_response['state'].should == 'closed' + expect(json_response['state']).to eq('closed') end end - describe "PUT /projects/:id/milestones/:milestone_id to test observer on close" do - it "should create an activity event when an milestone is closed" do - Event.should_receive(:create) + describe 'PUT /projects/:id/milestones/:milestone_id to test observer on close' do + it 'should create an activity event when an milestone is closed' do + expect(Event).to receive(:create) put api("/projects/#{project.id}/milestones/#{milestone.id}", user), state_event: 'close' end end + + describe 'GET /projects/:id/milestones/:milestone_id/issues' do + before do + milestone.issues << create(:issue) + end + it 'should return project issues for a particular milestone' do + get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['milestone']['title']).to eq(milestone.title) + end + + it 'should return a 401 error if user not authenticated' do + get api("/projects/#{project.id}/milestones/#{milestone.id}/issues") + expect(response.status).to eq(401) + end + end end diff --git a/spec/requests/api/namespaces_spec.rb b/spec/requests/api/namespaces_spec.rb index d9d52468b1..6ddaaa0a6d 100644 --- a/spec/requests/api/namespaces_spec.rb +++ b/spec/requests/api/namespaces_spec.rb @@ -10,18 +10,17 @@ describe API::API, api: true do context "when unauthenticated" do it "should return authentication error" do get api("/namespaces") - response.status.should == 401 + expect(response.status).to eq(401) end end context "when authenticated as admin" do it "admin: should return an array of all namespaces" do get api("/namespaces", admin) - response.status.should == 200 - json_response.should be_an Array + expect(response.status).to eq(200) + expect(json_response).to be_an Array - # Admin namespace + 2 group namespaces - json_response.length.should == 3 + expect(json_response.length).to eq(Namespace.count) end end end diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb index 7aa53787ae..8b177af468 100644 --- a/spec/requests/api/notes_spec.rb +++ b/spec/requests/api/notes_spec.rb @@ -16,42 +16,42 @@ 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) - response.status.should == 200 - json_response.should be_an Array - json_response.first['body'].should == issue_note.note + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['body']).to eq(issue_note.note) end it "should return a 404 error when issue id not found" do get api("/projects/#{project.id}/issues/123/notes", user) - response.status.should == 404 + expect(response.status).to eq(404) end end 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) - response.status.should == 200 - json_response.should be_an Array - json_response.first['body'].should == snippet_note.note + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['body']).to eq(snippet_note.note) end it "should return a 404 error when snippet id not found" do get api("/projects/#{project.id}/snippets/42/notes", user) - response.status.should == 404 + expect(response.status).to eq(404) end end 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) - response.status.should == 200 - json_response.should be_an Array - json_response.first['body'].should == merge_request_note.note + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['body']).to eq(merge_request_note.note) end it "should return a 404 error if merge request id not found" do get api("/projects/#{project.id}/merge_requests/4444/notes", user) - response.status.should == 404 + expect(response.status).to eq(404) end end end @@ -60,26 +60,26 @@ 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) - response.status.should == 200 - json_response['body'].should == issue_note.note + 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/123", user) - response.status.should == 404 + expect(response.status).to eq(404) end end 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) - response.status.should == 200 - json_response['body'].should == snippet_note.note + 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/123", user) - response.status.should == 404 + expect(response.status).to eq(404) end end end @@ -88,47 +88,101 @@ 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!' - response.status.should == 201 - json_response['body'].should == 'hi!' - json_response['author']['username'].should == user.username + expect(response.status).to eq(201) + expect(json_response['body']).to eq('hi!') + expect(json_response['author']['username']).to eq(user.username) end it "should return a 400 bad request error if body not given" do post api("/projects/#{project.id}/issues/#{issue.id}/notes", user) - response.status.should == 400 + 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!' - response.status.should == 401 + expect(response.status).to eq(401) end end 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!' - response.status.should == 201 - json_response['body'].should == 'hi!' - json_response['author']['username'].should == user.username + expect(response.status).to eq(201) + expect(json_response['body']).to eq('hi!') + expect(json_response['author']['username']).to eq(user.username) end it "should return a 400 bad request error if body not given" do post api("/projects/#{project.id}/snippets/#{snippet.id}/notes", user) - response.status.should == 400 + 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!' - response.status.should == 401 + expect(response.status).to eq(401) end end end describe "POST /projects/:id/noteable/:noteable_id/notes to test observer on create" do it "should create an activity event when an issue note is created" do - Event.should_receive(:create) + expect(Event).to receive(:create) post api("/projects/#{project.id}/issues/#{issue.id}/notes", user), body: 'hi!' end end + + describe 'PUT /projects/:id/noteable/:noteable_id/notes/:note_id' do + context 'when noteable is an Issue' 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 + + it 'should return a 404 error when note id not found' do + put api("/projects/#{project.id}/issues/#{issue.id}/notes/123", 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 + + context 'when noteable is a Snippet' 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 + + it 'should return a 404 error when note id not found' do + put api("/projects/#{project.id}/snippets/#{snippet.id}/"\ + "notes/123", user), body: "Hello!" + expect(response.status).to eq(404) + end + end + + context 'when noteable is a Merge Request' 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 + + it 'should return a 404 error when note id not found' do + put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\ + "notes/123", user), body: "Hello!" + expect(response.status).to eq(404) + end + end + end + end diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb index cdb5e3d061..81fe68de66 100644 --- a/spec/requests/api/project_hooks_spec.rb +++ b/spec/requests/api/project_hooks_spec.rb @@ -16,18 +16,18 @@ describe API::API, 'ProjectHooks', api: true do context "authorized user" do it "should return project hooks" do get api("/projects/#{project.id}/hooks", user) - response.status.should == 200 + expect(response.status).to eq(200) - json_response.should be_an Array - json_response.count.should == 1 - json_response.first['url'].should == "http://example.com" + expect(json_response).to be_an Array + expect(json_response.count).to eq(1) + expect(json_response.first['url']).to eq("http://example.com") end end context "unauthorized user" do it "should not access project hooks" do get api("/projects/#{project.id}/hooks", user3) - response.status.should == 403 + expect(response.status).to eq(403) end end end @@ -36,26 +36,26 @@ describe API::API, 'ProjectHooks', api: true do context "authorized user" do it "should return a project hook" do get api("/projects/#{project.id}/hooks/#{hook.id}", user) - response.status.should == 200 - json_response['url'].should == hook.url + expect(response.status).to eq(200) + expect(json_response['url']).to eq(hook.url) end it "should return a 404 error if hook id is not available" do get api("/projects/#{project.id}/hooks/1234", user) - response.status.should == 404 + expect(response.status).to eq(404) end end context "unauthorized user" do it "should not access an existing hook" do get api("/projects/#{project.id}/hooks/#{hook.id}", user3) - response.status.should == 403 + expect(response.status).to eq(403) end end it "should return a 404 error if hook id is not available" do get api("/projects/#{project.id}/hooks/1234", user) - response.status.should == 404 + expect(response.status).to eq(404) end end @@ -65,17 +65,17 @@ describe API::API, 'ProjectHooks', api: true do post api("/projects/#{project.id}/hooks", user), url: "http://example.com", issues_events: true }.to change {project.hooks.count}.by(1) - response.status.should == 201 + expect(response.status).to eq(201) end it "should return a 400 error if url not given" do post api("/projects/#{project.id}/hooks", user) - response.status.should == 400 + expect(response.status).to eq(400) end it "should return a 422 error if url not valid" do post api("/projects/#{project.id}/hooks", user), "url" => "ftp://example.com" - response.status.should == 422 + expect(response.status).to eq(422) end end @@ -83,23 +83,23 @@ describe API::API, 'ProjectHooks', api: true do it "should update an existing project hook" do put api("/projects/#{project.id}/hooks/#{hook.id}", user), url: 'http://example.org', push_events: false - response.status.should == 200 - json_response['url'].should == 'http://example.org' + expect(response.status).to eq(200) + expect(json_response['url']).to eq('http://example.org') end it "should return 404 error if hook id not found" do put api("/projects/#{project.id}/hooks/1234", user), url: 'http://example.org' - response.status.should == 404 + expect(response.status).to eq(404) end it "should return 400 error if url is not given" do put api("/projects/#{project.id}/hooks/#{hook.id}", user) - response.status.should == 400 + expect(response.status).to eq(400) end it "should return a 422 error if url is not valid" do put api("/projects/#{project.id}/hooks/#{hook.id}", user), url: 'ftp://example.com' - response.status.should == 422 + expect(response.status).to eq(422) end end @@ -108,22 +108,22 @@ describe API::API, 'ProjectHooks', api: true do expect { delete api("/projects/#{project.id}/hooks/#{hook.id}", user) }.to change {project.hooks.count}.by(-1) - response.status.should == 200 + expect(response.status).to eq(200) end it "should return success when deleting hook" do delete api("/projects/#{project.id}/hooks/#{hook.id}", user) - response.status.should == 200 + expect(response.status).to eq(200) end it "should return success when deleting non existent hook" do delete api("/projects/#{project.id}/hooks/42", user) - response.status.should == 200 + expect(response.status).to eq(200) end it "should return a 405 error if hook id not given" do delete api("/projects/#{project.id}/hooks", user) - response.status.should == 405 + expect(response.status).to eq(405) end end end diff --git a/spec/requests/api/project_members_spec.rb b/spec/requests/api/project_members_spec.rb index 3c480c2ac4..8419a364ed 100644 --- a/spec/requests/api/project_members_spec.rb +++ b/spec/requests/api/project_members_spec.rb @@ -6,48 +6,48 @@ describe API::API, api: true do let(:user2) { create(:user) } let(:user3) { create(:user) } let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) } - let(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) } - let(:users_project2) { create(:users_project, user: user3, project: project, project_access: UsersProject::DEVELOPER) } + let(:project_member) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) } + let(:project_member2) { create(:project_member, user: user3, project: project, access_level: ProjectMember::DEVELOPER) } describe "GET /projects/:id/members" do - before { users_project } - before { users_project2 } + before { project_member } + before { project_member2 } it "should return project team members" do get api("/projects/#{project.id}/members", user) - response.status.should == 200 - json_response.should be_an Array - json_response.count.should == 2 - json_response.map { |u| u['username'] }.should include user.username + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.count).to eq(2) + expect(json_response.map { |u| u['username'] }).to include user.username end it "finds team members with query string" do get api("/projects/#{project.id}/members", user), query: user.username - response.status.should == 200 - json_response.should be_an Array - json_response.count.should == 1 - json_response.first['username'].should == user.username + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.count).to eq(1) + expect(json_response.first['username']).to eq(user.username) end it "should return a 404 error if id not found" do get api("/projects/9999/members", user) - response.status.should == 404 + expect(response.status).to eq(404) end end describe "GET /projects/:id/members/:user_id" do - before { users_project } + before { project_member } it "should return project team member" do get api("/projects/#{project.id}/members/#{user.id}", user) - response.status.should == 200 - json_response['username'].should == user.username - json_response['access_level'].should == UsersProject::MASTER + expect(response.status).to eq(200) + expect(json_response['username']).to eq(user.username) + expect(json_response['access_level']).to eq(ProjectMember::MASTER) end it "should return a 404 error if user id not found" do get api("/projects/#{project.id}/members/1234", user) - response.status.should == 404 + expect(response.status).to eq(404) end end @@ -55,99 +55,99 @@ describe API::API, api: true do it "should add user to project team" do expect { post api("/projects/#{project.id}/members", user), user_id: user2.id, - access_level: UsersProject::DEVELOPER - }.to change { UsersProject.count }.by(1) + access_level: ProjectMember::DEVELOPER + }.to change { ProjectMember.count }.by(1) - response.status.should == 201 - json_response['username'].should == user2.username - json_response['access_level'].should == UsersProject::DEVELOPER + expect(response.status).to eq(201) + expect(json_response['username']).to eq(user2.username) + expect(json_response['access_level']).to eq(ProjectMember::DEVELOPER) end it "should return a 201 status if user is already project member" do post api("/projects/#{project.id}/members", user), user_id: user2.id, - access_level: UsersProject::DEVELOPER + access_level: ProjectMember::DEVELOPER expect { post api("/projects/#{project.id}/members", user), user_id: user2.id, - access_level: UsersProject::DEVELOPER - }.not_to change { UsersProject.count }.by(1) + access_level: ProjectMember::DEVELOPER + }.not_to change { ProjectMember.count } - response.status.should == 201 - json_response['username'].should == user2.username - json_response['access_level'].should == UsersProject::DEVELOPER + expect(response.status).to eq(201) + expect(json_response['username']).to eq(user2.username) + expect(json_response['access_level']).to eq(ProjectMember::DEVELOPER) end it "should return a 400 error when user id is not given" do - post api("/projects/#{project.id}/members", user), access_level: UsersProject::MASTER - response.status.should == 400 + post api("/projects/#{project.id}/members", user), access_level: ProjectMember::MASTER + expect(response.status).to eq(400) end it "should return a 400 error when access level is not given" do post api("/projects/#{project.id}/members", user), user_id: user2.id - response.status.should == 400 + expect(response.status).to eq(400) end it "should return a 422 error when access level is not known" do post api("/projects/#{project.id}/members", user), user_id: user2.id, access_level: 1234 - response.status.should == 422 + expect(response.status).to eq(422) end end describe "PUT /projects/:id/members/:user_id" do - before { users_project2 } + before { project_member2 } it "should update project team member" do - put api("/projects/#{project.id}/members/#{user3.id}", user), access_level: UsersProject::MASTER - response.status.should == 200 - json_response['username'].should == user3.username - json_response['access_level'].should == UsersProject::MASTER + put api("/projects/#{project.id}/members/#{user3.id}", user), access_level: ProjectMember::MASTER + expect(response.status).to eq(200) + expect(json_response['username']).to eq(user3.username) + expect(json_response['access_level']).to eq(ProjectMember::MASTER) end it "should return a 404 error if user_id is not found" do - put api("/projects/#{project.id}/members/1234", user), access_level: UsersProject::MASTER - response.status.should == 404 + put api("/projects/#{project.id}/members/1234", user), access_level: ProjectMember::MASTER + expect(response.status).to eq(404) end it "should return a 400 error when access level is not given" do put api("/projects/#{project.id}/members/#{user3.id}", user) - response.status.should == 400 + expect(response.status).to eq(400) end it "should return a 422 error when access level is not known" do put api("/projects/#{project.id}/members/#{user3.id}", user), access_level: 123 - response.status.should == 422 + expect(response.status).to eq(422) end end describe "DELETE /projects/:id/members/:user_id" do - before { users_project } - before { users_project2 } + before { project_member } + before { project_member2 } it "should remove user from project team" do expect { delete api("/projects/#{project.id}/members/#{user3.id}", user) - }.to change { UsersProject.count }.by(-1) + }.to change { ProjectMember.count }.by(-1) end it "should return 200 if team member is not part of a project" do delete api("/projects/#{project.id}/members/#{user3.id}", user) expect { delete api("/projects/#{project.id}/members/#{user3.id}", user) - }.to_not change { UsersProject.count }.by(1) + }.to_not change { ProjectMember.count } end it "should return 200 if team member already removed" do delete api("/projects/#{project.id}/members/#{user3.id}", user) delete api("/projects/#{project.id}/members/#{user3.id}", user) - response.status.should == 200 + expect(response.status).to eq(200) end it "should return 200 OK when the user was not member" do expect { delete api("/projects/#{project.id}/members/1000000", user) - }.to change { UsersProject.count }.by(0) - response.status.should == 200 - json_response['message'].should == "Access revoked" - json_response['id'].should == 1000000 + }.to change { ProjectMember.count }.by(0) + expect(response.status).to eq(200) + expect(json_response['message']).to eq("Access revoked") + expect(json_response['id']).to eq(1000000) end end end diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index 12a3a07ff7..cc387378d3 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -1,199 +1,269 @@ +# -*- coding: utf-8 -*- require 'spec_helper' describe API::API, api: true do include ApiHelpers + include Gitlab::CurrentSettings let(:user) { create(:user) } let(:user2) { create(:user) } let(:user3) { create(:user) } 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, author: user, project: project, title: 'example') } - let(:users_project) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) } - let(:users_project2) { create(:users_project, user: user3, project: project, project_access: UsersProject::DEVELOPER) } - - describe "GET /projects" do - before { project } - - context "when unauthenticated" do - it "should return authentication error" do - get api("/projects") - response.status.should == 401 - end - end - - context "when authenticated" do - it "should return an array of projects" do - get api("/projects", user) - response.status.should == 200 - json_response.should be_an Array - json_response.first['name'].should == project.name - json_response.first['owner']['username'].should == user.username - end - end + let(:project_member) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) } + let(:project_member2) { create(:project_member, user: user3, project: project, access_level: ProjectMember::DEVELOPER) } + let(:user4) { create(:user) } + let(:project3) do + create(:project, + 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) + end + let(:project_member3) do + create(:project_member, + user: user4, + project: project3, + access_level: ProjectMember::MASTER) + end + let(:project4) do + create(:project, + name: 'third_project', + path: 'third_project', + creator_id: user4.id, + namespace: user4.namespace) end - describe "GET /projects/all" do + describe 'GET /projects' do before { project } - context "when unauthenticated" do - it "should return authentication error" do - get api("/projects/all") - response.status.should == 401 + context 'when unauthenticated' do + it 'should return authentication error' do + get api('/projects') + expect(response.status).to eq(401) end end - context "when authenticated as regular user" do - it "should return authentication error" do - get api("/projects/all", user) - response.status.should == 403 + context 'when authenticated' do + it 'should return an array of projects' do + get api('/projects', user) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['name']).to eq(project.name) + expect(json_response.first['owner']['username']).to eq(user.username) end - end - - context "when authenticated as admin" do - it "should return an array of all projects" do - get api("/projects/all", admin) + + it 'should include the project labels as the tag_list' do + get api('/projects', user) response.status.should == 200 json_response.should be_an Array - json_response.first['name'].should == project.name - json_response.first['owner']['username'].should == user.username + json_response.first.keys.should include('tag_list') end - end - end - - describe "POST /projects" do - context "maximum number of projects reached" do - before do - (1..user2.projects_limit).each do |project| - post api("/projects", user2), name: "foo#{project}" + + context 'and using search' do + it 'should return searched project' do + get api('/projects', user), { search: project.name } + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.length).to eq(1) end end - it "should not create new project" do + context 'and using sorting' do + before do + project2 + project3 + end + + it 'should return the correct order when sorted by id' do + get api('/projects', user), { order_by: 'id', sort: 'desc'} + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['id']).to eq(project3.id) + end + end + end + end + + describe 'GET /projects/all' do + before { project } + + context 'when unauthenticated' do + it 'should return authentication error' do + get api('/projects/all') + expect(response.status).to eq(401) + end + end + + context 'when authenticated as regular user' do + it 'should return authentication error' do + get api('/projects/all', user) + expect(response.status).to eq(403) + end + end + + context 'when authenticated as admin' do + it 'should return an array of all projects' do + get api('/projects/all', admin) + expect(response.status).to eq(200) + expect(json_response).to be_an Array + project_name = project.name + + expect(json_response.detect { + |project| project['name'] == project_name + }['name']).to eq(project_name) + + expect(json_response.detect { + |project| project['owner']['username'] == user.username + }['owner']['username']).to eq(user.username) + end + end + end + + describe 'POST /projects' do + context 'maximum number of projects reached' do + it 'should not create new project and respond with 403' do + allow_any_instance_of(User).to receive(:projects_limit_left).and_return(0) expect { - post api("/projects", user2), name: 'foo' + post api('/projects', user2), name: 'foo' }.to change {Project.count}.by(0) + expect(response.status).to eq(403) end end - it "should create new project without path" do - expect { post api("/projects", user), name: 'foo' }.to change {Project.count}.by(1) + it 'should create new project without path and return 201' do + expect { post api('/projects', user), name: 'foo' }. + to change { Project.count }.by(1) + expect(response.status).to eq(201) end - it "should not create new project without name" do - expect { post api("/projects", user) }.to_not change {Project.count} + it 'should create last project before reaching project limit' do + allow_any_instance_of(User).to receive(:projects_limit_left).and_return(1) + post api('/projects', user2), name: 'foo' + expect(response.status).to eq(201) end - it "should return a 400 error if name not given" do - post api("/projects", user) - response.status.should == 400 - end - - it "should create last project before reaching project limit" do - (1..user2.projects_limit-1).each { |p| post api("/projects", user2), name: "foo#{p}" } - post api("/projects", user2), name: "foo" - response.status.should == 201 - end - - it "should respond with 201 on success" do - post api("/projects", user), name: 'foo' - response.status.should == 201 - end - - it "should respond with 400 if name is not given" do - post api("/projects", user) - response.status.should == 400 - end - - it "should return a 403 error if project limit reached" do - (1..user.projects_limit).each do |p| - post api("/projects", user), name: "foo#{p}" - end - post api("/projects", user), name: 'bar' - response.status.should == 403 + it 'should not create new project without name and return 400' do + expect { post api('/projects', user) }.to_not change { Project.count } + expect(response.status).to eq(400) end it "should assign attributes to project" do project = attributes_for(:project, { + path: 'camelCasePath', description: Faker::Lorem.sentence, issues_enabled: false, merge_requests_enabled: false, wiki_enabled: false }) - post api("/projects", user), project + post api('/projects', user), project project.each_pair do |k,v| - next if k == :path - json_response[k.to_s].should == v + expect(json_response[k.to_s]).to eq(v) end end - it "should set a project as public" do + it 'should set a project as public' do project = attributes_for(:project, :public) - post api("/projects", user), project - json_response['public'].should be_true - json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC + post api('/projects', user), project + expect(json_response['public']).to be_truthy + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC) end - it "should set a project as public using :public" do + it 'should set a project as public using :public' do project = attributes_for(:project, { public: true }) - post api("/projects", user), project - json_response['public'].should be_true - json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC + post api('/projects', user), project + expect(json_response['public']).to be_truthy + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC) end - it "should set a project as internal" do + it 'should set a project as internal' do project = attributes_for(:project, :internal) - post api("/projects", user), project - json_response['public'].should be_false - json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL + post api('/projects', user), project + expect(json_response['public']).to be_falsey + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL) end - it "should set a project as internal overriding :public" do + it 'should set a project as internal overriding :public' do project = attributes_for(:project, :internal, { public: true }) - post api("/projects", user), project - json_response['public'].should be_false - json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL + post api('/projects', user), project + expect(json_response['public']).to be_falsey + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL) end - it "should set a project as private" do + it 'should set a project as private' do project = attributes_for(:project, :private) - post api("/projects", user), project - json_response['public'].should be_false - json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE + post api('/projects', user), project + expect(json_response['public']).to be_falsey + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE) end - it "should set a project as private using :public" do + it 'should set a project as private using :public' do project = attributes_for(:project, { public: false }) - post api("/projects", user), project - json_response['public'].should be_false - json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE + post api('/projects', user), project + expect(json_response['public']).to be_falsey + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE) + end + + context 'when a visibility level is restricted' do + before do + @project = attributes_for(:project, { public: true }) + allow_any_instance_of(ApplicationSetting).to( + receive(:restricted_visibility_levels).and_return([20]) + ) + end + + it 'should not allow a non-admin to use a restricted visibility level' do + post api('/projects', user), @project + expect(response.status).to eq(400) + expect(json_response['message']['visibility_level'].first).to( + match('restricted by your GitLab administrator') + ) + end + + it 'should allow an admin to override restricted visibility settings' do + post api('/projects', admin), @project + expect(json_response['public']).to be_truthy + expect(json_response['visibility_level']).to( + eq(Gitlab::VisibilityLevel::PUBLIC) + ) + end end end - describe "POST /projects/user/:id" do + describe 'POST /projects/user/:id' do before { project } before { admin } - it "should create new project without path" do + it 'should create new project without path and return 201' do expect { post api("/projects/user/#{user.id}", admin), name: 'foo' }.to change {Project.count}.by(1) + expect(response.status).to eq(201) end - it "should not create new project without name" do - expect { post api("/projects/user/#{user.id}", admin) }.to_not change {Project.count} + it 'should respond with 400 on failure and not project' do + expect { post api("/projects/user/#{user.id}", admin) }. + to_not change { Project.count } + + expect(response.status).to eq(400) + expect(json_response['message']['name']).to eq([ + 'can\'t be blank', + 'is too short (minimum is 0 characters)', + Gitlab::Regex.project_name_regex_message + ]) + expect(json_response['message']['path']).to eq([ + 'can\'t be blank', + 'is too short (minimum is 0 characters)', + Gitlab::Regex.send(:project_path_regex_message) + ]) end - it "should respond with 201 on success" do - post api("/projects/user/#{user.id}", admin), name: 'foo' - response.status.should == 201 - end - - it "should respond with 404 on failure" do - post api("/projects/user/#{user.id}", admin) - response.status.should == 404 - end - - it "should assign attributes to project" do + it 'should assign attributes to project' do project = attributes_for(:project, { description: Faker::Lorem.sentence, issues_enabled: false, @@ -205,223 +275,217 @@ describe API::API, api: true do project.each_pair do |k,v| next if k == :path - json_response[k.to_s].should == v + expect(json_response[k.to_s]).to eq(v) end end - it "should set a project as public" do + it 'should set a project as public' do project = attributes_for(:project, :public) post api("/projects/user/#{user.id}", admin), project - json_response['public'].should be_true - json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC + expect(json_response['public']).to be_truthy + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC) end - it "should set a project as public using :public" do + it 'should set a project as public using :public' do project = attributes_for(:project, { public: true }) post api("/projects/user/#{user.id}", admin), project - json_response['public'].should be_true - json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC + expect(json_response['public']).to be_truthy + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PUBLIC) end - it "should set a project as internal" do + it 'should set a project as internal' do project = attributes_for(:project, :internal) post api("/projects/user/#{user.id}", admin), project - json_response['public'].should be_false - json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL + expect(json_response['public']).to be_falsey + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL) end - it "should set a project as internal overriding :public" do + it 'should set a project as internal overriding :public' do project = attributes_for(:project, :internal, { public: true }) post api("/projects/user/#{user.id}", admin), project - json_response['public'].should be_false - json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL + expect(json_response['public']).to be_falsey + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::INTERNAL) end - it "should set a project as private" do + it 'should set a project as private' do project = attributes_for(:project, :private) post api("/projects/user/#{user.id}", admin), project - json_response['public'].should be_false - json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE + expect(json_response['public']).to be_falsey + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE) end - it "should set a project as private using :public" do + it 'should set a project as private using :public' do project = attributes_for(:project, { public: false }) post api("/projects/user/#{user.id}", admin), project - json_response['public'].should be_false - json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE + expect(json_response['public']).to be_falsey + expect(json_response['visibility_level']).to eq(Gitlab::VisibilityLevel::PRIVATE) end end - describe "GET /projects/:id" do + describe 'GET /projects/:id' do before { project } - before { users_project } + before { project_member } - it "should return a project by id" do + it 'should return a project by id' do get api("/projects/#{project.id}", user) - response.status.should == 200 - json_response['name'].should == project.name - json_response['owner']['username'].should == user.username + expect(response.status).to eq(200) + expect(json_response['name']).to eq(project.name) + expect(json_response['owner']['username']).to eq(user.username) end - it "should return a project by path name" do + it 'should return a project by path name' do get api("/projects/#{project.id}", user) - response.status.should == 200 - json_response['name'].should == project.name + expect(response.status).to eq(200) + expect(json_response['name']).to eq(project.name) end - it "should return a 404 error if not found" do - get api("/projects/42", user) - response.status.should == 404 - json_response['message'].should == '404 Not Found' + it 'should return a 404 error if not found' do + get api('/projects/42', user) + expect(response.status).to eq(404) + expect(json_response['message']).to eq('404 Project Not Found') end - it "should return a 404 error if user is not a member" do + it 'should return a 404 error if user is not a member' do other_user = create(:user) get api("/projects/#{project.id}", other_user) - response.status.should == 404 + expect(response.status).to eq(404) end describe 'permissions' do context 'personal project' do - before { get api("/projects/#{project.id}", user) } + it 'Sets project access and returns 200' do + project.team << [user, :master] + get api("/projects/#{project.id}", user) - it { response.status.should == 200 } - it { json_response['permissions']["project_access"]["access_level"].should == Gitlab::Access::MASTER } - it { json_response['permissions']["group_access"].should be_nil } + expect(response.status).to eq(200) + expect(json_response['permissions']['project_access']['access_level']). + to eq(Gitlab::Access::MASTER) + expect(json_response['permissions']['group_access']).to be_nil + end end context 'group project' do - before do + it 'should set the owner and return 200' do project2 = create(:project, group: create(:group)) project2.group.add_owner(user) get api("/projects/#{project2.id}", user) - end - it { response.status.should == 200 } - it { json_response['permissions']["project_access"].should be_nil } - it { json_response['permissions']["group_access"]["access_level"].should == Gitlab::Access::OWNER } + expect(response.status).to eq(200) + expect(json_response['permissions']['project_access']).to be_nil + expect(json_response['permissions']['group_access']['access_level']). + to eq(Gitlab::Access::OWNER) + end end end end - describe "GET /projects/:id/events" do - before { users_project } + describe 'GET /projects/:id/events' do + before { project_member2 } - it "should return a project events" do + it 'should return a project events' do get api("/projects/#{project.id}/events", user) - response.status.should == 200 + expect(response.status).to eq(200) json_event = json_response.first - json_event['action_name'].should == 'joined' - json_event['project_id'].to_i.should == project.id + expect(json_event['action_name']).to eq('joined') + expect(json_event['project_id'].to_i).to eq(project.id) + expect(json_event['author_username']).to eq(user3.username) end - it "should return a 404 error if not found" do - get api("/projects/42/events", user) - response.status.should == 404 - json_response['message'].should == '404 Not Found' + it 'should return a 404 error if not found' do + get api('/projects/42/events', user) + expect(response.status).to eq(404) + expect(json_response['message']).to eq('404 Project Not Found') end - it "should return a 404 error if user is not a member" do + it 'should return a 404 error if user is not a member' do other_user = create(:user) get api("/projects/#{project.id}/events", other_user) - response.status.should == 404 + expect(response.status).to eq(404) end end - describe "GET /projects/:id/snippets" do + describe 'GET /projects/:id/snippets' do before { snippet } - it "should return an array of project snippets" do + it 'should return an array of project snippets' do get api("/projects/#{project.id}/snippets", user) - response.status.should == 200 - json_response.should be_an Array - json_response.first['title'].should == snippet.title + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['title']).to eq(snippet.title) end end - describe "GET /projects/:id/snippets/:snippet_id" do - it "should return a project snippet" do + describe 'GET /projects/:id/snippets/:snippet_id' do + it 'should return a project snippet' do get api("/projects/#{project.id}/snippets/#{snippet.id}", user) - response.status.should == 200 - json_response['title'].should == snippet.title + expect(response.status).to eq(200) + expect(json_response['title']).to eq(snippet.title) end - it "should return a 404 error if snippet id not found" do + it 'should return a 404 error if snippet id not found' do get api("/projects/#{project.id}/snippets/1234", user) - response.status.should == 404 + expect(response.status).to eq(404) end end - describe "POST /projects/:id/snippets" do - it "should create a new project snippet" do + describe 'POST /projects/:id/snippets' do + it 'should create a new project snippet' do post api("/projects/#{project.id}/snippets", user), - title: 'api test', file_name: 'sample.rb', code: 'test' - response.status.should == 201 - json_response['title'].should == 'api test' + title: 'api test', file_name: 'sample.rb', code: 'test', + visibility_level: '0' + expect(response.status).to eq(201) + expect(json_response['title']).to eq('api test') end - it "should return a 400 error if title is not given" do - post api("/projects/#{project.id}/snippets", user), - file_name: 'sample.rb', code: 'test' - response.status.should == 400 - end - - it "should return a 400 error if file_name not given" do - post api("/projects/#{project.id}/snippets", user), - title: 'api test', code: 'test' - response.status.should == 400 - end - - it "should return a 400 error if code not given" do - post api("/projects/#{project.id}/snippets", user), - title: 'api test', file_name: 'sample.rb' - response.status.should == 400 + it 'should return a 400 error if invalid snippet is given' do + post api("/projects/#{project.id}/snippets", user) + expect(status).to eq(400) end end - describe "PUT /projects/:id/snippets/:shippet_id" do - it "should update an existing project snippet" do + describe 'PUT /projects/:id/snippets/:shippet_id' do + it 'should update an existing project snippet' do put api("/projects/#{project.id}/snippets/#{snippet.id}", user), code: 'updated code' - response.status.should == 200 - json_response['title'].should == 'example' - snippet.reload.content.should == 'updated code' + expect(response.status).to eq(200) + expect(json_response['title']).to eq('example') + expect(snippet.reload.content).to eq('updated code') end - it "should update an existing project snippet with new title" do + it 'should update an existing project snippet with new title' do put api("/projects/#{project.id}/snippets/#{snippet.id}", user), title: 'other api test' - response.status.should == 200 - json_response['title'].should == 'other api test' + expect(response.status).to eq(200) + expect(json_response['title']).to eq('other api test') end end - describe "DELETE /projects/:id/snippets/:snippet_id" do + describe 'DELETE /projects/:id/snippets/:snippet_id' do before { snippet } - it "should delete existing project snippet" do + it 'should delete existing project snippet' do expect { delete api("/projects/#{project.id}/snippets/#{snippet.id}", user) }.to change { Snippet.count }.by(-1) - response.status.should == 200 + expect(response.status).to eq(200) end - it "should return success when deleting unknown snippet id" do + it 'should return 404 when deleting unknown snippet id' do delete api("/projects/#{project.id}/snippets/1234", user) - response.status.should == 200 + expect(response.status).to eq(404) end end - describe "GET /projects/:id/snippets/:snippet_id/raw" do - it "should get a raw project snippet" do + describe 'GET /projects/:id/snippets/:snippet_id/raw' do + it 'should get a raw project snippet' do get api("/projects/#{project.id}/snippets/#{snippet.id}/raw", user) - response.status.should == 200 + expect(response.status).to eq(200) end - it "should return a 404 error if raw project snippet not found" do + it 'should return a 404 error if raw project snippet not found' do get api("/projects/#{project.id}/snippets/5555/raw", user) - response.status.should == 404 + expect(response.status).to eq(404) end end @@ -429,37 +493,51 @@ describe API::API, api: true do let(:deploy_keys_project) { create(:deploy_keys_project, project: project) } let(:deploy_key) { deploy_keys_project.deploy_key } - describe "GET /projects/:id/keys" do + describe 'GET /projects/:id/keys' do before { deploy_key } - it "should return array of ssh keys" do + it 'should return array of ssh keys' do get api("/projects/#{project.id}/keys", user) - response.status.should == 200 - json_response.should be_an Array - json_response.first['title'].should == deploy_key.title + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['title']).to eq(deploy_key.title) end end - describe "GET /projects/:id/keys/:key_id" do - it "should return a single key" do + describe 'GET /projects/:id/keys/:key_id' do + it 'should return a single key' do get api("/projects/#{project.id}/keys/#{deploy_key.id}", user) - response.status.should == 200 - json_response['title'].should == deploy_key.title + expect(response.status).to eq(200) + expect(json_response['title']).to eq(deploy_key.title) end - it "should return 404 Not Found with invalid ID" do + it 'should return 404 Not Found with invalid ID' do get api("/projects/#{project.id}/keys/404", user) - response.status.should == 404 + expect(response.status).to eq(404) end end - describe "POST /projects/:id/keys" do - it "should not create an invalid ssh key" do - post api("/projects/#{project.id}/keys", user), { title: "invalid key" } - response.status.should == 404 + describe 'POST /projects/:id/keys' do + it 'should not create an invalid ssh key' do + post api("/projects/#{project.id}/keys", user), { title: 'invalid key' } + expect(response.status).to eq(400) + expect(json_response['message']['key']).to eq([ + 'can\'t be blank', + 'is too short (minimum is 0 characters)', + 'is invalid' + ]) end - it "should create new ssh key" do + it 'should not create a key without title' do + post api("/projects/#{project.id}/keys", user), key: 'some key' + expect(response.status).to eq(400) + expect(json_response['message']['title']).to eq([ + 'can\'t be blank', + 'is too short (minimum is 0 characters)' + ]) + end + + it 'should create new ssh key' do key_attrs = attributes_for :key expect { post api("/projects/#{project.id}/keys", user), key_attrs @@ -467,18 +545,18 @@ describe API::API, api: true do end end - describe "DELETE /projects/:id/keys/:key_id" do + describe 'DELETE /projects/:id/keys/:key_id' do before { deploy_key } - it "should delete existing key" do + it 'should delete existing key' do expect { delete api("/projects/#{project.id}/keys/#{deploy_key.id}", user) }.to change{ project.deploy_keys.count }.by(-1) end - it "should return 404 Not Found with invalid ID" do + it 'should return 404 Not Found with invalid ID' do delete api("/projects/#{project.id}/keys/404", user) - response.status.should == 404 + expect(response.status).to eq(404) end end end @@ -487,70 +565,70 @@ describe API::API, api: true do let(:project_fork_target) { create(:project) } let(:project_fork_source) { create(:project, :public) } - describe "POST /projects/:id/fork/:forked_from_id" do + describe 'POST /projects/:id/fork/:forked_from_id' do let(:new_project_fork_source) { create(:project, :public) } it "shouldn't available for non admin users" do post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user) - response.status.should == 403 + expect(response.status).to eq(403) end - it "should allow project to be forked from an existing project" do - project_fork_target.forked?.should_not be_true + it 'should allow project to be forked from an existing project' do + expect(project_fork_target.forked?).not_to be_truthy post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin) - response.status.should == 201 + expect(response.status).to eq(201) project_fork_target.reload - project_fork_target.forked_from_project.id.should == project_fork_source.id - project_fork_target.forked_project_link.should_not be_nil - project_fork_target.forked?.should be_true + expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id) + expect(project_fork_target.forked_project_link).not_to be_nil + expect(project_fork_target.forked?).to be_truthy end - it "should fail if forked_from project which does not exist" do + it 'should fail if forked_from project which does not exist' do post api("/projects/#{project_fork_target.id}/fork/9999", admin) - response.status.should == 404 + expect(response.status).to eq(404) end - it "should fail with 409 if already forked" do + it 'should fail with 409 if already forked' do post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin) project_fork_target.reload - project_fork_target.forked_from_project.id.should == project_fork_source.id + expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id) post api("/projects/#{project_fork_target.id}/fork/#{new_project_fork_source.id}", admin) - response.status.should == 409 + expect(response.status).to eq(409) project_fork_target.reload - project_fork_target.forked_from_project.id.should == project_fork_source.id - project_fork_target.forked?.should be_true + expect(project_fork_target.forked_from_project.id).to eq(project_fork_source.id) + expect(project_fork_target.forked?).to be_truthy end end - describe "DELETE /projects/:id/fork" do + describe 'DELETE /projects/:id/fork' do it "shouldn't available for non admin users" do delete api("/projects/#{project_fork_target.id}/fork", user) - response.status.should == 403 + expect(response.status).to eq(403) end - it "should make forked project unforked" do + it 'should make forked project unforked' do post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", admin) project_fork_target.reload - project_fork_target.forked_from_project.should_not be_nil - project_fork_target.forked?.should be_true + expect(project_fork_target.forked_from_project).not_to be_nil + expect(project_fork_target.forked?).to be_truthy delete api("/projects/#{project_fork_target.id}/fork", admin) - response.status.should == 200 + expect(response.status).to eq(200) project_fork_target.reload - project_fork_target.forked_from_project.should be_nil - project_fork_target.forked?.should_not be_true + expect(project_fork_target.forked_from_project).to be_nil + expect(project_fork_target.forked?).not_to be_truthy end - it "should be idempotent if not forked" do - project_fork_target.forked_from_project.should be_nil + it 'should be idempotent if not forked' do + expect(project_fork_target.forked_from_project).to be_nil delete api("/projects/#{project_fork_target.id}/fork", admin) - response.status.should == 200 - project_fork_target.reload.forked_from_project.should be_nil + expect(response.status).to eq(200) + expect(project_fork_target.reload.forked_from_project).to be_nil end end end - describe "GET /projects/search/:query" do + describe 'GET /projects/search/:query' do let!(:query) { 'query'} let!(:search) { create(:empty_project, name: query, creator_id: user.id, namespace: user.namespace) } let!(:pre) { create(:empty_project, name: "pre_#{query}", creator_id: user.id, namespace: user.namespace) } @@ -562,68 +640,185 @@ describe API::API, api: true do let!(:public) { create(:empty_project, :public, name: "public #{query}") } let!(:unfound_public) { create(:empty_project, :public, name: 'unfound public') } - context "when unauthenticated" do - it "should return authentication error" do + context 'when unauthenticated' do + it 'should return authentication error' do get api("/projects/search/#{query}") - response.status.should == 401 + expect(response.status).to eq(401) end end - context "when authenticated" do - it "should return an array of projects" do + context 'when authenticated' do + it 'should return an array of projects' do get api("/projects/search/#{query}",user) - response.status.should == 200 - json_response.should be_an Array - json_response.size.should == 6 - json_response.each {|project| project['name'].should =~ /.*query.*/} + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(6) + json_response.each {|project| expect(project['name']).to match(/.*query.*/)} end end - context "when authenticated as a different user" do - it "should return matching public projects" do + context 'when authenticated as a different user' do + it 'should return matching public projects' do get api("/projects/search/#{query}", user2) - response.status.should == 200 - json_response.should be_an Array - json_response.size.should == 2 - json_response.each {|project| project['name'].should =~ /(internal|public) query/} + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.size).to eq(2) + json_response.each {|project| expect(project['name']).to match(/(internal|public) query/)} end end end - describe "DELETE /projects/:id" do - context "when authenticated as user" do - it "should remove project" do - delete api("/projects/#{project.id}", user) - response.status.should == 200 - end + describe 'PUT /projects/:id̈́' do + before { project } + before { user } + before { user3 } + before { user4 } + before { project3 } + before { project4 } + before { project_member3 } + before { project_member2 } - it "should not remove a project if not an owner" do - user3 = create(:user) - project.team << [user3, :developer] - delete api("/projects/#{project.id}", user3) - response.status.should == 403 - end - - it "should not remove a non existing project" do - delete api("/projects/1328", user) - response.status.should == 404 - end - - it "should not remove a project not attached to user" do - delete api("/projects/#{project.id}", user2) - response.status.should == 404 + context 'when unauthenticated' do + it 'should return authentication error' do + project_param = { name: 'bar' } + put api("/projects/#{project.id}"), project_param + expect(response.status).to eq(401) end end - context "when authenticated as admin" do - it "should remove any existing project" do - delete api("/projects/#{project.id}", admin) - response.status.should == 200 + context 'when authenticated as project owner' do + it 'should update name' do + project_param = { name: 'bar' } + put api("/projects/#{project.id}", user), project_param + expect(response.status).to eq(200) + project_param.each_pair do |k, v| + expect(json_response[k.to_s]).to eq(v) + end end - it "should not remove a non existing project" do - delete api("/projects/1328", admin) - response.status.should == 404 + it 'should update visibility_level' do + project_param = { visibility_level: 20 } + put api("/projects/#{project3.id}", user), project_param + expect(response.status).to eq(200) + project_param.each_pair do |k, v| + expect(json_response[k.to_s]).to eq(v) + end + end + + it 'should not update name to existing name' do + project_param = { name: project3.name } + put api("/projects/#{project.id}", user), project_param + expect(response.status).to eq(400) + expect(json_response['message']['name']).to eq(['has already been taken']) + end + + it 'should update path & name to existing path & name in different namespace' do + project_param = { path: project4.path, name: project4.name } + put api("/projects/#{project3.id}", user), project_param + expect(response.status).to eq(200) + project_param.each_pair do |k, v| + expect(json_response[k.to_s]).to eq(v) + end + end + end + + context 'when authenticated as project master' do + it 'should update path' do + project_param = { path: 'bar' } + put api("/projects/#{project3.id}", user4), project_param + expect(response.status).to eq(200) + project_param.each_pair do |k, v| + expect(json_response[k.to_s]).to eq(v) + end + end + + it 'should update other attributes' do + project_param = { issues_enabled: true, + wiki_enabled: true, + snippets_enabled: true, + merge_requests_enabled: true, + description: 'new description' } + + put api("/projects/#{project3.id}", user4), project_param + expect(response.status).to eq(200) + project_param.each_pair do |k, v| + expect(json_response[k.to_s]).to eq(v) + end + end + + it 'should not update path to existing path' do + project_param = { path: project.path } + put api("/projects/#{project3.id}", user4), project_param + expect(response.status).to eq(400) + expect(json_response['message']['path']).to eq(['has already been taken']) + end + + it 'should not update name' do + project_param = { name: 'bar' } + put api("/projects/#{project3.id}", user4), project_param + expect(response.status).to eq(403) + end + + it 'should not update visibility_level' do + project_param = { visibility_level: 20 } + put api("/projects/#{project3.id}", user4), project_param + expect(response.status).to eq(403) + end + end + + context 'when authenticated as project developer' do + it 'should not update other attributes' do + project_param = { path: 'bar', + issues_enabled: true, + wiki_enabled: true, + snippets_enabled: true, + merge_requests_enabled: true, + description: 'new description' } + put api("/projects/#{project.id}", user3), project_param + expect(response.status).to eq(403) + end + end + end + + describe 'DELETE /projects/:id' do + context 'when authenticated as user' do + it 'should remove project' do + expect(GitlabShellWorker).to( + receive(:perform_async).with(:remove_repository, + /#{project.path_with_namespace}/) + ).twice + + delete api("/projects/#{project.id}", user) + expect(response.status).to eq(200) + end + + it 'should not remove a project if not an owner' do + user3 = create(:user) + project.team << [user3, :developer] + delete api("/projects/#{project.id}", user3) + expect(response.status).to eq(403) + end + + it 'should not remove a non existing project' do + delete api('/projects/1328', user) + expect(response.status).to eq(404) + end + + it 'should not remove a project not attached to user' do + delete api("/projects/#{project.id}", user2) + expect(response.status).to eq(404) + end + end + + context 'when authenticated as admin' do + it 'should remove any existing project' do + delete api("/projects/#{project.id}", admin) + expect(response.status).to eq(200) + end + + it 'should not remove a non existing project' do + delete api('/projects/1328', admin) + expect(response.status).to eq(404) end end end diff --git a/spec/requests/api/repositories_spec.rb b/spec/requests/api/repositories_spec.rb index f8603e11a0..09a79553f7 100644 --- a/spec/requests/api/repositories_spec.rb +++ b/spec/requests/api/repositories_spec.rb @@ -8,36 +8,81 @@ describe API::API, api: true do let(:user) { create(:user) } let(:user2) { create(:user) } let!(:project) { create(:project, creator_id: user.id) } - let!(:master) { create(:users_project, user: user, project: project, project_access: UsersProject::MASTER) } - let!(:guest) { create(:users_project, user: user2, project: project, project_access: UsersProject::GUEST) } - - before { project.team << [user, :reporter] } + let!(:master) { create(:project_member, user: user, project: project, access_level: ProjectMember::MASTER) } + let!(:guest) { create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST) } describe "GET /projects/:id/repository/tags" do it "should return an array of project tags" do get api("/projects/#{project.id}/repository/tags", user) - response.status.should == 200 - json_response.should be_an Array - json_response.first['name'].should == project.repo.tags.sort_by(&:name).reverse.first.name + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['name']).to eq(project.repo.tags.sort_by(&:name).reverse.first.name) end end describe 'POST /projects/:id/repository/tags' do - it 'should create a new tag' do - post api("/projects/#{project.id}/repository/tags", user), - tag_name: 'v1.0.0', - ref: 'master' + context 'lightweight tags' do + it 'should create a new tag' do + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v7.0.1', + ref: 'master' - response.status.should == 201 - json_response['name'].should == 'v1.0.0' + expect(response.status).to eq(201) + expect(json_response['name']).to eq('v7.0.1') + end + end + + context 'annotated tag' do + it 'should create a new annotated tag' do + # Identity must be set in .gitconfig to create annotated tag. + repo_path = project.repository.path_to_repo + system(*%W(git --git-dir=#{repo_path} config user.name #{user.name})) + system(*%W(git --git-dir=#{repo_path} config user.email #{user.email})) + + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v7.1.0', + ref: 'master', + message: 'Release 7.1.0' + + expect(response.status).to eq(201) + expect(json_response['name']).to eq('v7.1.0') + expect(json_response['message']).to eq('Release 7.1.0') + end end it 'should deny for user without push access' do post api("/projects/#{project.id}/repository/tags", user2), - tag_name: 'v1.0.0', + tag_name: 'v1.9.0', ref: '621491c677087aa243f165eab467bfdfbee00be1' + expect(response.status).to eq(403) + end - response.status.should == 403 + it 'should return 400 if tag name is invalid' do + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v 1.0.0', + ref: 'master' + expect(response.status).to eq(400) + expect(json_response['message']).to eq('Tag name invalid') + end + + it 'should return 400 if tag already exists' do + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v8.0.0', + ref: 'master' + expect(response.status).to eq(201) + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'v8.0.0', + ref: 'master' + expect(response.status).to eq(400) + expect(json_response['message']).to eq('Tag already exists') + end + + it 'should return 400 if ref name is invalid' do + post api("/projects/#{project.id}/repository/tags", user), + tag_name: 'mytag', + ref: 'foo' + expect(response.status).to eq(400) + expect(json_response['message']).to eq('Invalid reference name') end end @@ -47,19 +92,27 @@ describe API::API, api: true do it "should return project commits" do get api("/projects/#{project.id}/repository/tree", user) - response.status.should == 200 + expect(response.status).to eq(200) - json_response.should be_an Array - json_response.first['name'].should == 'encoding' - json_response.first['type'].should == 'tree' - json_response.first['mode'].should == '040000' + expect(json_response).to be_an Array + expect(json_response.first['name']).to eq('encoding') + expect(json_response.first['type']).to eq('tree') + expect(json_response.first['mode']).to eq('040000') + end + + it 'should return a 404 for unknown ref' do + get api("/projects/#{project.id}/repository/tree?ref_name=foo", user) + expect(response.status).to eq(404) + + expect(json_response).to be_an Object + json_response['message'] == '404 Tree Not Found' end end context "unauthorized user" do it "should not return project commits" do get api("/projects/#{project.id}/repository/tree") - response.status.should == 401 + expect(response.status).to eq(401) end end end @@ -67,36 +120,44 @@ describe API::API, api: true do describe "GET /projects/:id/repository/blobs/:sha" do it "should get the raw file contents" do get api("/projects/#{project.id}/repository/blobs/master?filepath=README.md", user) - response.status.should == 200 + expect(response.status).to eq(200) end it "should return 404 for invalid branch_name" do get api("/projects/#{project.id}/repository/blobs/invalid_branch_name?filepath=README.md", user) - response.status.should == 404 + expect(response.status).to eq(404) end it "should return 404 for invalid file" do get api("/projects/#{project.id}/repository/blobs/master?filepath=README.invalid", user) - response.status.should == 404 + expect(response.status).to eq(404) end it "should return a 400 error if filepath is missing" do get api("/projects/#{project.id}/repository/blobs/master", user) - response.status.should == 400 + expect(response.status).to eq(400) end end describe "GET /projects/:id/repository/commits/:sha/blob" do it "should get the raw file contents" do get api("/projects/#{project.id}/repository/commits/master/blob?filepath=README.md", user) - response.status.should == 200 + expect(response.status).to eq(200) end end describe "GET /projects/:id/repository/raw_blobs/:sha" do it "should get the raw file contents" do get api("/projects/#{project.id}/repository/raw_blobs/#{sample_blob.oid}", user) - response.status.should == 200 + expect(response.status).to eq(200) + end + + it 'should return a 404 for unknown blob' do + get api("/projects/#{project.id}/repository/raw_blobs/123456", user) + expect(response.status).to eq(404) + + expect(json_response).to be_an Object + json_response['message'] == '404 Blob Not Found' end end @@ -104,83 +165,83 @@ describe API::API, api: true do it "should get the archive" do get api("/projects/#{project.id}/repository/archive", user) repo_name = project.repository.name.gsub("\.git", "") - response.status.should == 200 - response.headers['Content-Disposition'].should =~ /filename\=\"#{repo_name}\-[^\.]+\.tar.gz\"/ - response.content_type.should == MIME::Types.type_for('file.tar.gz').first.content_type + expect(response.status).to eq(200) + expect(response.headers['Content-Disposition']).to match(/filename\=\"#{repo_name}\-[^\.]+\.tar.gz\"/) + expect(response.content_type).to eq(MIME::Types.type_for('file.tar.gz').first.content_type) end it "should get the archive.zip" do get api("/projects/#{project.id}/repository/archive.zip", user) repo_name = project.repository.name.gsub("\.git", "") - response.status.should == 200 - response.headers['Content-Disposition'].should =~ /filename\=\"#{repo_name}\-[^\.]+\.zip\"/ - response.content_type.should == MIME::Types.type_for('file.zip').first.content_type + expect(response.status).to eq(200) + expect(response.headers['Content-Disposition']).to match(/filename\=\"#{repo_name}\-[^\.]+\.zip\"/) + expect(response.content_type).to eq(MIME::Types.type_for('file.zip').first.content_type) end it "should get the archive.tar.bz2" do get api("/projects/#{project.id}/repository/archive.tar.bz2", user) repo_name = project.repository.name.gsub("\.git", "") - response.status.should == 200 - response.headers['Content-Disposition'].should =~ /filename\=\"#{repo_name}\-[^\.]+\.tar.bz2\"/ - response.content_type.should == MIME::Types.type_for('file.tar.bz2').first.content_type + expect(response.status).to eq(200) + expect(response.headers['Content-Disposition']).to match(/filename\=\"#{repo_name}\-[^\.]+\.tar.bz2\"/) + expect(response.content_type).to eq(MIME::Types.type_for('file.tar.bz2').first.content_type) end it "should return 404 for invalid sha" do get api("/projects/#{project.id}/repository/archive/?sha=xxx", user) - response.status.should == 404 + expect(response.status).to eq(404) end end describe 'GET /projects/:id/repository/compare' do it "should compare branches" do get api("/projects/#{project.id}/repository/compare", user), from: 'master', to: 'feature' - response.status.should == 200 - json_response['commits'].should be_present - json_response['diffs'].should be_present + expect(response.status).to eq(200) + expect(json_response['commits']).to be_present + expect(json_response['diffs']).to be_present end it "should compare tags" do get api("/projects/#{project.id}/repository/compare", user), from: 'v1.0.0', to: 'v1.1.0' - response.status.should == 200 - json_response['commits'].should be_present - json_response['diffs'].should be_present + expect(response.status).to eq(200) + expect(json_response['commits']).to be_present + expect(json_response['diffs']).to be_present end it "should compare commits" do get api("/projects/#{project.id}/repository/compare", user), from: sample_commit.id, to: sample_commit.parent_id - response.status.should == 200 - json_response['commits'].should be_empty - json_response['diffs'].should be_empty - json_response['compare_same_ref'].should be_false + expect(response.status).to eq(200) + expect(json_response['commits']).to be_empty + expect(json_response['diffs']).to be_empty + expect(json_response['compare_same_ref']).to be_falsey end it "should compare commits in reverse order" do get api("/projects/#{project.id}/repository/compare", user), from: sample_commit.parent_id, to: sample_commit.id - response.status.should == 200 - json_response['commits'].should be_present - json_response['diffs'].should be_present + expect(response.status).to eq(200) + expect(json_response['commits']).to be_present + expect(json_response['diffs']).to be_present end it "should compare same refs" do get api("/projects/#{project.id}/repository/compare", user), from: 'master', to: 'master' - response.status.should == 200 - json_response['commits'].should be_empty - json_response['diffs'].should be_empty - json_response['compare_same_ref'].should be_true + expect(response.status).to eq(200) + expect(json_response['commits']).to be_empty + expect(json_response['diffs']).to be_empty + expect(json_response['compare_same_ref']).to be_truthy end end describe 'GET /projects/:id/repository/contributors' do it 'should return valid data' do get api("/projects/#{project.id}/repository/contributors", user) - response.status.should == 200 - json_response.should be_an Array + expect(response.status).to eq(200) + expect(json_response).to be_an Array contributor = json_response.first - contributor['email'].should == 'dmitriy.zaporozhets@gmail.com' - contributor['name'].should == 'Dmitriy Zaporozhets' - contributor['commits'].should == 13 - contributor['additions'].should == 4081 - contributor['deletions'].should == 29 + expect(contributor['email']).to eq('dmitriy.zaporozhets@gmail.com') + expect(contributor['name']).to eq('Dmitriy Zaporozhets') + expect(contributor['commits']).to eq(13) + expect(contributor['additions']).to eq(0) + expect(contributor['deletions']).to eq(0) end end end diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb index f883c9e028..51c543578d 100644 --- a/spec/requests/api/services_spec.rb +++ b/spec/requests/api/services_spec.rb @@ -9,13 +9,13 @@ describe API::API, api: true do it "should update gitlab-ci settings" do put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'secret-token', project_url: "http://ci.example.com/projects/1" - response.status.should == 200 + expect(response.status).to eq(200) end it "should return if required fields missing" do put api("/projects/#{project.id}/services/gitlab-ci", user), project_url: "http://ci.example.com/projects/1", active: true - response.status.should == 400 + expect(response.status).to eq(400) end end @@ -23,8 +23,34 @@ describe API::API, api: true do it "should update gitlab-ci settings" do delete api("/projects/#{project.id}/services/gitlab-ci", user) - response.status.should == 200 - project.gitlab_ci_service.should be_nil + expect(response.status).to eq(200) + expect(project.gitlab_ci_service).to be_nil + end + end + + describe 'PUT /projects/:id/services/hipchat' do + it 'should update hipchat settings' do + put api("/projects/#{project.id}/services/hipchat", user), + token: 'secret-token', room: 'test' + + expect(response.status).to eq(200) + expect(project.hipchat_service).not_to be_nil + end + + it 'should return if required fields missing' do + put api("/projects/#{project.id}/services/gitlab-ci", user), + token: 'secret-token', active: true + + expect(response.status).to eq(400) + end + end + + describe 'DELETE /projects/:id/services/hipchat' do + it 'should delete hipchat settings' do + delete api("/projects/#{project.id}/services/hipchat", user) + + expect(response.status).to eq(200) + expect(project.hipchat_service).to be_nil end end end diff --git a/spec/requests/api/session_spec.rb b/spec/requests/api/session_spec.rb index 013f425d6c..fbd57b34a5 100644 --- a/spec/requests/api/session_spec.rb +++ b/spec/requests/api/session_spec.rb @@ -9,43 +9,69 @@ describe API::API, api: true do context "when valid password" do it "should return private token" do post api("/session"), email: user.email, password: '12345678' - response.status.should == 201 + expect(response.status).to eq(201) - json_response['email'].should == user.email - json_response['private_token'].should == user.private_token - json_response['is_admin'].should == user.is_admin? - json_response['can_create_project'].should == user.can_create_project? - json_response['can_create_group'].should == user.can_create_group? + expect(json_response['email']).to eq(user.email) + expect(json_response['private_token']).to eq(user.private_token) + expect(json_response['is_admin']).to eq(user.is_admin?) + expect(json_response['can_create_project']).to eq(user.can_create_project?) + expect(json_response['can_create_group']).to eq(user.can_create_group?) + end + end + + context 'when email has case-typo and password is valid' do + it 'should return private token' do + post api('/session'), email: user.email.upcase, password: '12345678' + expect(response.status).to eq 201 + + expect(json_response['email']).to eq user.email + expect(json_response['private_token']).to eq user.private_token + expect(json_response['is_admin']).to eq user.is_admin? + expect(json_response['can_create_project']).to eq user.can_create_project? + expect(json_response['can_create_group']).to eq user.can_create_group? + end + end + + context 'when login has case-typo and password is valid' do + it 'should return private token' do + post api('/session'), login: user.username.upcase, password: '12345678' + expect(response.status).to eq 201 + + expect(json_response['email']).to eq user.email + expect(json_response['private_token']).to eq user.private_token + expect(json_response['is_admin']).to eq user.is_admin? + expect(json_response['can_create_project']).to eq user.can_create_project? + expect(json_response['can_create_group']).to eq user.can_create_group? end end context "when invalid password" do it "should return authentication error" do post api("/session"), email: user.email, password: '123' - response.status.should == 401 + expect(response.status).to eq(401) - json_response['email'].should be_nil - json_response['private_token'].should be_nil + expect(json_response['email']).to be_nil + expect(json_response['private_token']).to be_nil end end context "when empty password" do it "should return authentication error" do post api("/session"), email: user.email - response.status.should == 401 + expect(response.status).to eq(401) - json_response['email'].should be_nil - json_response['private_token'].should be_nil + expect(json_response['email']).to be_nil + expect(json_response['private_token']).to be_nil end end context "when empty name" do it "should return authentication error" do post api("/session"), password: user.password - response.status.should == 401 + expect(response.status).to eq(401) - json_response['email'].should be_nil - json_response['private_token'].should be_nil + expect(json_response['email']).to be_nil + expect(json_response['private_token']).to be_nil end end end diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb index 5784ae8c23..a9d86bbce6 100644 --- a/spec/requests/api/system_hooks_spec.rb +++ b/spec/requests/api/system_hooks_spec.rb @@ -13,23 +13,23 @@ describe API::API, api: true do context "when no user" do it "should return authentication error" do get api("/hooks") - response.status.should == 401 + expect(response.status).to eq(401) end end context "when not an admin" do it "should return forbidden error" do get api("/hooks", user) - response.status.should == 403 + expect(response.status).to eq(403) end end context "when authenticated as admin" do it "should return an array of hooks" do get api("/hooks", admin) - response.status.should == 200 - json_response.should be_an Array - json_response.first['url'].should == hook.url + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['url']).to eq(hook.url) end end end @@ -43,7 +43,7 @@ describe API::API, api: true do it "should respond with 400 if url not given" do post api("/hooks", admin) - response.status.should == 400 + expect(response.status).to eq(400) end it "should not create new hook without url" do @@ -56,13 +56,13 @@ describe API::API, api: true do describe "GET /hooks/:id" do it "should return hook by id" do get api("/hooks/#{hook.id}", admin) - response.status.should == 200 - json_response['event_name'].should == 'project_create' + expect(response.status).to eq(200) + expect(json_response['event_name']).to eq('project_create') end it "should return 404 on failure" do get api("/hooks/404", admin) - response.status.should == 404 + expect(response.status).to eq(404) end end @@ -75,7 +75,7 @@ describe API::API, api: true do it "should return success if hook id not found" do delete api("/hooks/12345", admin) - response.status.should == 200 + expect(response.status).to eq(200) end end end diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb index 8bbe9b5b73..e6d5545f81 100644 --- a/spec/requests/api/users_spec.rb +++ b/spec/requests/api/users_spec.rb @@ -11,27 +11,30 @@ describe API::API, api: true do context "when unauthenticated" do it "should return authentication error" do get api("/users") - response.status.should == 401 + expect(response.status).to eq(401) end end context "when authenticated" do it "should return an array of users" do get api("/users", user) - response.status.should == 200 - json_response.should be_an Array - json_response.first['username'].should == user.username + expect(response.status).to eq(200) + expect(json_response).to be_an Array + username = user.username + expect(json_response.detect { + |user| user['username'] == username + }['username']).to eq(username) end end context "when admin" do it "should return an array of users" do get api("/users", admin) - response.status.should == 200 - json_response.should be_an Array - json_response.first.keys.should include 'email' - json_response.first.keys.should include 'extern_uid' - json_response.first.keys.should include 'can_create_project' + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first.keys).to include 'email' + expect(json_response.first.keys).to include 'identities' + expect(json_response.first.keys).to include 'can_create_project' end end end @@ -39,18 +42,19 @@ describe API::API, api: true do describe "GET /users/:id" do it "should return a user by id" do get api("/users/#{user.id}", user) - response.status.should == 200 - json_response['username'].should == user.username + expect(response.status).to eq(200) + expect(json_response['username']).to eq(user.username) end it "should return a 401 if unauthenticated" do get api("/users/9998") - response.status.should == 401 + expect(response.status).to eq(401) end it "should return a 404 error if user id not found" do get api("/users/9999", user) - response.status.should == 404 + expect(response.status).to eq(404) + expect(json_response['message']).to eq('404 Not found') end end @@ -65,99 +69,126 @@ describe API::API, api: true do it "should create user with correct attributes" do post api('/users', admin), attributes_for(:user, admin: true, can_create_group: true) - response.status.should == 201 + expect(response.status).to eq(201) user_id = json_response['id'] new_user = User.find(user_id) - new_user.should_not == nil - new_user.admin.should == true - new_user.can_create_group.should == true + expect(new_user).not_to eq(nil) + expect(new_user.admin).to eq(true) + expect(new_user.can_create_group).to eq(true) end it "should create non-admin user" do post api('/users', admin), attributes_for(:user, admin: false, can_create_group: false) - response.status.should == 201 + expect(response.status).to eq(201) user_id = json_response['id'] new_user = User.find(user_id) - new_user.should_not == nil - new_user.admin.should == false - new_user.can_create_group.should == false + expect(new_user).not_to eq(nil) + expect(new_user.admin).to eq(false) + expect(new_user.can_create_group).to eq(false) end it "should create non-admin users by default" do post api('/users', admin), attributes_for(:user) - response.status.should == 201 + expect(response.status).to eq(201) user_id = json_response['id'] new_user = User.find(user_id) - new_user.should_not == nil - new_user.admin.should == false + expect(new_user).not_to eq(nil) + expect(new_user.admin).to eq(false) end it "should return 201 Created on success" do post api("/users", admin), attributes_for(:user, projects_limit: 3) - response.status.should == 201 + expect(response.status).to eq(201) end it "should not create user with invalid email" do - post api("/users", admin), { email: "invalid email", password: 'password' } - response.status.should == 400 + post api('/users', admin), + email: 'invalid email', + password: 'password', + name: 'test' + expect(response.status).to eq(400) end - it "should return 400 error if password not given" do - post api("/users", admin), { email: 'test@example.com' } - response.status.should == 400 + it 'should return 400 error if name not given' do + post api('/users', admin), email: 'test@example.com', password: 'pass1234' + expect(response.status).to eq(400) + end + + it 'should return 400 error if password not given' do + post api('/users', admin), email: 'test@example.com', name: 'test' + expect(response.status).to eq(400) end it "should return 400 error if email not given" do - post api("/users", admin), { password: 'pass1234' } - response.status.should == 400 + post api('/users', admin), password: 'pass1234', name: 'test' + expect(response.status).to eq(400) + end + + it 'should return 400 error if user does not validate' do + post api('/users', admin), + password: 'pass', + email: 'test@example.com', + username: 'test!', + name: 'test', + bio: 'g' * 256, + projects_limit: -1 + expect(response.status).to eq(400) + expect(json_response['message']['password']). + to eq(['is too short (minimum is 8 characters)']) + expect(json_response['message']['bio']). + to eq(['is too long (maximum is 255 characters)']) + expect(json_response['message']['projects_limit']). + to eq(['must be greater than or equal to 0']) + expect(json_response['message']['username']). + to eq([Gitlab::Regex.send(:namespace_regex_message)]) end it "shouldn't available for non admin users" do post api("/users", user), attributes_for(:user) - response.status.should == 403 + expect(response.status).to eq(403) end - context "with existing user" do - before { post api("/users", admin), { email: 'test@example.com', password: 'password', username: 'test' } } + context 'with existing user' do + before do + post api('/users', admin), + email: 'test@example.com', + password: 'password', + username: 'test', + name: 'foo' + end - it "should not create user with same email" do + it 'should return 409 conflict error if user with same email exists' do expect { - post api("/users", admin), { email: 'test@example.com', password: 'password' } + post api('/users', admin), + name: 'foo', + email: 'test@example.com', + password: 'password', + username: 'foo' }.to change { User.count }.by(0) + expect(response.status).to eq(409) + expect(json_response['message']).to eq('Email has already been taken') end - it "should return 409 conflict error if user with email exists" do - post api("/users", admin), { email: 'test@example.com', password: 'password' } - end - - it "should return 409 conflict error if same username exists" do - post api("/users", admin), { email: 'foo@example.com', password: 'pass', username: 'test' } + it 'should return 409 conflict error if same username exists' do + expect do + post api('/users', admin), + name: 'foo', + email: 'foo@example.com', + password: 'password', + username: 'test' + end.to change { User.count }.by(0) + expect(response.status).to eq(409) + expect(json_response['message']).to eq('Username has already been taken') end end end describe "GET /users/sign_up" do - context 'enabled' do - before do - Gitlab.config.gitlab.stub(:signup_enabled).and_return(true) - end - it "should return sign up page if signup is enabled" do - get "/users/sign_up" - response.status.should == 200 - end - end - - context 'disabled' do - before do - Gitlab.config.gitlab.stub(:signup_enabled).and_return(false) - end - - it "should redirect to sign in page if signup is disabled" do - get "/users/sign_up" - response.status.should == 302 - response.should redirect_to(new_user_session_path) - end + it "should redirect to sign in page" do + get "/users/sign_up" + expect(response.status).to eq(302) + expect(response).to redirect_to(new_user_session_path) end end @@ -168,59 +199,95 @@ describe API::API, api: true do it "should update user with new bio" do put api("/users/#{user.id}", admin), {bio: 'new test bio'} - response.status.should == 200 - json_response['bio'].should == 'new test bio' - user.reload.bio.should == 'new test bio' + expect(response.status).to eq(200) + expect(json_response['bio']).to eq('new test bio') + expect(user.reload.bio).to eq('new test bio') + end + + it 'should update user with his own email' do + put api("/users/#{user.id}", admin), email: user.email + expect(response.status).to eq(200) + expect(json_response['email']).to eq(user.email) + expect(user.reload.email).to eq(user.email) + end + + it 'should update user with his own username' do + put api("/users/#{user.id}", admin), username: user.username + expect(response.status).to eq(200) + expect(json_response['username']).to eq(user.username) + expect(user.reload.username).to eq(user.username) end it "should update admin status" do put api("/users/#{user.id}", admin), {admin: true} - response.status.should == 200 - json_response['is_admin'].should == true - user.reload.admin.should == true + expect(response.status).to eq(200) + expect(json_response['is_admin']).to eq(true) + expect(user.reload.admin).to eq(true) end it "should not update admin status" do put api("/users/#{admin_user.id}", admin), {can_create_group: false} - response.status.should == 200 - json_response['is_admin'].should == true - admin_user.reload.admin.should == true - admin_user.can_create_group.should == false + expect(response.status).to eq(200) + expect(json_response['is_admin']).to eq(true) + expect(admin_user.reload.admin).to eq(true) + expect(admin_user.can_create_group).to eq(false) end it "should not allow invalid update" do put api("/users/#{user.id}", admin), {email: 'invalid email'} - response.status.should == 404 - user.reload.email.should_not == 'invalid email' + expect(response.status).to eq(400) + expect(user.reload.email).not_to eq('invalid email') end it "shouldn't available for non admin users" do put api("/users/#{user.id}", user), attributes_for(:user) - response.status.should == 403 + expect(response.status).to eq(403) end it "should return 404 for non-existing user" do put api("/users/999999", admin), {bio: 'update should fail'} - response.status.should == 404 + expect(response.status).to eq(404) + expect(json_response['message']).to eq('404 Not found') + end + + it 'should return 400 error if user does not validate' do + put api("/users/#{user.id}", admin), + password: 'pass', + email: 'test@example.com', + username: 'test!', + name: 'test', + bio: 'g' * 256, + projects_limit: -1 + expect(response.status).to eq(400) + expect(json_response['message']['password']). + to eq(['is too short (minimum is 8 characters)']) + expect(json_response['message']['bio']). + to eq(['is too long (maximum is 255 characters)']) + expect(json_response['message']['projects_limit']). + to eq(['must be greater than or equal to 0']) + expect(json_response['message']['username']). + to eq([Gitlab::Regex.send(:namespace_regex_message)]) end context "with existing user" do before { post api("/users", admin), { email: 'test@example.com', password: 'password', username: 'test', name: 'test' } post api("/users", admin), { email: 'foo@bar.com', password: 'password', username: 'john', name: 'john' } - @user_id = User.all.last.id + @user = User.all.last } -# it "should return 409 conflict error if email address exists" do -# put api("/users/#{@user_id}", admin), { email: 'test@example.com' } -# response.status.should == 409 -# end -# -# it "should return 409 conflict error if username taken" do -# @user_id = User.all.last.id -# put api("/users/#{@user_id}", admin), { username: 'test' } -# response.status.should == 409 -# end + it 'should return 409 conflict error if email address exists' do + put api("/users/#{@user.id}", admin), email: 'test@example.com' + expect(response.status).to eq(409) + expect(@user.reload.email).to eq(@user.email) + end + + it 'should return 409 conflict error if username taken' do + @user_id = User.all.last.id + put api("/users/#{@user.id}", admin), username: 'test' + expect(response.status).to eq(409) + expect(@user.reload.username).to eq(@user.username) + end end end @@ -229,7 +296,14 @@ describe API::API, api: true do it "should not create invalid ssh key" do post api("/users/#{user.id}/keys", admin), { title: "invalid key" } - response.status.should == 404 + expect(response.status).to eq(400) + expect(json_response['message']).to eq('400 (Bad request) "key" not given') + end + + it 'should not create key without title' do + post api("/users/#{user.id}/keys", admin), key: 'some key' + expect(response.status).to eq(400) + expect(json_response['message']).to eq('400 (Bad request) "title" not given') end it "should create ssh key" do @@ -246,23 +320,24 @@ describe API::API, api: true do context 'when unauthenticated' do it 'should return authentication error' do get api("/users/#{user.id}/keys") - response.status.should == 401 + expect(response.status).to eq(401) end end context 'when authenticated' do it 'should return 404 for non-existing user' do get api('/users/999999/keys', admin) - response.status.should == 404 + expect(response.status).to eq(404) + expect(json_response['message']).to eq('404 User Not Found') end it 'should return array of ssh keys' do user.keys << key user.save get api("/users/#{user.id}/keys", admin) - response.status.should == 200 - json_response.should be_an Array - json_response.first['title'].should == key.title + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first['title']).to eq(key.title) end end end @@ -273,7 +348,7 @@ describe API::API, api: true do context 'when unauthenticated' do it 'should return authentication error' do delete api("/users/#{user.id}/keys/42") - response.status.should == 401 + expect(response.status).to eq(401) end end @@ -284,19 +359,21 @@ describe API::API, api: true do expect { delete api("/users/#{user.id}/keys/#{key.id}", admin) }.to change { user.keys.count }.by(-1) - response.status.should == 200 + expect(response.status).to eq(200) end it 'should return 404 error if user not found' do user.keys << key user.save delete api("/users/999999/keys/#{key.id}", admin) - response.status.should == 404 + expect(response.status).to eq(404) + expect(json_response['message']).to eq('404 User Not Found') end it 'should return 404 error if key not foud' do delete api("/users/#{user.id}/keys/42", admin) - response.status.should == 404 + expect(response.status).to eq(404) + expect(json_response['message']).to eq('404 Key Not Found') end end end @@ -306,40 +383,42 @@ describe API::API, api: true do it "should delete user" do delete api("/users/#{user.id}", admin) - response.status.should == 200 + expect(response.status).to eq(200) expect { User.find(user.id) }.to raise_error ActiveRecord::RecordNotFound - json_response['email'].should == user.email + expect(json_response['email']).to eq(user.email) end it "should not delete for unauthenticated user" do delete api("/users/#{user.id}") - response.status.should == 401 + expect(response.status).to eq(401) end it "shouldn't available for non admin users" do delete api("/users/#{user.id}", user) - response.status.should == 403 + expect(response.status).to eq(403) end it "should return 404 for non-existing user" do delete api("/users/999999", admin) - response.status.should == 404 + expect(response.status).to eq(404) + expect(json_response['message']).to eq('404 User Not Found') end end describe "GET /user" do it "should return current user" do get api("/user", user) - response.status.should == 200 - json_response['email'].should == user.email - json_response['is_admin'].should == user.is_admin? - json_response['can_create_project'].should == user.can_create_project? - json_response['can_create_group'].should == user.can_create_group? + expect(response.status).to eq(200) + expect(json_response['email']).to eq(user.email) + expect(json_response['is_admin']).to eq(user.is_admin?) + expect(json_response['can_create_project']).to eq(user.can_create_project?) + expect(json_response['can_create_group']).to eq(user.can_create_group?) + expect(json_response['projects_limit']).to eq(user.projects_limit) end it "should return 401 error if user is unauthenticated" do get api("/user") - response.status.should == 401 + expect(response.status).to eq(401) end end @@ -347,7 +426,7 @@ describe API::API, api: true do context "when unauthenticated" do it "should return authentication error" do get api("/user/keys") - response.status.should == 401 + expect(response.status).to eq(401) end end @@ -356,9 +435,9 @@ describe API::API, api: true do user.keys << key user.save get api("/user/keys", user) - response.status.should == 200 - json_response.should be_an Array - json_response.first["title"].should == key.title + expect(response.status).to eq(200) + expect(json_response).to be_an Array + expect(json_response.first["title"]).to eq(key.title) end end end @@ -368,13 +447,14 @@ describe API::API, api: true do user.keys << key user.save get api("/user/keys/#{key.id}", user) - response.status.should == 200 - json_response["title"].should == key.title + expect(response.status).to eq(200) + expect(json_response["title"]).to eq(key.title) end it "should return 404 Not Found within invalid ID" do get api("/user/keys/42", user) - response.status.should == 404 + expect(response.status).to eq(404) + expect(json_response['message']).to eq('404 Not found') end it "should return 404 error if admin accesses user's ssh key" do @@ -382,7 +462,8 @@ describe API::API, api: true do user.save admin get api("/user/keys/#{key.id}", admin) - response.status.should == 404 + expect(response.status).to eq(404) + expect(json_response['message']).to eq('404 Not found') end end @@ -392,22 +473,29 @@ describe API::API, api: true do expect { post api("/user/keys", user), key_attrs }.to change{ user.keys.count }.by(1) - response.status.should == 201 + expect(response.status).to eq(201) end it "should return a 401 error if unauthorized" do post api("/user/keys"), title: 'some title', key: 'some key' - response.status.should == 401 + expect(response.status).to eq(401) end it "should not create ssh key without key" do post api("/user/keys", user), title: 'title' - response.status.should == 400 + expect(response.status).to eq(400) + expect(json_response['message']).to eq('400 (Bad request) "key" not given') + end + + it 'should not create ssh key without title' do + post api('/user/keys', user), key: 'some key' + expect(response.status).to eq(400) + expect(json_response['message']).to eq('400 (Bad request) "title" not given') end it "should not create ssh key without title" do post api("/user/keys", user), key: "somekey" - response.status.should == 400 + expect(response.status).to eq(400) end end @@ -418,19 +506,19 @@ describe API::API, api: true do expect { delete api("/user/keys/#{key.id}", user) }.to change{user.keys.count}.by(-1) - response.status.should == 200 + expect(response.status).to eq(200) end it "should return success if key ID not found" do delete api("/user/keys/42", user) - response.status.should == 200 + expect(response.status).to eq(200) end it "should return 401 error if unauthorized" do user.keys << key user.save delete api("/user/keys/#{key.id}") - response.status.should == 401 + expect(response.status).to eq(401) end end end diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb index 7fe18ff47c..bf8abcfb00 100644 --- a/spec/routing/admin_routing_spec.rb +++ b/spec/routing/admin_routing_spec.rb @@ -12,47 +12,47 @@ require 'spec_helper' # DELETE /admin/users/:id(.:format) admin/users#destroy describe Admin::UsersController, "routing" do it "to #team_update" do - put("/admin/users/1/team_update").should route_to('admin/users#team_update', id: '1') + expect(put("/admin/users/1/team_update")).to route_to('admin/users#team_update', id: '1') end it "to #block" do - put("/admin/users/1/block").should route_to('admin/users#block', id: '1') + expect(put("/admin/users/1/block")).to route_to('admin/users#block', id: '1') end it "to #unblock" do - put("/admin/users/1/unblock").should route_to('admin/users#unblock', id: '1') + expect(put("/admin/users/1/unblock")).to route_to('admin/users#unblock', id: '1') end it "to #index" do - get("/admin/users").should route_to('admin/users#index') + expect(get("/admin/users")).to route_to('admin/users#index') end it "to #show" do - get("/admin/users/1").should route_to('admin/users#show', id: '1') + expect(get("/admin/users/1")).to route_to('admin/users#show', id: '1') end it "to #create" do - post("/admin/users").should route_to('admin/users#create') + expect(post("/admin/users")).to route_to('admin/users#create') end it "to #new" do - get("/admin/users/new").should route_to('admin/users#new') + expect(get("/admin/users/new")).to route_to('admin/users#new') end it "to #edit" do - get("/admin/users/1/edit").should route_to('admin/users#edit', id: '1') + expect(get("/admin/users/1/edit")).to route_to('admin/users#edit', id: '1') end it "to #show" do - get("/admin/users/1").should route_to('admin/users#show', id: '1') + expect(get("/admin/users/1")).to route_to('admin/users#show', id: '1') end it "to #update" do - put("/admin/users/1").should route_to('admin/users#update', id: '1') + expect(put("/admin/users/1")).to route_to('admin/users#update', id: '1') end it "to #destroy" do - delete("/admin/users/1").should route_to('admin/users#destroy', id: '1') + expect(delete("/admin/users/1")).to route_to('admin/users#destroy', id: '1') end end @@ -67,11 +67,11 @@ end # DELETE /admin/projects/:id(.:format) admin/projects#destroy {id: /[^\/]+/} describe Admin::ProjectsController, "routing" do it "to #index" do - get("/admin/projects").should route_to('admin/projects#index') + expect(get("/admin/projects")).to route_to('admin/projects#index') end it "to #show" do - get("/admin/projects/gitlab").should route_to('admin/projects#show', id: 'gitlab') + expect(get("/admin/projects/gitlab")).to route_to('admin/projects#show', namespace_id: 'gitlab') end end @@ -81,19 +81,19 @@ end # admin_hook DELETE /admin/hooks/:id(.:format) admin/hooks#destroy describe Admin::HooksController, "routing" do it "to #test" do - get("/admin/hooks/1/test").should route_to('admin/hooks#test', hook_id: '1') + expect(get("/admin/hooks/1/test")).to route_to('admin/hooks#test', hook_id: '1') end it "to #index" do - get("/admin/hooks").should route_to('admin/hooks#index') + expect(get("/admin/hooks")).to route_to('admin/hooks#index') end it "to #create" do - post("/admin/hooks").should route_to('admin/hooks#create') + expect(post("/admin/hooks")).to route_to('admin/hooks#create') end it "to #destroy" do - delete("/admin/hooks/1").should route_to('admin/hooks#destroy', id: '1') + expect(delete("/admin/hooks/1")).to route_to('admin/hooks#destroy', id: '1') end end @@ -101,21 +101,21 @@ end # admin_logs GET /admin/logs(.:format) admin/logs#show describe Admin::LogsController, "routing" do it "to #show" do - get("/admin/logs").should route_to('admin/logs#show') + expect(get("/admin/logs")).to route_to('admin/logs#show') end end # admin_background_jobs GET /admin/background_jobs(.:format) admin/background_jobs#show describe Admin::BackgroundJobsController, "routing" do it "to #show" do - get("/admin/background_jobs").should route_to('admin/background_jobs#show') + expect(get("/admin/background_jobs")).to route_to('admin/background_jobs#show') end end # admin_root /admin(.:format) admin/dashboard#index describe Admin::DashboardController, "routing" do it "to #index" do - get("/admin").should route_to('admin/dashboard#index') + expect(get("/admin")).to route_to('admin/dashboard#index') end end diff --git a/spec/routing/notifications_routing_spec.rb b/spec/routing/notifications_routing_spec.rb index 112b825e02..24592942a9 100644 --- a/spec/routing/notifications_routing_spec.rb +++ b/spec/routing/notifications_routing_spec.rb @@ -3,11 +3,11 @@ require "spec_helper" describe Profiles::NotificationsController do describe "routing" do it "routes to #show" do - get("/profile/notifications").should route_to("profiles/notifications#show") + expect(get("/profile/notifications")).to route_to("profiles/notifications#show") end it "routes to #update" do - put("/profile/notifications").should route_to("profiles/notifications#update") + expect(put("/profile/notifications")).to route_to("profiles/notifications#update") end end end diff --git a/spec/routing/project_routing_spec.rb b/spec/routing/project_routing_spec.rb index 4b2eb42c70..042352311d 100644 --- a/spec/routing/project_routing_spec.rb +++ b/spec/routing/project_routing_spec.rb @@ -12,86 +12,88 @@ require 'spec_helper' # Examples # # # Default behavior -# it_behaves_like "RESTful project resources" do +# it_behaves_like 'RESTful project resources' do # let(:controller) { 'issues' } # end # # # Customizing actions -# it_behaves_like "RESTful project resources" do +# it_behaves_like 'RESTful project resources' do # let(:actions) { [:index] } # let(:controller) { 'issues' } # end -shared_examples "RESTful project resources" do +shared_examples 'RESTful project resources' do let(:actions) { [:index, :create, :new, :edit, :show, :update, :destroy] } - it "to #index" do - get("/gitlab/gitlabhq/#{controller}").should route_to("projects/#{controller}#index", project_id: 'gitlab/gitlabhq') if actions.include?(:index) + it 'to #index' do + expect(get("/gitlab/gitlabhq/#{controller}")).to route_to("projects/#{controller}#index", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:index) end - it "to #create" do - post("/gitlab/gitlabhq/#{controller}").should route_to("projects/#{controller}#create", project_id: 'gitlab/gitlabhq') if actions.include?(:create) + it 'to #create' do + expect(post("/gitlab/gitlabhq/#{controller}")).to route_to("projects/#{controller}#create", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:create) end - it "to #new" do - get("/gitlab/gitlabhq/#{controller}/new").should route_to("projects/#{controller}#new", project_id: 'gitlab/gitlabhq') if actions.include?(:new) + it 'to #new' do + expect(get("/gitlab/gitlabhq/#{controller}/new")).to route_to("projects/#{controller}#new", namespace_id: 'gitlab', project_id: 'gitlabhq') if actions.include?(:new) end - it "to #edit" do - get("/gitlab/gitlabhq/#{controller}/1/edit").should route_to("projects/#{controller}#edit", project_id: 'gitlab/gitlabhq', id: '1') if actions.include?(:edit) + it 'to #edit' do + expect(get("/gitlab/gitlabhq/#{controller}/1/edit")).to route_to("projects/#{controller}#edit", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:edit) end - it "to #show" do - get("/gitlab/gitlabhq/#{controller}/1").should route_to("projects/#{controller}#show", project_id: 'gitlab/gitlabhq', id: '1') if actions.include?(:show) + it 'to #show' do + expect(get("/gitlab/gitlabhq/#{controller}/1")).to route_to("projects/#{controller}#show", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:show) end - it "to #update" do - put("/gitlab/gitlabhq/#{controller}/1").should route_to("projects/#{controller}#update", project_id: 'gitlab/gitlabhq', id: '1') if actions.include?(:update) + it 'to #update' do + expect(put("/gitlab/gitlabhq/#{controller}/1")).to route_to("projects/#{controller}#update", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:update) end - it "to #destroy" do - delete("/gitlab/gitlabhq/#{controller}/1").should route_to("projects/#{controller}#destroy", project_id: 'gitlab/gitlabhq', id: '1') if actions.include?(:destroy) + it 'to #destroy' do + expect(delete("/gitlab/gitlabhq/#{controller}/1")).to route_to("projects/#{controller}#destroy", namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') if actions.include?(:destroy) end end -# projects POST /projects(.:format) projects#create -# new_project GET /projects/new(.:format) projects#new -# fork_project POST /:id/fork(.:format) projects#fork -# files_project GET /:id/files(.:format) projects#files -# edit_project GET /:id/edit(.:format) projects#edit -# project GET /:id(.:format) projects#show -# PUT /:id(.:format) projects#update -# DELETE /:id(.:format) projects#destroy -describe ProjectsController, "routing" do - it "to #create" do - post("/projects").should route_to('projects#create') +# projects POST /projects(.:format) projects#create +# new_project GET /projects/new(.:format) projects#new +# files_project GET /:id/files(.:format) projects#files +# edit_project GET /:id/edit(.:format) projects#edit +# project GET /:id(.:format) projects#show +# PUT /:id(.:format) projects#update +# DELETE /:id(.:format) projects#destroy +# markdown_preview_project POST /:id/markdown_preview(.:format) projects#markdown_preview +describe ProjectsController, 'routing' do + it 'to #create' do + expect(post('/projects')).to route_to('projects#create') end - it "to #new" do - get("/projects/new").should route_to('projects#new') + it 'to #new' do + expect(get('/projects/new')).to route_to('projects#new') end - it "to #fork" do - post("/gitlab/gitlabhq/fork").should route_to('projects#fork', id: 'gitlab/gitlabhq') + it 'to #edit' do + expect(get('/gitlab/gitlabhq/edit')).to route_to('projects#edit', namespace_id: 'gitlab', id: 'gitlabhq') end - it "to #edit" do - get("/gitlab/gitlabhq/edit").should route_to('projects#edit', id: 'gitlab/gitlabhq') + it 'to #autocomplete_sources' do + expect(get('/gitlab/gitlabhq/autocomplete_sources')).to route_to('projects#autocomplete_sources', namespace_id: 'gitlab', id: 'gitlabhq') end - it "to #autocomplete_sources" do - get('/gitlab/gitlabhq/autocomplete_sources').should route_to('projects#autocomplete_sources', id: "gitlab/gitlabhq") + it 'to #show' do + expect(get('/gitlab/gitlabhq')).to route_to('projects#show', namespace_id: 'gitlab', id: 'gitlabhq') end - it "to #show" do - get("/gitlab/gitlabhq").should route_to('projects#show', id: 'gitlab/gitlabhq') + it 'to #update' do + expect(put('/gitlab/gitlabhq')).to route_to('projects#update', namespace_id: 'gitlab', id: 'gitlabhq') end - it "to #update" do - put("/gitlab/gitlabhq").should route_to('projects#update', id: 'gitlab/gitlabhq') + it 'to #destroy' do + expect(delete('/gitlab/gitlabhq')).to route_to('projects#destroy', namespace_id: 'gitlab', id: 'gitlabhq') end - it "to #destroy" do - delete("/gitlab/gitlabhq").should route_to('projects#destroy', id: 'gitlab/gitlabhq') + it 'to #markdown_preview' do + expect(post('/gitlab/gitlabhq/markdown_preview')).to( + route_to('projects#markdown_preview', namespace_id: 'gitlab', id: 'gitlabhq') + ) end end @@ -101,16 +103,16 @@ end # edit_project_wiki GET /:project_id/wikis/:id/edit(.:format) projects/wikis#edit # project_wiki GET /:project_id/wikis/:id(.:format) projects/wikis#show # DELETE /:project_id/wikis/:id(.:format) projects/wikis#destroy -describe Projects::WikisController, "routing" do - it "to #pages" do - get("/gitlab/gitlabhq/wikis/pages").should route_to('projects/wikis#pages', project_id: 'gitlab/gitlabhq') +describe Projects::WikisController, 'routing' do + it 'to #pages' do + expect(get('/gitlab/gitlabhq/wikis/pages')).to route_to('projects/wikis#pages', namespace_id: 'gitlab', project_id: 'gitlabhq') end - it "to #history" do - get("/gitlab/gitlabhq/wikis/1/history").should route_to('projects/wikis#history', project_id: 'gitlab/gitlabhq', id: '1') + it 'to #history' do + expect(get('/gitlab/gitlabhq/wikis/1/history')).to route_to('projects/wikis#history', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') end - it_behaves_like "RESTful project resources" do + it_behaves_like 'RESTful project resources' do let(:actions) { [:create, :edit, :show, :destroy] } let(:controller) { 'wikis' } end @@ -120,45 +122,45 @@ end # tags_project_repository GET /:project_id/repository/tags(.:format) projects/repositories#tags # archive_project_repository GET /:project_id/repository/archive(.:format) projects/repositories#archive # edit_project_repository GET /:project_id/repository/edit(.:format) projects/repositories#edit -describe Projects::RepositoriesController, "routing" do - it "to #archive" do - get("/gitlab/gitlabhq/repository/archive").should route_to('projects/repositories#archive', project_id: 'gitlab/gitlabhq') +describe Projects::RepositoriesController, 'routing' do + it 'to #archive' do + expect(get('/gitlab/gitlabhq/repository/archive')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq') end - it "to #archive format:zip" do - get("/gitlab/gitlabhq/repository/archive.zip").should route_to('projects/repositories#archive', project_id: 'gitlab/gitlabhq', format: 'zip') + it 'to #archive format:zip' do + expect(get('/gitlab/gitlabhq/repository/archive.zip')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'zip') end - it "to #archive format:tar.bz2" do - get("/gitlab/gitlabhq/repository/archive.tar.bz2").should route_to('projects/repositories#archive', project_id: 'gitlab/gitlabhq', format: 'tar.bz2') + it 'to #archive format:tar.bz2' do + expect(get('/gitlab/gitlabhq/repository/archive.tar.bz2')).to route_to('projects/repositories#archive', namespace_id: 'gitlab', project_id: 'gitlabhq', format: 'tar.bz2') end - it "to #show" do - get("/gitlab/gitlabhq/repository").should route_to('projects/repositories#show', project_id: 'gitlab/gitlabhq') + it 'to #show' do + expect(get('/gitlab/gitlabhq/repository')).to route_to('projects/repositories#show', namespace_id: 'gitlab', project_id: 'gitlabhq') end end -describe Projects::BranchesController, "routing" do - it "to #branches" do - get("/gitlab/gitlabhq/branches").should route_to('projects/branches#index', project_id: 'gitlab/gitlabhq') - delete("/gitlab/gitlabhq/branches/feature%2345").should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature#45') - delete("/gitlab/gitlabhq/branches/feature%2B45").should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature+45') - delete("/gitlab/gitlabhq/branches/feature@45").should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature@45') - delete("/gitlab/gitlabhq/branches/feature%2345/foo/bar/baz").should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature#45/foo/bar/baz') - delete("/gitlab/gitlabhq/branches/feature%2B45/foo/bar/baz").should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature+45/foo/bar/baz') - delete("/gitlab/gitlabhq/branches/feature@45/foo/bar/baz").should route_to('projects/branches#destroy', project_id: 'gitlab/gitlabhq', id: 'feature@45/foo/bar/baz') +describe Projects::BranchesController, 'routing' do + it 'to #branches' do + expect(get('/gitlab/gitlabhq/branches')).to route_to('projects/branches#index', namespace_id: 'gitlab', project_id: 'gitlabhq') + expect(delete('/gitlab/gitlabhq/branches/feature%2345')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45') + expect(delete('/gitlab/gitlabhq/branches/feature%2B45')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45') + expect(delete('/gitlab/gitlabhq/branches/feature@45')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45') + expect(delete('/gitlab/gitlabhq/branches/feature%2345/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45/foo/bar/baz') + expect(delete('/gitlab/gitlabhq/branches/feature%2B45/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45/foo/bar/baz') + expect(delete('/gitlab/gitlabhq/branches/feature@45/foo/bar/baz')).to route_to('projects/branches#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45/foo/bar/baz') end end -describe Projects::TagsController, "routing" do - it "to #tags" do - get("/gitlab/gitlabhq/tags").should route_to('projects/tags#index', project_id: 'gitlab/gitlabhq') - delete("/gitlab/gitlabhq/tags/feature%2345").should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature#45') - delete("/gitlab/gitlabhq/tags/feature%2B45").should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature+45') - delete("/gitlab/gitlabhq/tags/feature@45").should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature@45') - delete("/gitlab/gitlabhq/tags/feature%2345/foo/bar/baz").should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature#45/foo/bar/baz') - delete("/gitlab/gitlabhq/tags/feature%2B45/foo/bar/baz").should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature+45/foo/bar/baz') - delete("/gitlab/gitlabhq/tags/feature@45/foo/bar/baz").should route_to('projects/tags#destroy', project_id: 'gitlab/gitlabhq', id: 'feature@45/foo/bar/baz') +describe Projects::TagsController, 'routing' do + it 'to #tags' do + expect(get('/gitlab/gitlabhq/tags')).to route_to('projects/tags#index', namespace_id: 'gitlab', project_id: 'gitlabhq') + expect(delete('/gitlab/gitlabhq/tags/feature%2345')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45') + expect(delete('/gitlab/gitlabhq/tags/feature%2B45')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45') + expect(delete('/gitlab/gitlabhq/tags/feature@45')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45') + expect(delete('/gitlab/gitlabhq/tags/feature%2345/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45/foo/bar/baz') + expect(delete('/gitlab/gitlabhq/tags/feature%2B45/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45/foo/bar/baz') + expect(delete('/gitlab/gitlabhq/tags/feature@45/foo/bar/baz')).to route_to('projects/tags#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45/foo/bar/baz') end end @@ -166,12 +168,11 @@ end # project_deploy_keys GET /:project_id/deploy_keys(.:format) deploy_keys#index # POST /:project_id/deploy_keys(.:format) deploy_keys#create # new_project_deploy_key GET /:project_id/deploy_keys/new(.:format) deploy_keys#new -# edit_project_deploy_key GET /:project_id/deploy_keys/:id/edit(.:format) deploy_keys#edit # project_deploy_key GET /:project_id/deploy_keys/:id(.:format) deploy_keys#show -# PUT /:project_id/deploy_keys/:id(.:format) deploy_keys#update # DELETE /:project_id/deploy_keys/:id(.:format) deploy_keys#destroy -describe Projects::DeployKeysController, "routing" do - it_behaves_like "RESTful project resources" do +describe Projects::DeployKeysController, 'routing' do + it_behaves_like 'RESTful project resources' do + let(:actions) { [:index, :show, :new, :create] } let(:controller) { 'deploy_keys' } end end @@ -179,8 +180,8 @@ end # project_protected_branches GET /:project_id/protected_branches(.:format) protected_branches#index # POST /:project_id/protected_branches(.:format) protected_branches#create # project_protected_branch DELETE /:project_id/protected_branches/:id(.:format) protected_branches#destroy -describe Projects::ProtectedBranchesController, "routing" do - it_behaves_like "RESTful project resources" do +describe Projects::ProtectedBranchesController, 'routing' do + it_behaves_like 'RESTful project resources' do let(:actions) { [:index, :create, :destroy] } let(:controller) { 'protected_branches' } end @@ -189,21 +190,21 @@ end # switch_project_refs GET /:project_id/refs/switch(.:format) refs#switch # logs_tree_project_ref GET /:project_id/refs/:id/logs_tree(.:format) refs#logs_tree # logs_file_project_ref GET /:project_id/refs/:id/logs_tree/:path(.:format) refs#logs_tree -describe Projects::RefsController, "routing" do - it "to #switch" do - get("/gitlab/gitlabhq/refs/switch").should route_to('projects/refs#switch', project_id: 'gitlab/gitlabhq') +describe Projects::RefsController, 'routing' do + it 'to #switch' do + expect(get('/gitlab/gitlabhq/refs/switch')).to route_to('projects/refs#switch', namespace_id: 'gitlab', project_id: 'gitlabhq') end - it "to #logs_tree" do - get("/gitlab/gitlabhq/refs/stable/logs_tree").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'stable') - get("/gitlab/gitlabhq/refs/feature%2345/logs_tree").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature#45') - get("/gitlab/gitlabhq/refs/feature%2B45/logs_tree").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature+45') - get("/gitlab/gitlabhq/refs/feature@45/logs_tree").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature@45') - get("/gitlab/gitlabhq/refs/stable/logs_tree/foo/bar/baz").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'stable', path: 'foo/bar/baz') - get("/gitlab/gitlabhq/refs/feature%2345/logs_tree/foo/bar/baz").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature#45', path: 'foo/bar/baz') - get("/gitlab/gitlabhq/refs/feature%2B45/logs_tree/foo/bar/baz").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature+45', path: 'foo/bar/baz') - get("/gitlab/gitlabhq/refs/feature@45/logs_tree/foo/bar/baz").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'feature@45', path: 'foo/bar/baz') - get("/gitlab/gitlabhq/refs/stable/logs_tree/files.scss").should route_to('projects/refs#logs_tree', project_id: 'gitlab/gitlabhq', id: 'stable', path: 'files.scss') + it 'to #logs_tree' do + expect(get('/gitlab/gitlabhq/refs/stable/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'stable') + expect(get('/gitlab/gitlabhq/refs/feature%2345/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45') + expect(get('/gitlab/gitlabhq/refs/feature%2B45/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45') + expect(get('/gitlab/gitlabhq/refs/feature@45/logs_tree')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45') + expect(get('/gitlab/gitlabhq/refs/stable/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'stable', path: 'foo/bar/baz') + expect(get('/gitlab/gitlabhq/refs/feature%2345/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature#45', path: 'foo/bar/baz') + expect(get('/gitlab/gitlabhq/refs/feature%2B45/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature+45', path: 'foo/bar/baz') + expect(get('/gitlab/gitlabhq/refs/feature@45/logs_tree/foo/bar/baz')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'feature@45', path: 'foo/bar/baz') + expect(get('/gitlab/gitlabhq/refs/stable/logs_tree/files.scss')).to route_to('projects/refs#logs_tree', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'stable', path: 'files.scss') end end @@ -219,36 +220,36 @@ end # project_merge_request GET /:project_id/merge_requests/:id(.:format) projects/merge_requests#show # PUT /:project_id/merge_requests/:id(.:format) projects/merge_requests#update # DELETE /:project_id/merge_requests/:id(.:format) projects/merge_requests#destroy -describe Projects::MergeRequestsController, "routing" do - it "to #diffs" do - get("/gitlab/gitlabhq/merge_requests/1/diffs").should route_to('projects/merge_requests#diffs', project_id: 'gitlab/gitlabhq', id: '1') +describe Projects::MergeRequestsController, 'routing' do + it 'to #diffs' do + expect(get('/gitlab/gitlabhq/merge_requests/1/diffs')).to route_to('projects/merge_requests#diffs', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') end - it "to #automerge" do - post('/gitlab/gitlabhq/merge_requests/1/automerge').should route_to( + it 'to #automerge' do + expect(post('/gitlab/gitlabhq/merge_requests/1/automerge')).to route_to( 'projects/merge_requests#automerge', - project_id: 'gitlab/gitlabhq', id: '1' + namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1' ) end - it "to #automerge_check" do - get("/gitlab/gitlabhq/merge_requests/1/automerge_check").should route_to('projects/merge_requests#automerge_check', project_id: 'gitlab/gitlabhq', id: '1') + it 'to #automerge_check' do + expect(get('/gitlab/gitlabhq/merge_requests/1/automerge_check')).to route_to('projects/merge_requests#automerge_check', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') end - it "to #branch_from" do - get("/gitlab/gitlabhq/merge_requests/branch_from").should route_to('projects/merge_requests#branch_from', project_id: 'gitlab/gitlabhq') + it 'to #branch_from' do + expect(get('/gitlab/gitlabhq/merge_requests/branch_from')).to route_to('projects/merge_requests#branch_from', namespace_id: 'gitlab', project_id: 'gitlabhq') end - it "to #branch_to" do - get("/gitlab/gitlabhq/merge_requests/branch_to").should route_to('projects/merge_requests#branch_to', project_id: 'gitlab/gitlabhq') + it 'to #branch_to' do + expect(get('/gitlab/gitlabhq/merge_requests/branch_to')).to route_to('projects/merge_requests#branch_to', namespace_id: 'gitlab', project_id: 'gitlabhq') end - it "to #show" do - get("/gitlab/gitlabhq/merge_requests/1.diff").should route_to('projects/merge_requests#show', project_id: 'gitlab/gitlabhq', id: '1', format: 'diff') - get("/gitlab/gitlabhq/merge_requests/1.patch").should route_to('projects/merge_requests#show', project_id: 'gitlab/gitlabhq', id: '1', format: 'patch') + it 'to #show' do + expect(get('/gitlab/gitlabhq/merge_requests/1.diff')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'diff') + expect(get('/gitlab/gitlabhq/merge_requests/1.patch')).to route_to('projects/merge_requests#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1', format: 'patch') end - it_behaves_like "RESTful project resources" do + it_behaves_like 'RESTful project resources' do let(:controller) { 'merge_requests' } let(:actions) { [:index, :create, :new, :edit, :show, :update] } end @@ -262,37 +263,37 @@ end # project_snippet GET /:project_id/snippets/:id(.:format) snippets#show # PUT /:project_id/snippets/:id(.:format) snippets#update # DELETE /:project_id/snippets/:id(.:format) snippets#destroy -describe SnippetsController, "routing" do - it "to #raw" do - get("/gitlab/gitlabhq/snippets/1/raw").should route_to('projects/snippets#raw', project_id: 'gitlab/gitlabhq', id: '1') +describe SnippetsController, 'routing' do + it 'to #raw' do + expect(get('/gitlab/gitlabhq/snippets/1/raw')).to route_to('projects/snippets#raw', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') end - it "to #index" do - get("/gitlab/gitlabhq/snippets").should route_to("projects/snippets#index", project_id: 'gitlab/gitlabhq') + it 'to #index' do + expect(get('/gitlab/gitlabhq/snippets')).to route_to('projects/snippets#index', namespace_id: 'gitlab', project_id: 'gitlabhq') end - it "to #create" do - post("/gitlab/gitlabhq/snippets").should route_to("projects/snippets#create", project_id: 'gitlab/gitlabhq') + it 'to #create' do + expect(post('/gitlab/gitlabhq/snippets')).to route_to('projects/snippets#create', namespace_id: 'gitlab', project_id: 'gitlabhq') end - it "to #new" do - get("/gitlab/gitlabhq/snippets/new").should route_to("projects/snippets#new", project_id: 'gitlab/gitlabhq') + it 'to #new' do + expect(get('/gitlab/gitlabhq/snippets/new')).to route_to('projects/snippets#new', namespace_id: 'gitlab', project_id: 'gitlabhq') end - it "to #edit" do - get("/gitlab/gitlabhq/snippets/1/edit").should route_to("projects/snippets#edit", project_id: 'gitlab/gitlabhq', id: '1') + it 'to #edit' do + expect(get('/gitlab/gitlabhq/snippets/1/edit')).to route_to('projects/snippets#edit', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') end - it "to #show" do - get("/gitlab/gitlabhq/snippets/1").should route_to("projects/snippets#show", project_id: 'gitlab/gitlabhq', id: '1') + it 'to #show' do + expect(get('/gitlab/gitlabhq/snippets/1')).to route_to('projects/snippets#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') end - it "to #update" do - put("/gitlab/gitlabhq/snippets/1").should route_to("projects/snippets#update", project_id: 'gitlab/gitlabhq', id: '1') + it 'to #update' do + expect(put('/gitlab/gitlabhq/snippets/1')).to route_to('projects/snippets#update', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') end - it "to #destroy" do - delete("/gitlab/gitlabhq/snippets/1").should route_to("projects/snippets#destroy", project_id: 'gitlab/gitlabhq', id: '1') + it 'to #destroy' do + expect(delete('/gitlab/gitlabhq/snippets/1')).to route_to('projects/snippets#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') end end @@ -300,24 +301,24 @@ end # project_hooks GET /:project_id/hooks(.:format) hooks#index # POST /:project_id/hooks(.:format) hooks#create # project_hook DELETE /:project_id/hooks/:id(.:format) hooks#destroy -describe Projects::HooksController, "routing" do - it "to #test" do - get("/gitlab/gitlabhq/hooks/1/test").should route_to('projects/hooks#test', project_id: 'gitlab/gitlabhq', id: '1') +describe Projects::HooksController, 'routing' do + it 'to #test' do + expect(get('/gitlab/gitlabhq/hooks/1/test')).to route_to('projects/hooks#test', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '1') end - it_behaves_like "RESTful project resources" do + it_behaves_like 'RESTful project resources' do let(:actions) { [:index, :create, :destroy] } let(:controller) { 'hooks' } end end # project_commit GET /:project_id/commit/:id(.:format) commit#show {id: /[[:alnum:]]{6,40}/, project_id: /[^\/]+/} -describe Projects::CommitController, "routing" do - it "to #show" do - get("/gitlab/gitlabhq/commit/4246fb").should route_to('projects/commit#show', project_id: 'gitlab/gitlabhq', id: '4246fb') - get("/gitlab/gitlabhq/commit/4246fb.diff").should route_to('projects/commit#show', project_id: 'gitlab/gitlabhq', id: '4246fb', format: 'diff') - get("/gitlab/gitlabhq/commit/4246fb.patch").should route_to('projects/commit#show', project_id: 'gitlab/gitlabhq', id: '4246fb', format: 'patch') - get("/gitlab/gitlabhq/commit/4246fbd13872934f72a8fd0d6fb1317b47b59cb5").should route_to('projects/commit#show', project_id: 'gitlab/gitlabhq', id: '4246fbd13872934f72a8fd0d6fb1317b47b59cb5') +describe Projects::CommitController, 'routing' do + it 'to #show' do + expect(get('/gitlab/gitlabhq/commit/4246fb')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fb') + expect(get('/gitlab/gitlabhq/commit/4246fb.diff')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fb', format: 'diff') + expect(get('/gitlab/gitlabhq/commit/4246fb.patch')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fb', format: 'patch') + expect(get('/gitlab/gitlabhq/commit/4246fbd13872934f72a8fd0d6fb1317b47b59cb5')).to route_to('projects/commit#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: '4246fbd13872934f72a8fd0d6fb1317b47b59cb5') end end @@ -325,28 +326,25 @@ end # project_commits GET /:project_id/commits(.:format) commits#index # POST /:project_id/commits(.:format) commits#create # project_commit GET /:project_id/commits/:id(.:format) commits#show -describe Projects::CommitsController, "routing" do - it_behaves_like "RESTful project resources" do +describe Projects::CommitsController, 'routing' do + it_behaves_like 'RESTful project resources' do let(:actions) { [:show] } let(:controller) { 'commits' } end - it "to #show" do - get("/gitlab/gitlabhq/commits/master.atom").should route_to('projects/commits#show', project_id: 'gitlab/gitlabhq', id: "master", format: "atom") + it 'to #show' do + expect(get('/gitlab/gitlabhq/commits/master.atom')).to route_to('projects/commits#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'atom') end end -# project_team_members GET /:project_id/team_members(.:format) team_members#index -# POST /:project_id/team_members(.:format) team_members#create -# new_project_team_member GET /:project_id/team_members/new(.:format) team_members#new -# edit_project_team_member GET /:project_id/team_members/:id/edit(.:format) team_members#edit -# project_team_member GET /:project_id/team_members/:id(.:format) team_members#show -# PUT /:project_id/team_members/:id(.:format) team_members#update -# DELETE /:project_id/team_members/:id(.:format) team_members#destroy -describe Projects::TeamMembersController, "routing" do - it_behaves_like "RESTful project resources" do - let(:actions) { [:new, :create, :update, :destroy] } - let(:controller) { 'team_members' } +# project_project_members GET /:project_id/project_members(.:format) project_members#index +# POST /:project_id/project_members(.:format) project_members#create +# PUT /:project_id/project_members/:id(.:format) project_members#update +# DELETE /:project_id/project_members/:id(.:format) project_members#destroy +describe Projects::ProjectMembersController, 'routing' do + it_behaves_like 'RESTful project resources' do + let(:actions) { [:index, :create, :update, :destroy] } + let(:controller) { 'project_members' } end end @@ -357,17 +355,17 @@ end # project_milestone GET /:project_id/milestones/:id(.:format) milestones#show # PUT /:project_id/milestones/:id(.:format) milestones#update # DELETE /:project_id/milestones/:id(.:format) milestones#destroy -describe Projects::MilestonesController, "routing" do - it_behaves_like "RESTful project resources" do +describe Projects::MilestonesController, 'routing' do + it_behaves_like 'RESTful project resources' do let(:controller) { 'milestones' } let(:actions) { [:index, :create, :new, :edit, :show, :update] } end end # project_labels GET /:project_id/labels(.:format) labels#index -describe Projects::LabelsController, "routing" do - it "to #index" do - get("/gitlab/gitlabhq/labels").should route_to('projects/labels#index', project_id: 'gitlab/gitlabhq') +describe Projects::LabelsController, 'routing' do + it 'to #index' do + expect(get('/gitlab/gitlabhq/labels')).to route_to('projects/labels#index', namespace_id: 'gitlab', project_id: 'gitlabhq') end end @@ -381,84 +379,114 @@ end # project_issue GET /:project_id/issues/:id(.:format) issues#show # PUT /:project_id/issues/:id(.:format) issues#update # DELETE /:project_id/issues/:id(.:format) issues#destroy -describe Projects::IssuesController, "routing" do - it "to #bulk_update" do - post("/gitlab/gitlabhq/issues/bulk_update").should route_to('projects/issues#bulk_update', project_id: 'gitlab/gitlabhq') +describe Projects::IssuesController, 'routing' do + it 'to #bulk_update' do + expect(post('/gitlab/gitlabhq/issues/bulk_update')).to route_to('projects/issues#bulk_update', namespace_id: 'gitlab', project_id: 'gitlabhq') end - it_behaves_like "RESTful project resources" do + it_behaves_like 'RESTful project resources' do let(:controller) { 'issues' } let(:actions) { [:index, :create, :new, :edit, :show, :update] } end end -# preview_project_notes POST /:project_id/notes/preview(.:format) notes#preview # project_notes GET /:project_id/notes(.:format) notes#index # POST /:project_id/notes(.:format) notes#create # project_note DELETE /:project_id/notes/:id(.:format) notes#destroy -describe Projects::NotesController, "routing" do - it "to #preview" do - post("/gitlab/gitlabhq/notes/preview").should route_to('projects/notes#preview', project_id: 'gitlab/gitlabhq') - end - - it_behaves_like "RESTful project resources" do +describe Projects::NotesController, 'routing' do + it_behaves_like 'RESTful project resources' do let(:actions) { [:index, :create, :destroy] } let(:controller) { 'notes' } end end # project_blame GET /:project_id/blame/:id(.:format) blame#show {id: /.+/, project_id: /[^\/]+/} -describe Projects::BlameController, "routing" do - it "to #show" do - get("/gitlab/gitlabhq/blame/master/app/models/project.rb").should route_to('projects/blame#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/project.rb') - get("/gitlab/gitlabhq/blame/master/files.scss").should route_to('projects/blame#show', project_id: 'gitlab/gitlabhq', id: 'master/files.scss') +describe Projects::BlameController, 'routing' do + it 'to #show' do + expect(get('/gitlab/gitlabhq/blame/master/app/models/project.rb')).to route_to('projects/blame#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/project.rb') + expect(get('/gitlab/gitlabhq/blame/master/files.scss')).to route_to('projects/blame#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/files.scss') end end # project_blob GET /:project_id/blob/:id(.:format) blob#show {id: /.+/, project_id: /[^\/]+/} -describe Projects::BlobController, "routing" do - it "to #show" do - get("/gitlab/gitlabhq/blob/master/app/models/project.rb").should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/project.rb') - get("/gitlab/gitlabhq/blob/master/app/models/compare.rb").should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/compare.rb') - get("/gitlab/gitlabhq/blob/master/files.scss").should route_to('projects/blob#show', project_id: 'gitlab/gitlabhq', id: 'master/files.scss') +describe Projects::BlobController, 'routing' do + it 'to #show' do + expect(get('/gitlab/gitlabhq/blob/master/app/models/project.rb')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/project.rb') + expect(get('/gitlab/gitlabhq/blob/master/app/models/compare.rb')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/compare.rb') + expect(get('/gitlab/gitlabhq/blob/master/app/models/diff.js')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/diff.js') + expect(get('/gitlab/gitlabhq/blob/master/files.scss')).to route_to('projects/blob#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/files.scss') end end # project_tree GET /:project_id/tree/:id(.:format) tree#show {id: /.+/, project_id: /[^\/]+/} -describe Projects::TreeController, "routing" do - it "to #show" do - get("/gitlab/gitlabhq/tree/master/app/models/project.rb").should route_to('projects/tree#show', project_id: 'gitlab/gitlabhq', id: 'master/app/models/project.rb') - get("/gitlab/gitlabhq/tree/master/files.scss").should route_to('projects/tree#show', project_id: 'gitlab/gitlabhq', id: 'master/files.scss') +describe Projects::TreeController, 'routing' do + it 'to #show' do + expect(get('/gitlab/gitlabhq/tree/master/app/models/project.rb')).to route_to('projects/tree#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/app/models/project.rb') + expect(get('/gitlab/gitlabhq/tree/master/files.scss')).to route_to('projects/tree#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master/files.scss') + end +end + +describe Projects::BlobController, 'routing' do + it 'to #edit' do + expect(get('/gitlab/gitlabhq/edit/master/app/models/project.rb')).to( + route_to('projects/blob#edit', + namespace_id: 'gitlab', project_id: 'gitlabhq', + id: 'master/app/models/project.rb')) + end + + it 'to #preview' do + expect(post('/gitlab/gitlabhq/preview/master/app/models/project.rb')).to( + route_to('projects/blob#preview', + namespace_id: 'gitlab', project_id: 'gitlabhq', + id: 'master/app/models/project.rb')) end end # project_compare_index GET /:project_id/compare(.:format) compare#index {id: /[^\/]+/, project_id: /[^\/]+/} # POST /:project_id/compare(.:format) compare#create {id: /[^\/]+/, project_id: /[^\/]+/} # project_compare /:project_id/compare/:from...:to(.:format) compare#show {from: /.+/, to: /.+/, id: /[^\/]+/, project_id: /[^\/]+/} -describe Projects::CompareController, "routing" do - it "to #index" do - get("/gitlab/gitlabhq/compare").should route_to('projects/compare#index', project_id: 'gitlab/gitlabhq') +describe Projects::CompareController, 'routing' do + it 'to #index' do + expect(get('/gitlab/gitlabhq/compare')).to route_to('projects/compare#index', namespace_id: 'gitlab', project_id: 'gitlabhq') end - it "to #compare" do - post("/gitlab/gitlabhq/compare").should route_to('projects/compare#create', project_id: 'gitlab/gitlabhq') + it 'to #compare' do + expect(post('/gitlab/gitlabhq/compare')).to route_to('projects/compare#create', namespace_id: 'gitlab', project_id: 'gitlabhq') end - it "to #show" do - get("/gitlab/gitlabhq/compare/master...stable").should route_to('projects/compare#show', project_id: 'gitlab/gitlabhq', from: 'master', to: 'stable') - get("/gitlab/gitlabhq/compare/issue/1234...stable").should route_to('projects/compare#show', project_id: 'gitlab/gitlabhq', from: 'issue/1234', to: 'stable') + it 'to #show' do + expect(get('/gitlab/gitlabhq/compare/master...stable')).to route_to('projects/compare#show', namespace_id: 'gitlab', project_id: 'gitlabhq', from: 'master', to: 'stable') + expect(get('/gitlab/gitlabhq/compare/issue/1234...stable')).to route_to('projects/compare#show', namespace_id: 'gitlab', project_id: 'gitlabhq', from: 'issue/1234', to: 'stable') end end -describe Projects::NetworkController, "routing" do - it "to #show" do - get("/gitlab/gitlabhq/network/master").should route_to('projects/network#show', project_id: 'gitlab/gitlabhq', id: 'master') - get("/gitlab/gitlabhq/network/master.json").should route_to('projects/network#show', project_id: 'gitlab/gitlabhq', id: 'master', format: "json") +describe Projects::NetworkController, 'routing' do + it 'to #show' do + expect(get('/gitlab/gitlabhq/network/master')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master') + expect(get('/gitlab/gitlabhq/network/master.json')).to route_to('projects/network#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master', format: 'json') end end -describe Projects::GraphsController, "routing" do - it "to #show" do - get("/gitlab/gitlabhq/graphs/master").should route_to('projects/graphs#show', project_id: 'gitlab/gitlabhq', id: 'master') +describe Projects::GraphsController, 'routing' do + it 'to #show' do + expect(get('/gitlab/gitlabhq/graphs/master')).to route_to('projects/graphs#show', namespace_id: 'gitlab', project_id: 'gitlabhq', id: 'master') + end +end + +describe Projects::ForksController, 'routing' do + it 'to #new' do + expect(get('/gitlab/gitlabhq/fork/new')).to route_to('projects/forks#new', namespace_id: 'gitlab', project_id: 'gitlabhq') + end + + it 'to #create' do + expect(post('/gitlab/gitlabhq/fork')).to route_to('projects/forks#create', namespace_id: 'gitlab', project_id: 'gitlabhq') + end +end + +# project_avatar DELETE /project/avatar(.:format) projects/avatars#destroy +describe Projects::AvatarsController, 'routing' do + it 'to #destroy' do + expect(delete('/gitlab/gitlabhq/avatar')).to route_to( + 'projects/avatars#destroy', namespace_id: 'gitlab', project_id: 'gitlabhq') end end diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb index 1e92cf62dd..e219a57c29 100644 --- a/spec/routing/routing_spec.rb +++ b/spec/routing/routing_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' # search GET /search(.:format) search#show describe SearchController, "routing" do it "to #show" do - get("/search").should route_to('search#show') + expect(get("/search")).to route_to('search#show') end end @@ -11,11 +11,11 @@ end # /:path Grack describe "Mounted Apps", "routing" do it "to API" do - get("/api/issues").should be_routable + expect(get("/api/issues")).to be_routable end it "to Grack" do - get("/gitlab/gitlabhq.git").should be_routable + expect(get("/gitlab/gitlabhq.git")).to be_routable end end @@ -28,86 +28,71 @@ end # DELETE /snippets/:id(.:format) snippets#destroy describe SnippetsController, "routing" do it "to #user_index" do - get("/s/User").should route_to('snippets#user_index', username: 'User') + expect(get("/s/User")).to route_to('snippets#user_index', username: 'User') end it "to #raw" do - get("/snippets/1/raw").should route_to('snippets#raw', id: '1') + expect(get("/snippets/1/raw")).to route_to('snippets#raw', id: '1') end it "to #index" do - get("/snippets").should route_to('snippets#index') + expect(get("/snippets")).to route_to('snippets#index') end it "to #create" do - post("/snippets").should route_to('snippets#create') + expect(post("/snippets")).to route_to('snippets#create') end it "to #new" do - get("/snippets/new").should route_to('snippets#new') + expect(get("/snippets/new")).to route_to('snippets#new') end it "to #edit" do - get("/snippets/1/edit").should route_to('snippets#edit', id: '1') + expect(get("/snippets/1/edit")).to route_to('snippets#edit', id: '1') end it "to #show" do - get("/snippets/1").should route_to('snippets#show', id: '1') + expect(get("/snippets/1")).to route_to('snippets#show', id: '1') end it "to #update" do - put("/snippets/1").should route_to('snippets#update', id: '1') + expect(put("/snippets/1")).to route_to('snippets#update', id: '1') end it "to #destroy" do - delete("/snippets/1").should route_to('snippets#destroy', id: '1') + expect(delete("/snippets/1")).to route_to('snippets#destroy', id: '1') end end -# help GET /help(.:format) help#index -# help_permissions GET /help/permissions(.:format) help#permissions -# help_workflow GET /help/workflow(.:format) help#workflow -# help_api GET /help/api(.:format) help#api -# help_web_hooks GET /help/web_hooks(.:format) help#web_hooks -# help_system_hooks GET /help/system_hooks(.:format) help#system_hooks -# help_markdown GET /help/markdown(.:format) help#markdown -# help_ssh GET /help/ssh(.:format) help#ssh -# help_raketasks GET /help/raketasks(.:format) help#raketasks +# help GET /help(.:format) help#index +# help_page GET /help/:category/:file(.:format) help#show {:category=>/.*/, :file=>/[^\/\.]+/} +# help_shortcuts GET /help/shortcuts(.:format) help#shortcuts +# help_ui GET /help/ui(.:format) help#ui describe HelpController, "routing" do it "to #index" do - get("/help").should route_to('help#index') + expect(get("/help")).to route_to('help#index') end - it "to #permissions" do - get("/help/permissions/permissions").should route_to('help#show', category: "permissions", file: "permissions") + it 'to #show' do + path = '/help/markdown/markdown.md' + expect(get(path)).to route_to('help#show', + category: 'markdown', + file: 'markdown', + format: 'md') + + path = '/help/workflow/protected_branches/protected_branches1.png' + expect(get(path)).to route_to('help#show', + category: 'workflow/protected_branches', + file: 'protected_branches1', + format: 'png') end - it "to #workflow" do - get("/help/workflow/README").should route_to('help#show', category: "workflow", file: "README") + it 'to #shortcuts' do + expect(get('/help/shortcuts')).to route_to('help#shortcuts') end - it "to #api" do - get("/help/api/README").should route_to('help#show', category: "api", file: "README") - end - - it "to #web_hooks" do - get("/help/web_hooks/web_hooks").should route_to('help#show', category: "web_hooks", file: "web_hooks") - end - - it "to #system_hooks" do - get("/help/system_hooks/system_hooks").should route_to('help#show', category: "system_hooks", file: "system_hooks") - end - - it "to #markdown" do - get("/help/markdown/markdown").should route_to('help#show',category: "markdown", file: "markdown") - end - - it "to #ssh" do - get("/help/ssh/README").should route_to('help#show', category: "ssh", file: "README") - end - - it "to #raketasks" do - get("/help/raketasks/README").should route_to('help#show', category: "raketasks", file: "README") + it 'to #ui' do + expect(get('/help/ui')).to route_to('help#ui') end end @@ -121,23 +106,23 @@ end # profile_update PUT /profile/update(.:format) profile#update describe ProfilesController, "routing" do it "to #account" do - get("/profile/account").should route_to('profiles/accounts#show') + expect(get("/profile/account")).to route_to('profiles/accounts#show') end it "to #history" do - get("/profile/history").should route_to('profiles#history') + expect(get("/profile/history")).to route_to('profiles#history') end it "to #reset_private_token" do - put("/profile/reset_private_token").should route_to('profiles#reset_private_token') + expect(put("/profile/reset_private_token")).to route_to('profiles#reset_private_token') end it "to #show" do - get("/profile").should route_to('profiles#show') + expect(get("/profile")).to route_to('profiles#show') end it "to #design" do - get("/profile/design").should route_to('profiles#design') + expect(get("/profile/design")).to route_to('profiles#design') end end @@ -150,36 +135,36 @@ end # DELETE /keys/:id(.:format) keys#destroy describe Profiles::KeysController, "routing" do it "to #index" do - get("/profile/keys").should route_to('profiles/keys#index') + expect(get("/profile/keys")).to route_to('profiles/keys#index') end it "to #create" do - post("/profile/keys").should route_to('profiles/keys#create') + expect(post("/profile/keys")).to route_to('profiles/keys#create') end it "to #new" do - get("/profile/keys/new").should route_to('profiles/keys#new') + expect(get("/profile/keys/new")).to route_to('profiles/keys#new') end it "to #edit" do - get("/profile/keys/1/edit").should route_to('profiles/keys#edit', id: '1') + expect(get("/profile/keys/1/edit")).to route_to('profiles/keys#edit', id: '1') end it "to #show" do - get("/profile/keys/1").should route_to('profiles/keys#show', id: '1') + expect(get("/profile/keys/1")).to route_to('profiles/keys#show', id: '1') end it "to #update" do - put("/profile/keys/1").should route_to('profiles/keys#update', id: '1') + expect(put("/profile/keys/1")).to route_to('profiles/keys#update', id: '1') end it "to #destroy" do - delete("/profile/keys/1").should route_to('profiles/keys#destroy', id: '1') + expect(delete("/profile/keys/1")).to route_to('profiles/keys#destroy', id: '1') end # get all the ssh-keys of a user it "to #get_keys" do - get("/foo.keys").should route_to('profiles/keys#get_keys', username: 'foo') + expect(get("/foo.keys")).to route_to('profiles/keys#get_keys', username: 'foo') end end @@ -188,22 +173,22 @@ end # DELETE /keys/:id(.:format) keys#destroy describe Profiles::EmailsController, "routing" do it "to #index" do - get("/profile/emails").should route_to('profiles/emails#index') + expect(get("/profile/emails")).to route_to('profiles/emails#index') end it "to #create" do - post("/profile/emails").should route_to('profiles/emails#create') + expect(post("/profile/emails")).to route_to('profiles/emails#create') end it "to #destroy" do - delete("/profile/emails/1").should route_to('profiles/emails#destroy', id: '1') + expect(delete("/profile/emails/1")).to route_to('profiles/emails#destroy', id: '1') end end # profile_avatar DELETE /profile/avatar(.:format) profiles/avatars#destroy describe Profiles::AvatarsController, "routing" do it "to #destroy" do - delete("/profile/avatar").should route_to('profiles/avatars#destroy') + expect(delete("/profile/avatar")).to route_to('profiles/avatars#destroy') end end @@ -213,16 +198,16 @@ end # root / dashboard#show describe DashboardController, "routing" do it "to #index" do - get("/dashboard").should route_to('dashboard#show') - get("/").should route_to('dashboard#show') + expect(get("/dashboard")).to route_to('dashboard#show') + expect(get("/")).to route_to('dashboard#show') end it "to #issues" do - get("/dashboard/issues").should route_to('dashboard#issues') + expect(get("/dashboard/issues")).to route_to('dashboard#issues') end it "to #merge_requests" do - get("/dashboard/merge_requests").should route_to('dashboard#merge_requests') + expect(get("/dashboard/merge_requests")).to route_to('dashboard#merge_requests') end end @@ -241,11 +226,11 @@ end describe "Groups", "routing" do it "to #show" do - get("/groups/1").should route_to('groups#show', id: '1') + expect(get("/groups/1")).to route_to('groups#show', id: '1') end it "also display group#show on the short path" do - get('/1').should route_to('namespaces#show', id: '1') + expect(get('/1')).to route_to('namespaces#show', id: '1') end end diff --git a/spec/services/archive_repository_service_spec.rb b/spec/services/archive_repository_service_spec.rb new file mode 100644 index 0000000000..f168a91397 --- /dev/null +++ b/spec/services/archive_repository_service_spec.rb @@ -0,0 +1,93 @@ +require 'spec_helper' + +describe ArchiveRepositoryService do + let(:project) { create(:project) } + subject { ArchiveRepositoryService.new(project, "master", "zip") } + + describe "#execute" do + it "cleans old archives" do + expect(project.repository).to receive(:clean_old_archives) + + subject.execute(timeout: 0.0) + end + + context "when the repository doesn't have an archive file path" do + before do + allow(project.repository).to receive(:archive_file_path).and_return(nil) + end + + it "raises an error" do + expect { + subject.execute(timeout: 0.0) + }.to raise_error + end + end + + context "when the repository has an archive file path" do + let(:file_path) { "/archive.zip" } + let(:pid_file_path) { "/archive.zip.pid" } + + before do + allow(project.repository).to receive(:archive_file_path).and_return(file_path) + allow(project.repository).to receive(:archive_pid_file_path).and_return(pid_file_path) + end + + context "when the archive file already exists" do + before do + allow(File).to receive(:exist?).with(file_path).and_return(true) + end + + it "returns the file path" do + expect(subject.execute(timeout: 0.0)).to eq(file_path) + end + end + + context "when the archive file doesn't exist yet" do + before do + allow(File).to receive(:exist?).with(file_path).and_return(false) + allow(File).to receive(:exist?).with(pid_file_path).and_return(true) + end + + context "when the archive pid file doesn't exist yet" do + before do + allow(File).to receive(:exist?).with(pid_file_path).and_return(false) + end + + it "queues the RepositoryArchiveWorker" do + expect(RepositoryArchiveWorker).to receive(:perform_async) + + subject.execute(timeout: 0.0) + end + end + + context "when the archive pid file already exists" do + it "doesn't queue the RepositoryArchiveWorker" do + expect(RepositoryArchiveWorker).not_to receive(:perform_async) + + subject.execute(timeout: 0.0) + end + end + + context "when the archive file exists after a little while" do + before do + Thread.new do + sleep 0.1 + allow(File).to receive(:exist?).with(file_path).and_return(true) + end + end + + it "returns the file path" do + expect(subject.execute(timeout: 0.2)).to eq(file_path) + end + end + + context "when the archive file doesn't exist after the timeout" do + it "returns nil" do + expect(subject.execute(timeout: 0.0)).to eq(nil) + end + end + end + end + end +end + diff --git a/spec/services/create_snippet_service_spec.rb b/spec/services/create_snippet_service_spec.rb new file mode 100644 index 0000000000..08689c15ca --- /dev/null +++ b/spec/services/create_snippet_service_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +describe CreateSnippetService do + before do + @user = create :user + @admin = create :user, admin: true + @opts = { + title: 'Test snippet', + file_name: 'snippet.rb', + content: 'puts "hello world"', + visibility_level: Gitlab::VisibilityLevel::PRIVATE + } + end + + context 'When public visibility is restricted' do + before do + allow_any_instance_of(ApplicationSetting).to( + receive(:restricted_visibility_levels).and_return( + [Gitlab::VisibilityLevel::PUBLIC] + ) + ) + + @opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + end + + it 'non-admins should not be able to create a public snippet' do + snippet = create_snippet(nil, @user, @opts) + expect(snippet.errors.messages).to have_key(:visibility_level) + expect(snippet.errors.messages[:visibility_level].first).to( + match('Public visibility has been restricted') + ) + end + + it 'admins should be able to create a public snippet' do + snippet = create_snippet(nil, @admin, @opts) + expect(snippet.errors.any?).to be_falsey + expect(snippet.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC) + end + end + + def create_snippet(project, user, opts) + CreateSnippetService.new(project, user, opts).execute + end +end diff --git a/spec/services/event_create_service_spec.rb b/spec/services/event_create_service_spec.rb index 713aa3e7e7..007a9eed19 100644 --- a/spec/services/event_create_service_spec.rb +++ b/spec/services/event_create_service_spec.rb @@ -7,7 +7,7 @@ describe EventCreateService do describe :open_issue do let(:issue) { create(:issue) } - it { service.open_issue(issue, issue.author).should be_true } + it { expect(service.open_issue(issue, issue.author)).to be_truthy } it "should create new event" do expect { service.open_issue(issue, issue.author) }.to change { Event.count } @@ -17,7 +17,7 @@ describe EventCreateService do describe :close_issue do let(:issue) { create(:issue) } - it { service.close_issue(issue, issue.author).should be_true } + it { expect(service.close_issue(issue, issue.author)).to be_truthy } it "should create new event" do expect { service.close_issue(issue, issue.author) }.to change { Event.count } @@ -27,7 +27,7 @@ describe EventCreateService do describe :reopen_issue do let(:issue) { create(:issue) } - it { service.reopen_issue(issue, issue.author).should be_true } + it { expect(service.reopen_issue(issue, issue.author)).to be_truthy } it "should create new event" do expect { service.reopen_issue(issue, issue.author) }.to change { Event.count } @@ -39,7 +39,7 @@ describe EventCreateService do describe :open_mr do let(:merge_request) { create(:merge_request) } - it { service.open_mr(merge_request, merge_request.author).should be_true } + it { expect(service.open_mr(merge_request, merge_request.author)).to be_truthy } it "should create new event" do expect { service.open_mr(merge_request, merge_request.author) }.to change { Event.count } @@ -49,7 +49,7 @@ describe EventCreateService do describe :close_mr do let(:merge_request) { create(:merge_request) } - it { service.close_mr(merge_request, merge_request.author).should be_true } + it { expect(service.close_mr(merge_request, merge_request.author)).to be_truthy } it "should create new event" do expect { service.close_mr(merge_request, merge_request.author) }.to change { Event.count } @@ -59,7 +59,7 @@ describe EventCreateService do describe :merge_mr do let(:merge_request) { create(:merge_request) } - it { service.merge_mr(merge_request, merge_request.author).should be_true } + it { expect(service.merge_mr(merge_request, merge_request.author)).to be_truthy } it "should create new event" do expect { service.merge_mr(merge_request, merge_request.author) }.to change { Event.count } @@ -69,7 +69,7 @@ describe EventCreateService do describe :reopen_mr do let(:merge_request) { create(:merge_request) } - it { service.reopen_mr(merge_request, merge_request.author).should be_true } + it { expect(service.reopen_mr(merge_request, merge_request.author)).to be_truthy } it "should create new event" do expect { service.reopen_mr(merge_request, merge_request.author) }.to change { Event.count } @@ -83,7 +83,7 @@ describe EventCreateService do describe :open_milestone do let(:milestone) { create(:milestone) } - it { service.open_milestone(milestone, user).should be_true } + it { expect(service.open_milestone(milestone, user)).to be_truthy } it "should create new event" do expect { service.open_milestone(milestone, user) }.to change { Event.count } @@ -93,7 +93,7 @@ describe EventCreateService do describe :close_mr do let(:milestone) { create(:milestone) } - it { service.close_milestone(milestone, user).should be_true } + it { expect(service.close_milestone(milestone, user)).to be_truthy } it "should create new event" do expect { service.close_milestone(milestone, user) }.to change { Event.count } diff --git a/spec/services/fork_service_spec.rb b/spec/services/fork_service_spec.rb deleted file mode 100644 index b6573095db..0000000000 --- a/spec/services/fork_service_spec.rb +++ /dev/null @@ -1,57 +0,0 @@ -require 'spec_helper' - -describe Projects::ForkService do - describe :fork_by_user do - before do - @from_namespace = create(:namespace) - @from_user = create(:user, namespace: @from_namespace ) - @from_project = create(:project, creator_id: @from_user.id, namespace: @from_namespace) - @to_namespace = create(:namespace) - @to_user = create(:user, namespace: @to_namespace) - end - - context 'fork project' do - - it "successfully creates project in the user namespace" do - @to_project = fork_project(@from_project, @to_user) - - @to_project.owner.should == @to_user - @to_project.namespace.should == @to_user.namespace - end - end - - context 'fork project failure' do - - it "fails due to transaction failure" do - # make the mock gitlab-shell fail - @to_project = fork_project(@from_project, @to_user, false) - - @to_project.errors.should_not be_empty - @to_project.errors[:base].should include("Fork transaction failed.") - end - - end - - context 'project already exists' do - - it "should fail due to validation, not transaction failure" do - @existing_project = create(:project, creator_id: @to_user.id, name: @from_project.name, namespace: @to_namespace) - @to_project = fork_project(@from_project, @to_user) - - @existing_project.persisted?.should be_true - @to_project.errors[:base].should include("Invalid fork destination") - @to_project.errors[:base].should_not include("Fork transaction failed.") - end - - end - end - - def fork_project(from_project, user, fork_success = true) - context = Projects::ForkService.new(from_project, user) - shell = double("gitlab_shell") - shell.stub(fork_repository: fork_success) - context.stub(gitlab_shell: shell) - context.execute - end - -end diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb index fa99acabc7..aa9b15dd9e 100644 --- a/spec/services/git_push_service_spec.rb +++ b/spec/services/git_push_service_spec.rb @@ -8,12 +8,38 @@ describe GitPushService do let (:service) { GitPushService.new } before do - @blankrev = '0000000000000000000000000000000000000000' + @blankrev = Gitlab::Git::BLANK_SHA @oldrev = sample_commit.parent_id @newrev = sample_commit.id @ref = 'refs/heads/master' end + describe 'Push branches' do + context 'new branch' do + subject do + service.execute(project, user, @blankrev, @newrev, @ref) + end + + it { is_expected.to be_truthy } + end + + context 'existing branch' do + subject do + service.execute(project, user, @oldrev, @newrev, @ref) + end + + it { is_expected.to be_truthy } + end + + context 'rm branch' do + subject do + service.execute(project, user, @oldrev, @blankrev, @ref) + end + + it { is_expected.to be_truthy } + end + end + describe "Git Push Data" do before do service.execute(project, user, @oldrev, @newrev, @ref) @@ -23,41 +49,54 @@ describe GitPushService do subject { @push_data } - it { should include(before: @oldrev) } - it { should include(after: @newrev) } - it { should include(ref: @ref) } - it { should include(user_id: user.id) } - it { should include(user_name: user.name) } - it { should include(project_id: project.id) } + it { is_expected.to include(object_kind: 'push') } + it { is_expected.to include(before: @oldrev) } + it { is_expected.to include(after: @newrev) } + it { is_expected.to include(ref: @ref) } + it { is_expected.to include(user_id: user.id) } + it { is_expected.to include(user_name: user.name) } + it { is_expected.to include(project_id: project.id) } context "with repository data" do subject { @push_data[:repository] } - it { should include(name: project.name) } - it { should include(url: project.url_to_repo) } - it { should include(description: project.description) } - it { should include(homepage: project.web_url) } + it { is_expected.to include(name: project.name) } + it { is_expected.to include(url: project.url_to_repo) } + it { is_expected.to include(description: project.description) } + it { is_expected.to include(homepage: project.web_url) } end context "with commits" do subject { @push_data[:commits] } - it { should be_an(Array) } - it { should have(1).element } + it { is_expected.to be_an(Array) } + it 'has 1 element' do + expect(subject.size).to eq(1) + end context "the commit" do subject { @push_data[:commits].first } - it { should include(id: @commit.id) } - it { should include(message: @commit.safe_message) } - it { should include(timestamp: @commit.date.xmlschema) } - it { should include(url: "#{Gitlab.config.gitlab.url}/#{project.to_param}/commit/#{@commit.id}") } + it { is_expected.to include(id: @commit.id) } + it { is_expected.to include(message: @commit.safe_message) } + it { is_expected.to include(timestamp: @commit.date.xmlschema) } + it do + is_expected.to include( + url: [ + Gitlab.config.gitlab.url, + project.namespace.to_param, + project.to_param, + 'commit', + @commit.id + ].join('/') + ) + end context "with a author" do subject { @push_data[:commits].first[:author] } - it { should include(name: @commit.author_name) } - it { should include(email: @commit.author_email) } + it { is_expected.to include(name: @commit.author_name) } + it { is_expected.to include(email: @commit.author_email) } end end end @@ -69,28 +108,43 @@ describe GitPushService do @event = Event.last end - it { @event.should_not be_nil } - it { @event.project.should == project } - it { @event.action.should == Event::PUSHED } - it { @event.data.should == service.push_data } + it { expect(@event).not_to be_nil } + it { expect(@event.project).to eq(project) } + it { expect(@event.action).to eq(Event::PUSHED) } + it { expect(@event.data).to eq(service.push_data) } end describe "Web Hooks" do context "execute web hooks" do it "when pushing a branch for the first time" do - project.should_receive(:execute_hooks) + expect(project).to receive(:execute_hooks) + expect(project.default_branch).to eq("master") + expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: false }) + service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master') + end + + it "when pushing a branch for the first time with default branch protection disabled" do + ApplicationSetting.any_instance.stub(default_branch_protection: 0) + + expect(project).to receive(:execute_hooks) + expect(project.default_branch).to eq("master") + expect(project.protected_branches).not_to receive(:create) + service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master') + end + + it "when pushing a branch for the first time with default branch protection set to 'developers can push'" do + ApplicationSetting.any_instance.stub(default_branch_protection: 1) + + expect(project).to receive(:execute_hooks) + expect(project.default_branch).to eq("master") + expect(project.protected_branches).to receive(:create).with({ name: "master", developers_can_push: true }) service.execute(project, user, @blankrev, 'newrev', 'refs/heads/master') end it "when pushing new commits to existing branch" do - project.should_receive(:execute_hooks) + expect(project).to receive(:execute_hooks) service.execute(project, user, 'oldrev', 'newrev', 'refs/heads/master') end - - it "when pushing tags" do - project.should_not_receive(:execute_hooks) - service.execute(project, user, 'newrev', 'newrev', 'refs/tags/v1.0.0') - end end end @@ -110,7 +164,7 @@ describe GitPushService do end it "creates a note if a pushed commit mentions an issue" do - Note.should_receive(:create_cross_reference_note).with(issue, commit, commit_author, project) + expect(Note).to receive(:create_cross_reference_note).with(issue, commit, commit_author, project) service.execute(project, user, @oldrev, @newrev, @ref) end @@ -118,35 +172,26 @@ describe GitPushService do it "only creates a cross-reference note if one doesn't already exist" do Note.create_cross_reference_note(issue, commit, user, project) - Note.should_not_receive(:create_cross_reference_note).with(issue, commit, commit_author, project) + expect(Note).not_to receive(:create_cross_reference_note).with(issue, commit, commit_author, project) service.execute(project, user, @oldrev, @newrev, @ref) end it "defaults to the pushing user if the commit's author is not known" do commit.stub(author_name: 'unknown name', author_email: 'unknown@email.com') - Note.should_receive(:create_cross_reference_note).with(issue, commit, user, project) + expect(Note).to receive(:create_cross_reference_note).with(issue, commit, user, project) service.execute(project, user, @oldrev, @newrev, @ref) end it "finds references in the first push to a non-default branch" do - project.repository.stub(:commits_between).with(@blankrev, @newrev).and_return([]) - project.repository.stub(:commits_between).with("master", @newrev).and_return([commit]) + allow(project.repository).to receive(:commits_between).with(@blankrev, @newrev).and_return([]) + allow(project.repository).to receive(:commits_between).with("master", @newrev).and_return([commit]) - Note.should_receive(:create_cross_reference_note).with(issue, commit, commit_author, project) + expect(Note).to receive(:create_cross_reference_note).with(issue, commit, commit_author, project) service.execute(project, user, @blankrev, @newrev, 'refs/heads/other') end - - it "finds references in the first push to a default branch" do - project.repository.stub(:commits_between).with(@blankrev, @newrev).and_return([]) - project.repository.stub(:commits).with(@newrev).and_return([commit]) - - Note.should_receive(:create_cross_reference_note).with(issue, commit, commit_author, project) - - service.execute(project, user, @blankrev, @newrev, 'refs/heads/master') - end end describe "closing issues from pushed commits" do @@ -169,7 +214,7 @@ describe GitPushService do it "closes issues with commit messages" do service.execute(project, user, @oldrev, @newrev, @ref) - Issue.find(issue.id).should be_closed + expect(Issue.find(issue.id)).to be_closed end it "doesn't create cross-reference notes for a closing reference" do @@ -186,7 +231,7 @@ describe GitPushService do service.execute(project, user, @oldrev, @newrev, 'refs/heads/hurf') }.not_to change { Note.where(project_id: project.id, system: true).count } - Issue.find(issue.id).should be_opened + expect(Issue.find(issue.id)).to be_opened end end end diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb index e65a8204c5..a050fdf6c0 100644 --- a/spec/services/git_tag_push_service_spec.rb +++ b/spec/services/git_tag_push_service_spec.rb @@ -1,45 +1,87 @@ require 'spec_helper' describe GitTagPushService do + include RepoHelpers + let (:user) { create :user } let (:project) { create :project } let (:service) { GitTagPushService.new } before do - @ref = 'refs/tags/super-tag' - @oldrev = 'b98a310def241a6fd9c9a9a3e7934c48e498fe81' - @newrev = 'b19a04f53caeebf4fe5ec2327cb83e9253dc91bb' + @oldrev = Gitlab::Git::BLANK_SHA + @newrev = "8a2a6eb295bb170b34c24c76c49ed0e9b2eaf34b" # gitlab-test: git rev-parse refs/tags/v1.1.0 + @ref = 'refs/tags/v1.1.0' end - describe 'Git Tag Push Data' do + describe "Git Tag Push Data" do before do service.execute(project, user, @oldrev, @newrev, @ref) @push_data = service.push_data + @tag_name = Gitlab::Git.ref_name(@ref) + @tag = project.repository.find_tag(@tag_name) + @commit = project.repository.commit(@tag.target) end subject { @push_data } - it { should include(ref: @ref) } - it { should include(before: @oldrev) } - it { should include(after: @newrev) } - it { should include(user_id: user.id) } - it { should include(user_name: user.name) } - it { should include(project_id: project.id) } + it { is_expected.to include(object_kind: 'tag_push') } + it { is_expected.to include(ref: @ref) } + it { is_expected.to include(before: @oldrev) } + it { is_expected.to include(after: @newrev) } + it { is_expected.to include(message: @tag.message) } + it { is_expected.to include(user_id: user.id) } + it { is_expected.to include(user_name: user.name) } + it { is_expected.to include(project_id: project.id) } - context 'With repository data' do + context "with repository data" do subject { @push_data[:repository] } - it { should include(name: project.name) } - it { should include(url: project.url_to_repo) } - it { should include(description: project.description) } - it { should include(homepage: project.web_url) } + it { is_expected.to include(name: project.name) } + it { is_expected.to include(url: project.url_to_repo) } + it { is_expected.to include(description: project.description) } + it { is_expected.to include(homepage: project.web_url) } + end + + context "with commits" do + subject { @push_data[:commits] } + + it { is_expected.to be_an(Array) } + it 'has 1 element' do + expect(subject.size).to eq(1) + end + + context "the commit" do + subject { @push_data[:commits].first } + + it { is_expected.to include(id: @commit.id) } + it { is_expected.to include(message: @commit.safe_message) } + it { is_expected.to include(timestamp: @commit.date.xmlschema) } + it do + is_expected.to include( + url: [ + Gitlab.config.gitlab.url, + project.namespace.to_param, + project.to_param, + 'commit', + @commit.id + ].join('/') + ) + end + + context "with a author" do + subject { @push_data[:commits].first[:author] } + + it { is_expected.to include(name: @commit.author_name) } + it { is_expected.to include(email: @commit.author_email) } + end + end end end describe "Web Hooks" do context "execute web hooks" do it "when pushing tags" do - project.should_receive(:execute_hooks) + expect(project).to receive(:execute_hooks) service.execute(project, user, 'oldrev', 'newrev', 'refs/tags/v1.0.0') end end diff --git a/spec/services/issues/bulk_update_context_spec.rb b/spec/services/issues/bulk_update_context_spec.rb deleted file mode 100644 index f4c9148f1a..0000000000 --- a/spec/services/issues/bulk_update_context_spec.rb +++ /dev/null @@ -1,110 +0,0 @@ -require 'spec_helper' - -describe Issues::BulkUpdateService do - let(:issue) { - create(:issue, project: @project) - } - - before do - @user = create :user - opts = { - name: "GitLab", - namespace: @user.namespace - } - @project = Projects::CreateService.new(@user, opts).execute - end - - describe :close_issue do - - before do - @issues = 5.times.collect do - create(:issue, project: @project) - end - @params = { - update: { - status: 'closed', - issues_ids: @issues.map(&:id) - } - } - end - - it { - result = Issues::BulkUpdateService.new(@project, @user, @params).execute - result[:success].should be_true - result[:count].should == @issues.count - - @project.issues.opened.should be_empty - @project.issues.closed.should_not be_empty - } - - end - - describe :reopen_issues do - - before do - @issues = 5.times.collect do - create(:closed_issue, project: @project) - end - @params = { - update: { - status: 'reopen', - issues_ids: @issues.map(&:id) - } - } - end - - it { - result = Issues::BulkUpdateService.new(@project, @user, @params).execute - result[:success].should be_true - result[:count].should == @issues.count - - @project.issues.closed.should be_empty - @project.issues.opened.should_not be_empty - } - - end - - describe :update_assignee do - - before do - @new_assignee = create :user - @params = { - update: { - issues_ids: [issue.id], - assignee_id: @new_assignee.id - } - } - end - - it { - result = Issues::BulkUpdateService.new(@project, @user, @params).execute - result[:success].should be_true - result[:count].should == 1 - - @project.issues.first.assignee.should == @new_assignee - } - - end - - describe :update_milestone do - - before do - @milestone = create :milestone - @params = { - update: { - issues_ids: [issue.id], - milestone_id: @milestone.id - } - } - end - - it { - result = Issues::BulkUpdateService.new(@project, @user, @params).execute - result[:success].should be_true - result[:count].should == 1 - - @project.issues.first.milestone.should == @milestone - } - end - -end diff --git a/spec/services/issues/bulk_update_service_spec.rb b/spec/services/issues/bulk_update_service_spec.rb new file mode 100644 index 0000000000..a97c55011c --- /dev/null +++ b/spec/services/issues/bulk_update_service_spec.rb @@ -0,0 +1,121 @@ +require 'spec_helper' + +describe Issues::BulkUpdateService do + let(:issue) { + create(:issue, project: @project) + } + + before do + @user = create :user + opts = { + name: "GitLab", + namespace: @user.namespace + } + @project = Projects::CreateService.new(@user, opts).execute + end + + describe :close_issue do + + before do + @issues = 5.times.collect do + create(:issue, project: @project) + end + @params = { + state_event: 'close', + issues_ids: @issues.map(&:id) + } + end + + it { + result = Issues::BulkUpdateService.new(@project, @user, @params).execute + expect(result[:success]).to be_truthy + expect(result[:count]).to eq(@issues.count) + + expect(@project.issues.opened).to be_empty + expect(@project.issues.closed).not_to be_empty + } + + end + + describe :reopen_issues do + + before do + @issues = 5.times.collect do + create(:closed_issue, project: @project) + end + @params = { + state_event: 'reopen', + issues_ids: @issues.map(&:id) + } + end + + it { + result = Issues::BulkUpdateService.new(@project, @user, @params).execute + expect(result[:success]).to be_truthy + expect(result[:count]).to eq(@issues.count) + + expect(@project.issues.closed).to be_empty + expect(@project.issues.opened).not_to be_empty + } + + end + + describe :update_assignee do + + before do + @new_assignee = create :user + @params = { + issues_ids: [issue.id], + assignee_id: @new_assignee.id + } + end + + it { + result = Issues::BulkUpdateService.new(@project, @user, @params).execute + expect(result[:success]).to be_truthy + expect(result[:count]).to eq(1) + + expect(@project.issues.first.assignee).to eq(@new_assignee) + } + + it 'allows mass-unassigning' do + @project.issues.first.update_attribute(:assignee, @new_assignee) + expect(@project.issues.first.assignee).not_to be_nil + + @params[:assignee_id] = -1 + + Issues::BulkUpdateService.new(@project, @user, @params).execute + expect(@project.issues.first.assignee).to be_nil + end + + it 'does not unassign when assignee_id is not present' do + @project.issues.first.update_attribute(:assignee, @new_assignee) + expect(@project.issues.first.assignee).not_to be_nil + + @params[:assignee_id] = '' + + Issues::BulkUpdateService.new(@project, @user, @params).execute + expect(@project.issues.first.assignee).not_to be_nil + end + end + + describe :update_milestone do + + before do + @milestone = create :milestone + @params = { + issues_ids: [issue.id], + milestone_id: @milestone.id + } + end + + it { + result = Issues::BulkUpdateService.new(@project, @user, @params).execute + expect(result[:success]).to be_truthy + expect(result[:count]).to eq(1) + + expect(@project.issues.first.milestone).to eq(@milestone) + } + end + +end diff --git a/spec/services/issues/close_service_spec.rb b/spec/services/issues/close_service_spec.rb index d4f2cc1339..d15dff1b52 100644 --- a/spec/services/issues/close_service_spec.rb +++ b/spec/services/issues/close_service_spec.rb @@ -17,18 +17,18 @@ describe Issues::CloseService do @issue = Issues::CloseService.new(project, user, {}).execute(issue) end - it { @issue.should be_valid } - it { @issue.should be_closed } + it { expect(@issue).to be_valid } + it { expect(@issue).to be_closed } it 'should send email to user2 about assign of new issue' do email = ActionMailer::Base.deliveries.last - email.to.first.should == user2.email - email.subject.should include(issue.title) + expect(email.to.first).to eq(user2.email) + expect(email.subject).to include(issue.title) end it 'should create system note about issue reassign' do note = @issue.notes.last - note.note.should include "Status changed to closed" + expect(note.note).to include "Status changed to closed" end end end diff --git a/spec/services/issues/create_service_spec.rb b/spec/services/issues/create_service_spec.rb index 90720be5de..7f1ebcb319 100644 --- a/spec/services/issues/create_service_spec.rb +++ b/spec/services/issues/create_service_spec.rb @@ -16,8 +16,8 @@ describe Issues::CreateService do @issue = Issues::CreateService.new(project, user, opts).execute end - it { @issue.should be_valid } - it { @issue.title.should == 'Awesome issue' } + it { expect(@issue).to be_valid } + it { expect(@issue.title).to eq('Awesome issue') } end end end diff --git a/spec/services/issues/update_service_spec.rb b/spec/services/issues/update_service_spec.rb index 347560414e..22b89bec96 100644 --- a/spec/services/issues/update_service_spec.rb +++ b/spec/services/issues/update_service_spec.rb @@ -5,6 +5,7 @@ describe Issues::UpdateService do let(:user) { create(:user) } let(:user2) { create(:user) } let(:issue) { create(:issue) } + let(:label) { create(:label) } before do project.team << [user, :master] @@ -18,26 +19,35 @@ describe Issues::UpdateService do title: 'New title', description: 'Also please fix', assignee_id: user2.id, - state_event: 'close' + state_event: 'close', + label_ids: [label.id] } @issue = Issues::UpdateService.new(project, user, opts).execute(issue) + @issue.reload end - it { @issue.should be_valid } - it { @issue.title.should == 'New title' } - it { @issue.assignee.should == user2 } - it { @issue.should be_closed } + it { expect(@issue).to be_valid } + it { expect(@issue.title).to eq('New title') } + it { expect(@issue.assignee).to eq(user2) } + it { expect(@issue).to be_closed } + it { expect(@issue.labels.count).to eq(1) } + it { expect(@issue.labels.first.title).to eq('Bug') } it 'should send email to user2 about assign of new issue' do email = ActionMailer::Base.deliveries.last - email.to.first.should == user2.email - email.subject.should include(issue.title) + expect(email.to.first).to eq(user2.email) + expect(email.subject).to include(issue.title) end it 'should create system note about issue reassign' do note = @issue.notes.last - note.note.should include "Reassigned to \@#{user2.username}" + expect(note.note).to include "Reassigned to \@#{user2.username}" + end + + it 'should create system note about issue label edit' do + note = @issue.notes[1] + expect(note.note).to include "Added ~#{label.id} label" end end end diff --git a/spec/services/merge_requests/close_service_spec.rb b/spec/services/merge_requests/close_service_spec.rb index a504f916b0..b3cbfd4b5b 100644 --- a/spec/services/merge_requests/close_service_spec.rb +++ b/spec/services/merge_requests/close_service_spec.rb @@ -12,23 +12,32 @@ describe MergeRequests::CloseService do end describe :execute do - context "valid params" do + context 'valid params' do + let(:service) { MergeRequests::CloseService.new(project, user, {}) } + before do - @merge_request = MergeRequests::CloseService.new(project, user, {}).execute(merge_request) + allow(service).to receive(:execute_hooks) + + @merge_request = service.execute(merge_request) end - it { @merge_request.should be_valid } - it { @merge_request.should be_closed } + it { expect(@merge_request).to be_valid } + it { expect(@merge_request).to be_closed } + + it 'should execute hooks with close action' do + expect(service).to have_received(:execute_hooks). + with(@merge_request, 'close') + end it 'should send email to user2 about assign of new merge_request' do email = ActionMailer::Base.deliveries.last - email.to.first.should == user2.email - email.subject.should include(merge_request.title) + expect(email.to.first).to eq(user2.email) + expect(email.subject).to include(merge_request.title) end it 'should create system note about merge_request reassign' do note = @merge_request.notes.last - note.note.should include "Status changed to closed" + expect(note.note).to include 'Status changed to closed' end end end diff --git a/spec/services/merge_requests/create_service_spec.rb b/spec/services/merge_requests/create_service_spec.rb index cebeb0644d..d9bfdf6430 100644 --- a/spec/services/merge_requests/create_service_spec.rb +++ b/spec/services/merge_requests/create_service_spec.rb @@ -5,21 +5,30 @@ describe MergeRequests::CreateService do let(:user) { create(:user) } describe :execute do - context "valid params" do - before do - project.team << [user, :master] - opts = { + context 'valid params' do + let(:opts) do + { title: 'Awesome merge_request', description: 'please fix', source_branch: 'stable', target_branch: 'master' } + end + let(:service) { MergeRequests::CreateService.new(project, user, opts) } - @merge_request = MergeRequests::CreateService.new(project, user, opts).execute + before do + project.team << [user, :master] + allow(service).to receive(:execute_hooks) + + @merge_request = service.execute end - it { @merge_request.should be_valid } - it { @merge_request.title.should == 'Awesome merge_request' } + it { expect(@merge_request).to be_valid } + it { expect(@merge_request.title).to eq('Awesome merge_request') } + + it 'should execute hooks with default action' do + expect(service).to have_received(:execute_hooks).with(@merge_request) + end end end end diff --git a/spec/services/merge_requests/merge_service_spec.rb b/spec/services/merge_requests/merge_service_spec.rb new file mode 100644 index 0000000000..0a25fb12f4 --- /dev/null +++ b/spec/services/merge_requests/merge_service_spec.rb @@ -0,0 +1,44 @@ +require 'spec_helper' + +describe MergeRequests::MergeService do + let(:user) { create(:user) } + let(:user2) { create(:user) } + let(:merge_request) { create(:merge_request, assignee: user2) } + let(:project) { merge_request.project } + + before do + project.team << [user, :master] + project.team << [user2, :developer] + end + + describe :execute do + context 'valid params' do + let(:service) { MergeRequests::MergeService.new(project, user, {}) } + + before do + allow(service).to receive(:execute_hooks) + + service.execute(merge_request, 'Awesome message') + end + + it { expect(merge_request).to be_valid } + it { expect(merge_request).to be_merged } + + it 'should execute hooks with merge action' do + expect(service).to have_received(:execute_hooks). + with(merge_request, 'merge') + end + + it 'should send email to user2 about merge of new merge_request' do + email = ActionMailer::Base.deliveries.last + expect(email.to.first).to eq(user2.email) + expect(email.subject).to include(merge_request.title) + end + + it 'should create system note about merge_request merge' do + note = merge_request.notes.last + expect(note.note).to include 'Status changed to merged' + end + end + end +end diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb new file mode 100644 index 0000000000..879df0c9c6 --- /dev/null +++ b/spec/services/merge_requests/refresh_service_spec.rb @@ -0,0 +1,98 @@ +require 'spec_helper' + +describe MergeRequests::RefreshService do + let(:project) { create(:project) } + let(:user) { create(:user) } + let(:service) { MergeRequests::RefreshService } + + describe :execute do + before do + @user = create(:user) + group = create(:group) + group.add_owner(@user) + + @project = create(:project, namespace: group) + @fork_project = Projects::ForkService.new(@project, @user).execute + @merge_request = create(:merge_request, source_project: @project, + source_branch: 'master', + target_branch: 'feature', + target_project: @project) + + @fork_merge_request = create(:merge_request, source_project: @fork_project, + source_branch: 'master', + target_branch: 'feature', + target_project: @project) + + @commits = @merge_request.commits + + @oldrev = @commits.last.id + @newrev = @commits.first.id + end + + context 'push to origin repo source branch' do + before do + service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/master') + reload_mrs + end + + it { expect(@merge_request.notes).not_to be_empty } + it { expect(@merge_request).to be_open } + it { expect(@fork_merge_request).to be_open } + it { expect(@fork_merge_request.notes).to be_empty } + end + + context 'push to origin repo target branch' do + before do + service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature') + reload_mrs + end + + it { expect(@merge_request.notes.last.note).to include('changed to merged') } + 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') } + end + + context 'push to fork repo source branch' do + before do + service.new(@fork_project, @user).execute(@oldrev, @newrev, 'refs/heads/master') + reload_mrs + end + + it { expect(@merge_request.notes).to be_empty } + 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 } + end + + context 'push to fork repo target branch' do + before do + service.new(@fork_project, @user).execute(@oldrev, @newrev, 'refs/heads/feature') + reload_mrs + end + + it { expect(@merge_request.notes).to be_empty } + it { expect(@merge_request).to be_open } + it { expect(@fork_merge_request.notes).to be_empty } + it { expect(@fork_merge_request).to be_open } + end + + context 'push to origin repo target branch after fork project was removed' do + before do + @fork_project.destroy + service.new(@project, @user).execute(@oldrev, @newrev, 'refs/heads/feature') + reload_mrs + end + + it { expect(@merge_request.notes.last.note).to include('changed to merged') } + it { expect(@merge_request).to be_merged } + it { expect(@fork_merge_request).to be_open } + it { expect(@fork_merge_request.notes).to be_empty } + end + + def reload_mrs + @merge_request.reload + @fork_merge_request.reload + end + end +end diff --git a/spec/services/merge_requests/reopen_service_spec.rb b/spec/services/merge_requests/reopen_service_spec.rb new file mode 100644 index 0000000000..9401bc3b55 --- /dev/null +++ b/spec/services/merge_requests/reopen_service_spec.rb @@ -0,0 +1,45 @@ +require 'spec_helper' + +describe MergeRequests::ReopenService do + let(:user) { create(:user) } + let(:user2) { create(:user) } + let(:merge_request) { create(:merge_request, assignee: user2) } + let(:project) { merge_request.project } + + before do + project.team << [user, :master] + project.team << [user2, :developer] + end + + describe :execute do + context 'valid params' do + let(:service) { MergeRequests::ReopenService.new(project, user, {}) } + + before do + allow(service).to receive(:execute_hooks) + + merge_request.state = :closed + service.execute(merge_request) + end + + it { expect(merge_request).to be_valid } + it { expect(merge_request).to be_reopened } + + it 'should execute hooks with reopen action' do + expect(service).to have_received(:execute_hooks). + with(merge_request, 'reopen') + end + + it 'should send email to user2 about reopen of merge_request' do + email = ActionMailer::Base.deliveries.last + expect(email.to.first).to eq(user2.email) + expect(email.subject).to include(merge_request.title) + end + + it 'should create system note about merge_request reopen' do + note = merge_request.notes.last + expect(note.note).to include 'Status changed to reopened' + end + end + end +end diff --git a/spec/services/merge_requests/update_service_spec.rb b/spec/services/merge_requests/update_service_spec.rb index af5d3a3dc8..916b01e1c4 100644 --- a/spec/services/merge_requests/update_service_spec.rb +++ b/spec/services/merge_requests/update_service_spec.rb @@ -5,6 +5,7 @@ describe MergeRequests::UpdateService do let(:user2) { create(:user) } let(:merge_request) { create(:merge_request, :simple) } let(:project) { merge_request.project } + let(:label) { create(:label) } before do project.team << [user, :master] @@ -12,32 +13,52 @@ describe MergeRequests::UpdateService do end describe :execute do - context "valid params" do - before do - opts = { + context 'valid params' do + let(:opts) do + { title: 'New title', description: 'Also please fix', assignee_id: user2.id, - state_event: 'close' + state_event: 'close', + label_ids: [label.id] } - - @merge_request = MergeRequests::UpdateService.new(project, user, opts).execute(merge_request) end - it { @merge_request.should be_valid } - it { @merge_request.title.should == 'New title' } - it { @merge_request.assignee.should == user2 } - it { @merge_request.should be_closed } + let(:service) { MergeRequests::UpdateService.new(project, user, opts) } + + before do + allow(service).to receive(:execute_hooks) + + @merge_request = service.execute(merge_request) + @merge_request.reload + end + + it { expect(@merge_request).to be_valid } + it { expect(@merge_request.title).to eq('New title') } + it { expect(@merge_request.assignee).to eq(user2) } + it { expect(@merge_request).to be_closed } + it { expect(@merge_request.labels.count).to eq(1) } + it { expect(@merge_request.labels.first.title).to eq('Bug') } + + it 'should execute hooks with update action' do + expect(service).to have_received(:execute_hooks). + with(@merge_request, 'update') + end it 'should send email to user2 about assign of new merge_request' do email = ActionMailer::Base.deliveries.last - email.to.first.should == user2.email - email.subject.should include(merge_request.title) + expect(email.to.first).to eq(user2.email) + expect(email.subject).to include(merge_request.title) end it 'should create system note about merge_request reassign' do note = @merge_request.notes.last - note.note.should include "Reassigned to \@#{user2.username}" + expect(note.note).to include "Reassigned to \@#{user2.username}" + end + + it 'should create system note about merge_request label edit' do + note = @merge_request.notes[1] + expect(note.note).to include "Added ~#{label.id} label" end end end diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb index f59786efcf..1a02299bf1 100644 --- a/spec/services/notes/create_service_spec.rb +++ b/spec/services/notes/create_service_spec.rb @@ -18,8 +18,8 @@ describe Notes::CreateService do @note = Notes::CreateService.new(project, user, opts).execute end - it { @note.should be_valid } - it { @note.note.should == 'Awesome comment' } + it { expect(@note).to be_valid } + it { expect(@note.note).to eq('Awesome comment') } end end end diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb index df355f6f07..bfca2c8826 100644 --- a/spec/services/notification_service_spec.rb +++ b/spec/services/notification_service_spec.rb @@ -7,10 +7,10 @@ describe NotificationService do describe :new_key do let!(:key) { create(:personal_key) } - it { notification.new_key(key).should be_true } + it { expect(notification.new_key(key)).to be_truthy } it 'should sent email to key owner' do - Notify.should_receive(:new_ssh_key_email).with(key.id) + expect(Notify).to receive(:new_ssh_key_email).with(key.id) notification.new_key(key) end end @@ -20,10 +20,10 @@ describe NotificationService do describe :new_email do let!(:email) { create(:email) } - it { notification.new_email(email).should be_true } + it { expect(notification.new_email(email)).to be_truthy } it 'should send email to email owner' do - Notify.should_receive(:new_email_email).with(email.id) + expect(Notify).to receive(:new_email_email).with(email.id) notification.new_email(email) end end @@ -41,20 +41,25 @@ describe NotificationService do describe :new_note do it do + add_users_with_subscription(note.project, issue) + should_email(@u_watcher.id) should_email(note.noteable.author_id) should_email(note.noteable.assignee_id) should_email(@u_mentioned.id) + should_email(@subscriber.id) should_not_email(note.author_id) should_not_email(@u_participating.id) should_not_email(@u_disabled.id) + should_not_email(@unsubscriber.id) + notification.new_note(note) end it 'filters out "mentioned in" notes' do mentioned_note = Note.create_cross_reference_note(mentioned_issue, issue, issue.author, issue.project) - Notify.should_not_receive(:note_issue_email) + expect(Notify).not_to receive(:note_issue_email) notification.new_note(mentioned_note) end end @@ -64,14 +69,14 @@ describe NotificationService do before do note.project.namespace_id = group.id - note.project.group.add_user(@u_watcher, UsersGroup::MASTER) + note.project.group.add_user(@u_watcher, GroupMember::MASTER) note.project.save - user_project = note.project.users_projects.find_by_user_id(@u_watcher.id) + user_project = note.project.project_members.find_by_user_id(@u_watcher.id) user_project.notification_level = Notification::N_PARTICIPATING user_project.save - user_group = note.project.group.users_groups.find_by_user_id(@u_watcher.id) - user_group.notification_level = Notification::N_GLOBAL - user_group.save + group_member = note.project.group.group_members.find_by_user_id(@u_watcher.id) + group_member.notification_level = Notification::N_GLOBAL + group_member.save end it do @@ -87,11 +92,11 @@ describe NotificationService do end def should_email(user_id) - Notify.should_receive(:note_issue_email).with(user_id, note.id) + expect(Notify).to receive(:note_issue_email).with(user_id, note.id) end def should_not_email(user_id) - Notify.should_not_receive(:note_issue_email).with(user_id, note.id) + expect(Notify).not_to receive(:note_issue_email).with(user_id, note.id) end end @@ -116,6 +121,7 @@ describe NotificationService do should_email(note.noteable.assignee_id) should_not_email(note.author_id) + should_not_email(@u_mentioned.id) should_not_email(@u_disabled.id) should_not_email(@u_not_mentioned.id) notification.new_note(note) @@ -124,17 +130,17 @@ describe NotificationService do it 'filters out "mentioned in" notes' do mentioned_note = Note.create_cross_reference_note(mentioned_issue, issue, issue.author, issue.project) - Notify.should_not_receive(:note_issue_email) + expect(Notify).not_to receive(:note_issue_email) notification.new_note(mentioned_note) end end def should_email(user_id) - Notify.should_receive(:note_issue_email).with(user_id, note.id) + expect(Notify).to receive(:note_issue_email).with(user_id, note.id) end def should_not_email(user_id) - Notify.should_not_receive(:note_issue_email).with(user_id, note.id) + expect(Notify).not_to receive(:note_issue_email).with(user_id, note.id) end end @@ -168,39 +174,54 @@ describe NotificationService do notification.new_note(note) end + it do + @u_committer.update_attributes(notification_level: Notification::N_MENTION) + should_not_email(@u_committer.id, note) + notification.new_note(note) + end + def should_email(user_id, n) - Notify.should_receive(:note_commit_email).with(user_id, n.id) + expect(Notify).to receive(:note_commit_email).with(user_id, n.id) end def should_not_email(user_id, n) - Notify.should_not_receive(:note_commit_email).with(user_id, n.id) + expect(Notify).not_to receive(:note_commit_email).with(user_id, n.id) end end end end describe 'Issues' do - let(:issue) { create :issue, assignee: create(:user) } + let(:issue) { create :issue, assignee: create(:user), description: 'cc @participant' } before do build_team(issue.project) + add_users_with_subscription(issue.project, issue) end describe :new_issue do it do should_email(issue.assignee_id) should_email(@u_watcher.id) + should_email(@u_participant_mentioned.id) + should_not_email(@u_mentioned.id) should_not_email(@u_participating.id) should_not_email(@u_disabled.id) notification.new_issue(issue, @u_disabled) end + it do + issue.assignee.update_attributes(notification_level: Notification::N_MENTION) + should_not_email(issue.assignee_id) + notification.new_issue(issue, @u_disabled) + end + def should_email(user_id) - Notify.should_receive(:new_issue_email).with(user_id, issue.id) + expect(Notify).to receive(:new_issue_email).with(user_id, issue.id) end def should_not_email(user_id) - Notify.should_not_receive(:new_issue_email).with(user_id, issue.id) + expect(Notify).not_to receive(:new_issue_email).with(user_id, issue.id) end end @@ -208,6 +229,9 @@ describe NotificationService do it 'should email new assignee' do should_email(issue.assignee_id) should_email(@u_watcher.id) + should_email(@u_participant_mentioned.id) + should_email(@subscriber.id) + should_not_email(@unsubscriber.id) should_not_email(@u_participating.id) should_not_email(@u_disabled.id) @@ -215,11 +239,11 @@ describe NotificationService do end def should_email(user_id) - Notify.should_receive(:reassigned_issue_email).with(user_id, issue.id, nil, @u_disabled.id) + expect(Notify).to receive(:reassigned_issue_email).with(user_id, issue.id, nil, @u_disabled.id) end def should_not_email(user_id) - Notify.should_not_receive(:reassigned_issue_email).with(user_id, issue.id, issue.assignee_id, @u_disabled.id) + expect(Notify).not_to receive(:reassigned_issue_email).with(user_id, issue.id, issue.assignee_id, @u_disabled.id) end end @@ -228,6 +252,9 @@ describe NotificationService do should_email(issue.assignee_id) should_email(issue.author_id) should_email(@u_watcher.id) + should_email(@u_participant_mentioned.id) + should_email(@subscriber.id) + should_not_email(@unsubscriber.id) should_not_email(@u_participating.id) should_not_email(@u_disabled.id) @@ -235,11 +262,11 @@ describe NotificationService do end def should_email(user_id) - Notify.should_receive(:closed_issue_email).with(user_id, issue.id, @u_disabled.id) + expect(Notify).to receive(:closed_issue_email).with(user_id, issue.id, @u_disabled.id) end def should_not_email(user_id) - Notify.should_not_receive(:closed_issue_email).with(user_id, issue.id, @u_disabled.id) + expect(Notify).not_to receive(:closed_issue_email).with(user_id, issue.id, @u_disabled.id) end end @@ -248,6 +275,9 @@ describe NotificationService do should_email(issue.assignee_id) should_email(issue.author_id) should_email(@u_watcher.id) + should_email(@u_participant_mentioned.id) + should_email(@subscriber.id) + should_not_email(@unsubscriber.id) should_not_email(@u_participating.id) should_not_email(@u_disabled.id) @@ -255,11 +285,11 @@ describe NotificationService do end def should_email(user_id) - Notify.should_receive(:issue_status_changed_email).with(user_id, issue.id, 'reopened', @u_disabled.id) + expect(Notify).to receive(:issue_status_changed_email).with(user_id, issue.id, 'reopened', @u_disabled.id) end def should_not_email(user_id) - Notify.should_not_receive(:issue_status_changed_email).with(user_id, issue.id, 'reopened', @u_disabled.id) + expect(Notify).not_to receive(:issue_status_changed_email).with(user_id, issue.id, 'reopened', @u_disabled.id) end end end @@ -269,6 +299,7 @@ describe NotificationService do before do build_team(merge_request.target_project) + add_users_with_subscription(merge_request.target_project, merge_request) end describe :new_merge_request do @@ -281,11 +312,11 @@ describe NotificationService do end def should_email(user_id) - Notify.should_receive(:new_merge_request_email).with(user_id, merge_request.id) + expect(Notify).to receive(:new_merge_request_email).with(user_id, merge_request.id) end def should_not_email(user_id) - Notify.should_not_receive(:new_merge_request_email).with(user_id, merge_request.id) + expect(Notify).not_to receive(:new_merge_request_email).with(user_id, merge_request.id) end end @@ -293,17 +324,19 @@ describe NotificationService do it do should_email(merge_request.assignee_id) should_email(@u_watcher.id) + should_email(@subscriber.id) + should_not_email(@unsubscriber.id) should_not_email(@u_participating.id) should_not_email(@u_disabled.id) notification.reassigned_merge_request(merge_request, merge_request.author) end def should_email(user_id) - Notify.should_receive(:reassigned_merge_request_email).with(user_id, merge_request.id, nil, merge_request.author_id) + expect(Notify).to receive(:reassigned_merge_request_email).with(user_id, merge_request.id, nil, merge_request.author_id) end def should_not_email(user_id) - Notify.should_not_receive(:reassigned_merge_request_email).with(user_id, merge_request.id, merge_request.assignee_id, merge_request.author_id) + expect(Notify).not_to receive(:reassigned_merge_request_email).with(user_id, merge_request.id, merge_request.assignee_id, merge_request.author_id) end end @@ -311,17 +344,19 @@ describe NotificationService do it do should_email(merge_request.assignee_id) should_email(@u_watcher.id) + should_email(@subscriber.id) + should_not_email(@unsubscriber.id) should_not_email(@u_participating.id) should_not_email(@u_disabled.id) notification.close_mr(merge_request, @u_disabled) end def should_email(user_id) - Notify.should_receive(:closed_merge_request_email).with(user_id, merge_request.id, @u_disabled.id) + expect(Notify).to receive(:closed_merge_request_email).with(user_id, merge_request.id, @u_disabled.id) end def should_not_email(user_id) - Notify.should_not_receive(:closed_merge_request_email).with(user_id, merge_request.id, @u_disabled.id) + expect(Notify).not_to receive(:closed_merge_request_email).with(user_id, merge_request.id, @u_disabled.id) end end @@ -329,17 +364,19 @@ describe NotificationService do it do should_email(merge_request.assignee_id) should_email(@u_watcher.id) + should_email(@subscriber.id) + should_not_email(@unsubscriber.id) should_not_email(@u_participating.id) should_not_email(@u_disabled.id) notification.merge_mr(merge_request, @u_disabled) end def should_email(user_id) - Notify.should_receive(:merged_merge_request_email).with(user_id, merge_request.id, @u_disabled.id) + expect(Notify).to receive(:merged_merge_request_email).with(user_id, merge_request.id, @u_disabled.id) end def should_not_email(user_id) - Notify.should_not_receive(:merged_merge_request_email).with(user_id, merge_request.id, @u_disabled.id) + expect(Notify).not_to receive(:merged_merge_request_email).with(user_id, merge_request.id, @u_disabled.id) end end @@ -347,17 +384,19 @@ describe NotificationService do it do should_email(merge_request.assignee_id) should_email(@u_watcher.id) + should_email(@subscriber.id) + should_not_email(@unsubscriber.id) should_not_email(@u_participating.id) should_not_email(@u_disabled.id) notification.reopen_mr(merge_request, @u_disabled) end def should_email(user_id) - Notify.should_receive(:merge_request_status_email).with(user_id, merge_request.id, 'reopened', @u_disabled.id) + expect(Notify).to receive(:merge_request_status_email).with(user_id, merge_request.id, 'reopened', @u_disabled.id) end def should_not_email(user_id) - Notify.should_not_receive(:merge_request_status_email).with(user_id, merge_request.id, 'reopened', @u_disabled.id) + expect(Notify).not_to receive(:merge_request_status_email).with(user_id, merge_request.id, 'reopened', @u_disabled.id) end end end @@ -378,11 +417,11 @@ describe NotificationService do end def should_email(user_id) - Notify.should_receive(:project_was_moved_email).with(project.id, user_id) + expect(Notify).to receive(:project_was_moved_email).with(project.id, user_id) end def should_not_email(user_id) - Notify.should_not_receive(:project_was_moved_email).with(project.id, user_id) + expect(Notify).not_to receive(:project_was_moved_email).with(project.id, user_id) end end end @@ -390,8 +429,9 @@ describe NotificationService do def build_team(project) @u_watcher = create(:user, notification_level: Notification::N_WATCH) @u_participating = create(:user, notification_level: Notification::N_PARTICIPATING) + @u_participant_mentioned = create(:user, username: 'participant', notification_level: Notification::N_PARTICIPATING) @u_disabled = create(:user, notification_level: Notification::N_DISABLED) - @u_mentioned = create(:user, username: 'mention', notification_level: Notification::N_PARTICIPATING) + @u_mentioned = create(:user, username: 'mention', notification_level: Notification::N_MENTION) @u_committer = create(:user, username: 'committer') @u_not_mentioned = create(:user, username: 'regular', notification_level: Notification::N_PARTICIPATING) @@ -401,4 +441,15 @@ describe NotificationService do project.team << [@u_mentioned, :master] project.team << [@u_committer, :master] end + + def add_users_with_subscription(project, issuable) + @subscriber = create :user + @unsubscriber = create :user + + project.team << [@subscriber, :master] + project.team << [@unsubscriber, :master] + + issuable.subscriptions.create(user: @subscriber, subscribed: true) + issuable.subscriptions.create(user: @unsubscriber, subscribed: false) + end end diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb index 9c97dad2ff..337dae592d 100644 --- a/spec/services/projects/create_service_spec.rb +++ b/spec/services/projects/create_service_spec.rb @@ -16,9 +16,9 @@ describe Projects::CreateService do @project = create_project(@user, @opts) end - it { @project.should be_valid } - it { @project.owner.should == @user } - it { @project.namespace.should == @user.namespace } + it { expect(@project).to be_valid } + it { expect(@project.owner).to eq(@user) } + it { expect(@project.namespace).to eq(@user.namespace) } end context 'group namespace' do @@ -30,9 +30,9 @@ describe Projects::CreateService do @project = create_project(@user, @opts) end - it { @project.should be_valid } - it { @project.owner.should == @group } - it { @project.namespace.should == @group } + it { expect(@project).to be_valid } + it { expect(@project.owner).to eq(@group) } + it { expect(@project.namespace).to eq(@group) } end context 'wiki_enabled creates repository directory' do @@ -42,7 +42,7 @@ describe Projects::CreateService do @path = ProjectWiki.new(@project, @user).send(:path_to_repo) end - it { File.exists?(@path).should be_true } + it { expect(File.exists?(@path)).to be_truthy } end context 'wiki_enabled false does not create wiki repository directory' do @@ -52,7 +52,34 @@ describe Projects::CreateService do @path = ProjectWiki.new(@project, @user).send(:path_to_repo) end - it { File.exists?(@path).should be_false } + it { expect(File.exists?(@path)).to be_falsey } + end + end + + context 'restricted visibility level' do + before do + allow_any_instance_of(ApplicationSetting).to( + receive(:restricted_visibility_levels).and_return([20]) + ) + + @opts.merge!( + visibility_level: Gitlab::VisibilityLevel.options['Public'] + ) + end + + it 'should not allow a restricted visibility level for non-admins' do + project = create_project(@user, @opts) + expect(project).to respond_to(:errors) + expect(project.errors.messages).to have_key(:visibility_level) + expect(project.errors.messages[:visibility_level].first).to( + match('restricted by your GitLab administrator') + ) + end + + it 'should allow a restricted visibility level for admins' do + project = create_project(@admin, @opts) + expect(project.errors.any?).to be(false) + expect(project.saved?).to be(true) end end end diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb new file mode 100644 index 0000000000..c9025bdf13 --- /dev/null +++ b/spec/services/projects/fork_service_spec.rb @@ -0,0 +1,108 @@ +require 'spec_helper' + +describe Projects::ForkService do + describe :fork_by_user do + before do + @from_namespace = create(:namespace) + @from_user = create(:user, namespace: @from_namespace ) + @from_project = create(:project, creator_id: @from_user.id, + namespace: @from_namespace, star_count: 107, + description: 'wow such project') + @to_namespace = create(:namespace) + @to_user = create(:user, namespace: @to_namespace) + end + + context 'fork project' do + describe "successfully creates project in the user namespace" do + let(:to_project) { fork_project(@from_project, @to_user) } + + it { expect(to_project.owner).to eq(@to_user) } + it { expect(to_project.namespace).to eq(@to_user.namespace) } + it { expect(to_project.star_count).to be_zero } + it { expect(to_project.description).to eq(@from_project.description) } + end + end + + context 'fork project failure' do + it "fails due to transaction failure" do + @to_project = fork_project(@from_project, @to_user, false) + expect(@to_project.errors).not_to be_empty + expect(@to_project.errors[:base]).to include("Fork transaction failed.") + end + end + + context 'project already exists' do + it "should fail due to validation, not transaction failure" do + @existing_project = create(:project, creator_id: @to_user.id, name: @from_project.name, namespace: @to_namespace) + @to_project = fork_project(@from_project, @to_user) + expect(@existing_project.persisted?).to be_truthy + expect(@to_project.errors[:base]).to include("Invalid fork destination") + expect(@to_project.errors[:base]).not_to include("Fork transaction failed.") + end + end + + context 'GitLab CI is enabled' do + it "calls fork registrator for CI" do + @from_project.build_missing_services + @from_project.gitlab_ci_service.update_attributes(active: true) + + expect(ForkRegistrationWorker).to receive(:perform_async) + + fork_project(@from_project, @to_user) + end + end + end + + describe :fork_to_namespace do + before do + @group_owner = create(:user) + @developer = create(:user) + @project = create(:project, creator_id: @group_owner.id, + star_count: 777, + description: 'Wow, such a cool project!') + @group = create(:group) + @group.add_user(@group_owner, GroupMember::OWNER) + @group.add_user(@developer, GroupMember::DEVELOPER) + @opts = { namespace: @group } + end + + context 'fork project for group' do + it 'group owner successfully forks project into the group' do + to_project = fork_project(@project, @group_owner, true, @opts) + expect(to_project.owner).to eq(@group) + expect(to_project.namespace).to eq(@group) + expect(to_project.name).to eq(@project.name) + expect(to_project.path).to eq(@project.path) + expect(to_project.description).to eq(@project.description) + expect(to_project.star_count).to be_zero + end + end + + context 'fork project for group when user not owner' do + it 'group developer should fail to fork project into the group' do + to_project = fork_project(@project, @developer, true, @opts) + expect(to_project.errors[:namespace]).to eq(['insufficient access rights']) + end + end + + context 'project already exists in group' do + it 'should fail due to validation, not transaction failure' do + existing_project = create(:project, name: @project.name, + namespace: @group) + to_project = fork_project(@project, @group_owner, true, @opts) + expect(existing_project.persisted?).to be_truthy + expect(to_project.errors[:base]).to eq(['Invalid fork destination']) + expect(to_project.errors[:name]).to eq(['has already been taken']) + expect(to_project.errors[:path]).to eq(['has already been taken']) + end + end + end + + def fork_project(from_project, user, fork_success = true, params = {}) + context = Projects::ForkService.new(from_project, user, params) + shell = double('gitlab_shell') + shell.stub(fork_repository: fork_success) + context.stub(gitlab_shell: shell) + context.execute + end +end diff --git a/spec/services/projects/image_service_spec.rb b/spec/services/projects/image_service_spec.rb deleted file mode 100644 index 23c4e227ae..0000000000 --- a/spec/services/projects/image_service_spec.rb +++ /dev/null @@ -1,62 +0,0 @@ -require 'spec_helper' - -describe Projects::ImageService do - describe 'Image service' do - before do - @user = create :user - @project = create :project, creator_id: @user.id, namespace: @user.namespace - end - - context 'for valid gif file' do - before do - gif = fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') - @link_to_image = upload_image(@project.repository, { 'markdown_img' => gif }, "http://test.example/") - end - - it { expect(@link_to_image).to have_key("alt") } - it { expect(@link_to_image).to have_key("url") } - it { expect(@link_to_image).to have_value("banana_sample") } - it { expect(@link_to_image["url"]).to match("http://test.example/uploads/#{@project.path_with_namespace}") } - it { expect(@link_to_image["url"]).to match("banana_sample.gif") } - end - - context 'for valid png file' do - before do - png = fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', 'image/png') - @link_to_image = upload_image(@project.repository, { 'markdown_img' => png }, "http://test.example/") - end - - it { expect(@link_to_image).to have_key("alt") } - it { expect(@link_to_image).to have_key("url") } - it { expect(@link_to_image).to have_value("dk") } - it { expect(@link_to_image["url"]).to match("http://test.example/uploads/#{@project.path_with_namespace}") } - it { expect(@link_to_image["url"]).to match("dk.png") } - end - - context 'for valid jpg file' do - before do - jpg = fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') - @link_to_image = upload_image(@project.repository, { 'markdown_img' => jpg }, "http://test.example/") - end - - it { expect(@link_to_image).to have_key("alt") } - it { expect(@link_to_image).to have_key("url") } - it { expect(@link_to_image).to have_value("rails_sample") } - it { expect(@link_to_image["url"]).to match("http://test.example/uploads/#{@project.path_with_namespace}") } - it { expect(@link_to_image["url"]).to match("rails_sample.jpg") } - end - - context 'for txt file' do - before do - txt = fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') - @link_to_image = upload_image(@project.repository, { 'markdown_img' => txt }, "http://test.example/") - end - - it { expect(@link_to_image).to be_nil } - end - end - - def upload_image(repository, params, root_url) - Projects::ImageService.new(repository, params, root_url).execute - end -end diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb index 2508dfc456..5650626fb1 100644 --- a/spec/services/projects/transfer_service_spec.rb +++ b/spec/services/projects/transfer_service_spec.rb @@ -3,41 +3,39 @@ require 'spec_helper' describe Projects::TransferService do let(:user) { create(:user) } let(:group) { create(:group) } - let(:group2) { create(:group) } let(:project) { create(:project, namespace: user.namespace) } context 'namespace -> namespace' do before do group.add_owner(user) - @service = Projects::TransferService.new(project, user, namespace_id: group.id) - @service.gitlab_shell.stub(mv_repository: true) - @result = @service.execute + @result = transfer_project(project, user, new_namespace_id: group.id) end - it { @result.should be_true } - it { project.namespace.should == group } + it { expect(@result).to be_truthy } + it { expect(project.namespace).to eq(group) } end context 'namespace -> no namespace' do before do - group.add_owner(user) - @service = Projects::TransferService.new(project, user, namespace_id: nil) - @service.gitlab_shell.stub(mv_repository: true) - @result = @service.execute + @result = transfer_project(project, user, new_namespace_id: nil) end - it { @result.should be_false } - it { project.namespace.should == user.namespace } + it { expect(@result).not_to be_nil } # { result.should be_false } passes on nil + it { expect(@result).to be_falsey } + it { expect(project.namespace).to eq(user.namespace) } end context 'namespace -> not allowed namespace' do before do - @service = Projects::TransferService.new(project, user, namespace_id: group2.id) - @service.gitlab_shell.stub(mv_repository: true) - @result = @service.execute + @result = transfer_project(project, user, new_namespace_id: group.id) end - it { @result.should be_false } - it { project.namespace.should == user.namespace } + it { expect(@result).not_to be_nil } # { result.should be_false } passes on nil + it { expect(@result).to be_falsey } + it { expect(project.namespace).to eq(user.namespace) } + end + + def transfer_project(project, user, params) + Projects::TransferService.new(project, user, params).execute end end diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index 6235778752..ea5b881310 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -17,8 +17,8 @@ describe Projects::UpdateService do update_project(@project, @user, @opts) end - it { @created_private.should be_true } - it { @project.private?.should be_true } + it { expect(@created_private).to be_truthy } + it { expect(@project.private?).to be_truthy } end context 'should be internal when updated to internal' do @@ -29,8 +29,8 @@ describe Projects::UpdateService do update_project(@project, @user, @opts) end - it { @created_private.should be_true } - it { @project.internal?.should be_true } + it { expect(@created_private).to be_truthy } + it { expect(@project.internal?).to be_truthy } end context 'should be public when updated to public' do @@ -41,15 +41,15 @@ describe Projects::UpdateService do update_project(@project, @user, @opts) end - it { @created_private.should be_true } - it { @project.public?.should be_true } + it { expect(@created_private).to be_truthy } + it { expect(@project.public?).to be_truthy } end context 'respect configured visibility restrictions setting' do before(:each) do - @restrictions = double("restrictions") - @restrictions.stub(:restricted_visibility_levels) { [ Gitlab::VisibilityLevel::PUBLIC ] } - Settings.stub_chain(:gitlab).and_return(@restrictions) + allow_any_instance_of(ApplicationSetting).to( + receive(:restricted_visibility_levels).and_return([20]) + ) end context 'should be private when updated to private' do @@ -60,8 +60,8 @@ describe Projects::UpdateService do update_project(@project, @user, @opts) end - it { @created_private.should be_true } - it { @project.private?.should be_true } + it { expect(@created_private).to be_truthy } + it { expect(@project.private?).to be_truthy } end context 'should be internal when updated to internal' do @@ -72,8 +72,8 @@ describe Projects::UpdateService do update_project(@project, @user, @opts) end - it { @created_private.should be_true } - it { @project.internal?.should be_true } + it { expect(@created_private).to be_truthy } + it { expect(@project.internal?).to be_truthy } end context 'should be private when updated to public' do @@ -84,8 +84,8 @@ describe Projects::UpdateService do update_project(@project, @user, @opts) end - it { @created_private.should be_true } - it { @project.private?.should be_true } + it { expect(@created_private).to be_truthy } + it { expect(@project.private?).to be_truthy } end context 'should be public when updated to public by admin' do @@ -96,8 +96,8 @@ describe Projects::UpdateService do update_project(@project, @admin, @opts) end - it { @created_private.should be_true } - it { @project.public?.should be_true } + it { expect(@created_private).to be_truthy } + it { expect(@project.public?).to be_truthy } end end end diff --git a/spec/services/projects/upload_service_spec.rb b/spec/services/projects/upload_service_spec.rb new file mode 100644 index 0000000000..e5c47015a0 --- /dev/null +++ b/spec/services/projects/upload_service_spec.rb @@ -0,0 +1,85 @@ +require 'spec_helper' + +describe Projects::UploadService do + describe 'File service' do + before do + @user = create :user + @project = create :project, creator_id: @user.id, namespace: @user.namespace + end + + context 'for valid gif file' do + before do + gif = fixture_file_upload(Rails.root + 'spec/fixtures/banana_sample.gif', 'image/gif') + @link_to_file = upload_file(@project.repository, gif) + end + + it { expect(@link_to_file).to have_key('alt') } + it { expect(@link_to_file).to have_key('url') } + it { expect(@link_to_file).to have_key('is_image') } + it { expect(@link_to_file).to have_value('banana_sample') } + it { expect(@link_to_file['is_image']).to equal(true) } + it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } + it { expect(@link_to_file['url']).to match('banana_sample.gif') } + end + + context 'for valid png file' do + before do + png = fixture_file_upload(Rails.root + 'spec/fixtures/dk.png', + 'image/png') + @link_to_file = upload_file(@project.repository, png) + end + + it { expect(@link_to_file).to have_key('alt') } + it { expect(@link_to_file).to have_key('url') } + it { expect(@link_to_file).to have_value('dk') } + it { expect(@link_to_file).to have_key('is_image') } + it { expect(@link_to_file['is_image']).to equal(true) } + it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } + it { expect(@link_to_file['url']).to match('dk.png') } + end + + context 'for valid jpg file' do + before do + jpg = fixture_file_upload(Rails.root + 'spec/fixtures/rails_sample.jpg', 'image/jpg') + @link_to_file = upload_file(@project.repository, jpg) + end + + it { expect(@link_to_file).to have_key('alt') } + it { expect(@link_to_file).to have_key('url') } + it { expect(@link_to_file).to have_key('is_image') } + it { expect(@link_to_file).to have_value('rails_sample') } + it { expect(@link_to_file['is_image']).to equal(true) } + it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } + it { expect(@link_to_file['url']).to match('rails_sample.jpg') } + end + + context 'for txt file' do + before do + txt = fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') + @link_to_file = upload_file(@project.repository, txt) + end + + it { expect(@link_to_file).to have_key('alt') } + it { expect(@link_to_file).to have_key('url') } + it { expect(@link_to_file).to have_key('is_image') } + it { expect(@link_to_file).to have_value('doc_sample.txt') } + it { expect(@link_to_file['is_image']).to equal(false) } + it { expect(@link_to_file['url']).to match("/#{@project.path_with_namespace}") } + it { expect(@link_to_file['url']).to match('doc_sample.txt') } + end + + context 'for too large a file' do + before do + txt = fixture_file_upload(Rails.root + 'spec/fixtures/doc_sample.txt', 'text/plain') + allow(txt).to receive(:size) { 1000.megabytes.to_i } + @link_to_file = upload_file(@project.repository, txt) + end + + it { expect(@link_to_file).to eq(nil) } + end + end + + def upload_file(repository, file) + Projects::UploadService.new(repository, file).execute + end +end diff --git a/spec/services/search_service_spec.rb b/spec/services/search_service_spec.rb index daffe98a8e..f57bfaea87 100644 --- a/spec/services/search_service_spec.rb +++ b/spec/services/search_service_spec.rb @@ -19,7 +19,7 @@ describe 'Search::GlobalService' do it 'should return public projects only' do context = Search::GlobalService.new(nil, search: "searchable") results = context.execute - results[:projects].should match_array [public_project] + expect(results.objects('projects')).to match_array [public_project] end end @@ -27,19 +27,19 @@ describe 'Search::GlobalService' do it 'should return public, internal and private projects' do context = Search::GlobalService.new(user, search: "searchable") results = context.execute - results[:projects].should match_array [public_project, found_project, internal_project] + expect(results.objects('projects')).to match_array [public_project, found_project, internal_project] end it 'should return only public & internal projects' do context = Search::GlobalService.new(internal_user, search: "searchable") results = context.execute - results[:projects].should match_array [internal_project, public_project] + expect(results.objects('projects')).to match_array [internal_project, public_project] end it 'namespace name should be searchable' do context = Search::GlobalService.new(user, search: found_project.namespace.path) results = context.execute - results[:projects].should match_array [found_project] + expect(results.objects('projects')).to match_array [found_project] end end end diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb index 3c2eec6cfd..199ac99660 100644 --- a/spec/services/system_hooks_service_spec.rb +++ b/spec/services/system_hooks_service_spec.rb @@ -3,24 +3,60 @@ require 'spec_helper' describe SystemHooksService do let (:user) { create :user } let (:project) { create :project } - let (:users_project) { create :users_project } + let (:project_member) { create :project_member } + let (:key) { create(:key, user: user) } + let (:group) { create(:group) } + let (:group_member) { create(:group_member) } context 'event data' do - it { event_data(user, :create).should include(:event_name, :name, :created_at, :email, :user_id) } - it { event_data(user, :destroy).should include(:event_name, :name, :created_at, :email, :user_id) } - it { event_data(project, :create).should include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) } - it { event_data(project, :destroy).should include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) } - it { event_data(users_project, :create).should include(:event_name, :created_at, :project_name, :project_path, :project_id, :user_name, :user_email, :project_access, :project_visibility) } - it { event_data(users_project, :destroy).should include(:event_name, :created_at, :project_name, :project_path, :project_id, :user_name, :user_email, :project_access, :project_visibility) } + it { expect(event_data(user, :create)).to include(:event_name, :name, :created_at, :email, :user_id) } + it { expect(event_data(user, :destroy)).to include(:event_name, :name, :created_at, :email, :user_id) } + it { expect(event_data(project, :create)).to include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) } + it { expect(event_data(project, :destroy)).to include(:event_name, :name, :created_at, :path, :project_id, :owner_name, :owner_email, :project_visibility) } + it { expect(event_data(project_member, :create)).to include(:event_name, :created_at, :project_name, :project_path, :project_id, :user_name, :user_email, :access_level, :project_visibility) } + it { expect(event_data(project_member, :destroy)).to include(:event_name, :created_at, :project_name, :project_path, :project_id, :user_name, :user_email, :access_level, :project_visibility) } + it { expect(event_data(key, :create)).to include(:username, :key, :id) } + it { expect(event_data(key, :destroy)).to include(:username, :key, :id) } + + it do + expect(event_data(group, :create)).to include( + :event_name, :name, :created_at, :path, :group_id, :owner_name, + :owner_email + ) + end + it do + expect(event_data(group, :destroy)).to include( + :event_name, :name, :created_at, :path, :group_id, :owner_name, + :owner_email + ) + end + it do + expect(event_data(group_member, :create)).to include( + :event_name, :created_at, :group_name, :group_path, :group_id, :user_id, + :user_name, :user_email, :group_access + ) + end + it do + expect(event_data(group_member, :destroy)).to include( + :event_name, :created_at, :group_name, :group_path, :group_id, :user_id, + :user_name, :user_email, :group_access + ) + end end context 'event names' do - it { event_name(user, :create).should eq "user_create" } - it { event_name(user, :destroy).should eq "user_destroy" } - it { event_name(project, :create).should eq "project_create" } - it { event_name(project, :destroy).should eq "project_destroy" } - it { event_name(users_project, :create).should eq "user_add_to_team" } - it { event_name(users_project, :destroy).should eq "user_remove_from_team" } + it { expect(event_name(user, :create)).to eq "user_create" } + it { expect(event_name(user, :destroy)).to eq "user_destroy" } + it { expect(event_name(project, :create)).to eq "project_create" } + it { expect(event_name(project, :destroy)).to eq "project_destroy" } + it { expect(event_name(project_member, :create)).to eq "user_add_to_team" } + it { expect(event_name(project_member, :destroy)).to eq "user_remove_from_team" } + it { expect(event_name(key, :create)).to eq 'key_create' } + it { expect(event_name(key, :destroy)).to eq 'key_destroy' } + it { expect(event_name(group, :create)).to eq 'group_create' } + it { expect(event_name(group, :destroy)).to eq 'group_destroy' } + it { expect(event_name(group_member, :create)).to eq 'user_add_to_group' } + it { expect(event_name(group_member, :destroy)).to eq 'user_remove_from_group' } end def event_data(*args) diff --git a/spec/services/test_hook_service_spec.rb b/spec/services/test_hook_service_spec.rb index 76af5bf7b8..d2b505f55a 100644 --- a/spec/services/test_hook_service_spec.rb +++ b/spec/services/test_hook_service_spec.rb @@ -8,7 +8,7 @@ describe TestHookService do describe :execute do it "should execute successfully" do stub_request(:post, hook.url).to_return(status: 200) - TestHookService.new.execute(hook, user).should be_true + expect(TestHookService.new.execute(hook, user)).to be_truthy end end end diff --git a/spec/services/update_snippet_service_spec.rb b/spec/services/update_snippet_service_spec.rb new file mode 100644 index 0000000000..841ef9bfed --- /dev/null +++ b/spec/services/update_snippet_service_spec.rb @@ -0,0 +1,52 @@ +require 'spec_helper' + +describe UpdateSnippetService do + before do + @user = create :user + @admin = create :user, admin: true + @opts = { + title: 'Test snippet', + file_name: 'snippet.rb', + content: 'puts "hello world"', + visibility_level: Gitlab::VisibilityLevel::PRIVATE + } + end + + context 'When public visibility is restricted' do + before do + allow_any_instance_of(ApplicationSetting).to( + receive(:restricted_visibility_levels).and_return( + [Gitlab::VisibilityLevel::PUBLIC] + ) + ) + + @snippet = create_snippet(@project, @user, @opts) + @opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + end + + it 'non-admins should not be able to update to public visibility' do + old_visibility = @snippet.visibility_level + update_snippet(@project, @user, @snippet, @opts) + expect(@snippet.errors.messages).to have_key(:visibility_level) + expect(@snippet.errors.messages[:visibility_level].first).to( + match('Public visibility has been restricted') + ) + expect(@snippet.visibility_level).to eq(old_visibility) + end + + it 'admins should be able to update to pubic visibility' do + old_visibility = @snippet.visibility_level + update_snippet(@project, @admin, @snippet, @opts) + expect(@snippet.visibility_level).not_to eq(old_visibility) + expect(@snippet.visibility_level).to eq(Gitlab::VisibilityLevel::PUBLIC) + end + end + + def create_snippet(project, user, opts) + CreateSnippetService.new(project, user, opts).execute + end + + def update_snippet(project = nil, user, snippet, opts) + UpdateSnippetService.new(project, user, snippet, opts).execute + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 6934cabadf..53ccaa4fd6 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,14 +1,14 @@ -# This file is copied to spec/ when you run 'rails generate rspec:install' -ENV["RAILS_ENV"] ||= 'test' -require File.expand_path("../../config/environment", __FILE__) - -require 'simplecov' unless ENV['CI'] - -if ENV['TRAVIS'] - require 'coveralls' - Coveralls.wear! +if ENV['SIMPLECOV'] + require 'simplecov' end +if ENV['COVERALLS'] + require 'coveralls' + Coveralls.wear_merged! +end + +ENV["RAILS_ENV"] ||= 'test' +require File.expand_path("../../config/environment", __FILE__) require 'rspec/rails' require 'capybara/rails' require 'capybara/rspec' @@ -37,8 +37,12 @@ RSpec.configure do |config| config.include Devise::TestHelpers, type: :controller config.include TestEnv + config.infer_spec_type_from_file_location! + config.raise_errors_for_deprecations! config.before(:suite) do TestEnv.init end end + +ActiveRecord::Migration.maintain_test_schema! diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb index d2d532d973..cca7652093 100644 --- a/spec/support/db_cleaner.rb +++ b/spec/support/db_cleaner.rb @@ -36,4 +36,15 @@ RSpec.configure do |config| config.after(:each) do DatabaseCleaner.clean end + + # rspec-rails 3 will no longer automatically infer an example group's spec type + # from the file location. You can explicitly opt-in to the feature using this + # config option. + # To explicitly tag specs without using automatic inference, set the `:type` + # metadata manually: + # + # describe ThingsController, :type => :controller do + # # Equivalent to being in spec/controllers + # end + config.infer_spec_type_from_file_location! end diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb index 238ac7c661..791d2a1fd6 100644 --- a/spec/support/login_helpers.rb +++ b/spec/support/login_helpers.rb @@ -21,6 +21,6 @@ module LoginHelpers # Requires Javascript driver. def logout - page.find(:css, ".icon-signout").click + find(:css, ".fa.fa-sign-out").click end end diff --git a/spec/support/matchers.rb b/spec/support/matchers.rb index 15fb47004e..52b11bd632 100644 --- a/spec/support/matchers.rb +++ b/spec/support/matchers.rb @@ -42,19 +42,19 @@ module UrlAccess def url_allowed?(user, url) emulate_user(user) visit url - (page.status_code != 404 && current_path != new_user_session_path) + (status_code != 404 && current_path != new_user_session_path) end def url_denied?(user, url) emulate_user(user) visit url - (page.status_code == 404 || current_path == new_user_session_path) + (status_code == 404 || current_path == new_user_session_path) end def url_404?(user, url) emulate_user(user) visit url - page.status_code == 404 + status_code == 404 end def emulate_user(user) diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb index 0d67e7ee4e..305592fa5a 100644 --- a/spec/support/mentionable_shared_examples.rb +++ b/spec/support/mentionable_shared_examples.rb @@ -14,22 +14,32 @@ def common_mentionable_setup let(:mentioned_mr) { create :merge_request, :simple, source_project: mproject } let(:mentioned_commit) { double('commit', sha: '1234567890abcdef').as_null_object } + let(:ext_proj) { create :project, :public } + let(:ext_issue) { create :issue, project: ext_proj } + let(:other_ext_issue) { create :issue, project: ext_proj } + let(:ext_mr) { create :merge_request, :simple, source_project: ext_proj } + let(:ext_commit) { ext_proj.repository.commit } + # Override to add known commits to the repository stub. let(:extra_commits) { [] } # A string that mentions each of the +mentioned_.*+ objects above. Mentionables should add a self-reference # to this string and place it in their +mentionable_text+. let(:ref_string) do - "mentions ##{mentioned_issue.iid} twice ##{mentioned_issue.iid}, !#{mentioned_mr.iid}, " + - "#{mentioned_commit.sha[0..5]} and itself as #{backref_text}" + "mentions ##{mentioned_issue.iid} twice ##{mentioned_issue.iid}, " + + "!#{mentioned_mr.iid}, " + + "#{ext_proj.path_with_namespace}##{ext_issue.iid}, " + + "#{ext_proj.path_with_namespace}!#{ext_mr.iid}, " + + "#{ext_proj.path_with_namespace}@#{ext_commit.short_id}, " + + "#{mentioned_commit.sha[0..10]} and itself as #{backref_text}" end before do # Wire the project's repository to return the mentioned commit, and +nil+ for any # unrecognized commits. - commitmap = { '123456' => mentioned_commit } - extra_commits.each { |c| commitmap[c.sha[0..5]] = c } - mproject.repository.stub(:commit) { |sha| commitmap[sha] } + commitmap = { '1234567890a' => mentioned_commit } + extra_commits.each { |c| commitmap[c.short_id] = c } + allow(mproject.repository).to receive(:commit) { |sha| commitmap[sha] } set_mentionable_text.call(ref_string) end end @@ -38,22 +48,27 @@ shared_examples 'a mentionable' do common_mentionable_setup it 'generates a descriptive back-reference' do - subject.gfm_reference.should == backref_text + expect(subject.gfm_reference).to eq(backref_text) end it "extracts references from its reference property" do # De-duplicate and omit itself refs = subject.references(mproject) - - refs.should have(3).items - refs.should include(mentioned_issue) - refs.should include(mentioned_mr) - refs.should include(mentioned_commit) + expect(refs.size).to eq(6) + expect(refs).to include(mentioned_issue) + expect(refs).to include(mentioned_mr) + expect(refs).to include(mentioned_commit) + expect(refs).to include(ext_issue) + expect(refs).to include(ext_mr) + expect(refs).to include(ext_commit) end it 'creates cross-reference notes' do - [mentioned_issue, mentioned_mr, mentioned_commit].each do |referenced| - Note.should_receive(:create_cross_reference_note).with(referenced, subject.local_reference, mauthor, mproject) + mentioned_objects = [mentioned_issue, mentioned_mr, mentioned_commit, + ext_issue, ext_mr, ext_commit] + + mentioned_objects.each do |referenced| + expect(Note).to receive(:create_cross_reference_note).with(referenced, subject.local_reference, mauthor, mproject) end subject.create_cross_references!(mproject, mauthor) @@ -62,8 +77,8 @@ shared_examples 'a mentionable' do it 'detects existing cross-references' do Note.create_cross_reference_note(mentioned_issue, subject.local_reference, mauthor, mproject) - subject.has_mentioned?(mentioned_issue).should be_true - subject.has_mentioned?(mentioned_mr).should be_false + expect(subject.has_mentioned?(mentioned_issue)).to be_truthy + expect(subject.has_mentioned?(mentioned_mr)).to be_falsey end end @@ -73,15 +88,25 @@ shared_examples 'an editable mentionable' do it_behaves_like 'a mentionable' it 'creates new cross-reference notes when the mentionable text is edited' do - new_text = "this text still mentions ##{mentioned_issue.iid} and #{mentioned_commit.sha[0..5]}, " + - "but now it mentions ##{other_issue.iid}, too." + new_text = "still mentions ##{mentioned_issue.iid}, " + + "#{mentioned_commit.sha[0..10]}, " + + "#{ext_issue.iid}, " + + "new refs: ##{other_issue.iid}, " + + "#{ext_proj.path_with_namespace}##{other_ext_issue.iid}" - [mentioned_issue, mentioned_commit].each do |oldref| - Note.should_not_receive(:create_cross_reference_note).with(oldref, subject.local_reference, + [mentioned_issue, mentioned_commit, ext_issue].each do |oldref| + expect(Note).not_to receive(:create_cross_reference_note).with(oldref, subject.local_reference, mauthor, mproject) end - Note.should_receive(:create_cross_reference_note).with(other_issue, subject.local_reference, mauthor, mproject) + [other_issue, other_ext_issue].each do |newref| + expect(Note).to receive(:create_cross_reference_note).with( + newref, + subject.local_reference, + mauthor, + mproject + ) + end subject.save set_mentionable_text.call(new_text) diff --git a/spec/support/repo_helpers.rb b/spec/support/repo_helpers.rb index 4c4775da69..aadf791bf3 100644 --- a/spec/support/repo_helpers.rb +++ b/spec/support/repo_helpers.rb @@ -43,6 +43,25 @@ eos ) end + def another_sample_commit + OpenStruct.new( + id: "e56497bb5f03a90a51293fc6d516788730953899", + parent_id: '4cd80ccab63c82b4bad16faa5193fbd2aa06df40', + author_full_name: "Sytse Sijbrandij", + author_email: "sytse@gitlab.com", + files_changed_count: 1, + message: < 'e56497b', + 'feature' => '0b4bc9a', + 'feature_conflict' => 'bb5206f', + 'fix' => '12d65c8', + 'improve/awesome' => '5937ac0', + 'markdown' => '0ed8c6c', + 'master' => '5937ac0' + } + # Test environment # # See gitlab.yml.example test section for paths # def init(opts = {}) - RSpec::Mocks::setup(self) - # Disable mailer for spinach tests disable_mailer if opts[:mailer] == false - # Clean /tmp/tests - tmp_test_path = Rails.root.join('tmp', 'tests') + clean_test_path - if File.directory?(tmp_test_path) - FileUtils.rm_r(tmp_test_path) - end - - FileUtils.mkdir_p(tmp_test_path) + FileUtils.mkdir_p(repos_path) # Setup GitLab shell for test instance setup_gitlab_shell @@ -34,27 +38,58 @@ module TestEnv end def enable_mailer - NotificationService.any_instance.unstub(:mailer) + allow_any_instance_of(NotificationService).to receive(:mailer).and_call_original + end + + # Clean /tmp/tests + # + # Keeps gitlab-shell and gitlab-test + def clean_test_path + tmp_test_path = Rails.root.join('tmp', 'tests', '**') + + Dir[tmp_test_path].each do |entry| + unless File.basename(entry) =~ /\Agitlab-(shell|test)\z/ + FileUtils.rm_rf(entry) + end + end end def setup_gitlab_shell - unless File.directory?(Gitlab.config.gitlab_shell.path) - %x[rake gitlab:shell:install] + unless File.directory?(Rails.root.join(*%w(tmp tests gitlab-shell))) + `rake gitlab:shell:install` end end def setup_factory_repo - repo_path = repos_path + "/root/testme.git" - clone_url = 'https://gitlab.com/gitlab-org/gitlab-test.git' + clone_url = "https://gitlab.com/gitlab-org/#{factory_repo_name}.git" - unless File.directory?(repo_path) - git_cmd = %W(git clone --bare #{clone_url} #{repo_path}) - system(*git_cmd) + unless File.directory?(factory_repo_path) + system(*%W(git clone -q #{clone_url} #{factory_repo_path})) end + + Dir.chdir(factory_repo_path) do + BRANCH_SHA.each do |branch, sha| + # Try to reset without fetching to avoid using the network. + reset = %W(git update-ref refs/heads/#{branch} #{sha}) + unless system(*reset) + if system(*%w(git fetch origin)) + unless system(*reset) + raise 'The fetched test seed '\ + 'does not contain the required revision.' + end + else + raise 'Could not fetch test seed repository.' + end + end + end + end + + # We must copy bare repositories because we will push to them. + system(git_env, *%W(git clone -q --bare #{factory_repo_path} #{factory_repo_path_bare})) end def copy_repo(project) - base_repo_path = File.expand_path(repos_path + "/root/testme.git") + base_repo_path = File.expand_path(factory_repo_path_bare) target_repo_path = File.expand_path(repos_path + "/#{project.namespace.path}/#{project.path}.git") FileUtils.mkdir_p(target_repo_path) FileUtils.cp_r("#{base_repo_path}/.", target_repo_path) @@ -64,4 +99,24 @@ module TestEnv def repos_path Gitlab.config.gitlab_shell.repos_path end + + private + + def factory_repo_path + @factory_repo_path ||= Rails.root.join('tmp', 'tests', factory_repo_name) + end + + def factory_repo_path_bare + "#{factory_repo_path}_bare" + end + + def factory_repo_name + 'gitlab-test' + end + + # Prevent developer git configurations from being persisted to test + # repositories + def git_env + {'GIT_TEMPLATE_DIR' => ''} + end end diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb index 71a45eb2fa..a59f74c212 100644 --- a/spec/tasks/gitlab/backup_rake_spec.rb +++ b/spec/tasks/gitlab/backup_rake_spec.rb @@ -10,21 +10,21 @@ describe 'gitlab:app namespace rake task' do Rake::Task.define_task :environment end + def run_rake_task(task_name) + Rake::Task[task_name].reenable + Rake.application.invoke_task task_name + end + describe 'backup_restore' do before do # avoid writing task output to spec progress - $stdout.stub :write - end - - let :run_rake_task do - Rake::Task["gitlab:backup:restore"].reenable - Rake.application.invoke_task "gitlab:backup:restore" + allow($stdout).to receive :write end context 'gitlab version' do before do Dir.stub glob: [] - Dir.stub :chdir + allow(Dir).to receive :chdir File.stub exists?: true Kernel.stub system: true FileUtils.stub cp_r: true @@ -36,17 +36,117 @@ describe 'gitlab:app namespace rake task' do it 'should fail on mismatch' do YAML.stub load_file: {gitlab_version: "not #{gitlab_version}" } - expect { run_rake_task }.to raise_error SystemExit + expect { run_rake_task('gitlab:backup:restore') }.to( + raise_error SystemExit + ) end it 'should invoke restoration on mach' do YAML.stub load_file: {gitlab_version: gitlab_version} - Rake::Task["gitlab:backup:db:restore"].should_receive :invoke - Rake::Task["gitlab:backup:repo:restore"].should_receive :invoke - Rake::Task["gitlab:shell:setup"].should_receive :invoke - expect { run_rake_task }.to_not raise_error + expect(Rake::Task["gitlab:backup:db:restore"]).to receive :invoke + expect(Rake::Task["gitlab:backup:repo:restore"]).to receive :invoke + expect(Rake::Task["gitlab:shell:setup"]).to receive :invoke + expect { run_rake_task('gitlab:backup:restore') }.to_not raise_error end end end # backup_restore task + + describe 'backup_create' do + def tars_glob + Dir.glob(File.join(Gitlab.config.backup.path, '*_gitlab_backup.tar')) + end + + before :all do + # Record the existing backup tars so we don't touch them + existing_tars = tars_glob + + # Redirect STDOUT and run the rake task + orig_stdout = $stdout + $stdout = StringIO.new + run_rake_task('gitlab:backup:create') + $stdout = orig_stdout + + @backup_tar = (tars_glob - existing_tars).first + end + + after :all do + FileUtils.rm(@backup_tar) + end + + it 'should set correct permissions on the tar file' do + expect(File.exist?(@backup_tar)).to be_truthy + expect(File::Stat.new(@backup_tar).mode.to_s(8)).to eq('100600') + end + + it 'should set correct permissions on the tar contents' do + tar_contents, exit_status = Gitlab::Popen.popen( + %W{tar -tvf #{@backup_tar} db uploads repositories} + ) + expect(exit_status).to eq(0) + expect(tar_contents).to match('db/') + expect(tar_contents).to match('uploads/') + expect(tar_contents).to match('repositories/') + expect(tar_contents).not_to match(/^.{4,9}[rwx].* (db|uploads|repositories)\/$/) + end + + it 'should delete temp directories' do + temp_dirs = Dir.glob( + File.join(Gitlab.config.backup.path, '{db,repositories,uploads}') + ) + + expect(temp_dirs).to be_empty + end + end # backup_create task + + describe "Skipping items" do + def tars_glob + Dir.glob(File.join(Gitlab.config.backup.path, '*_gitlab_backup.tar')) + end + + before :all do + @origin_cd = Dir.pwd + + Rake::Task["gitlab:backup:db:create"].reenable + Rake::Task["gitlab:backup:repo:create"].reenable + Rake::Task["gitlab:backup:uploads:create"].reenable + + # Record the existing backup tars so we don't touch them + existing_tars = tars_glob + + # Redirect STDOUT and run the rake task + orig_stdout = $stdout + $stdout = StringIO.new + ENV["SKIP"] = "repositories" + run_rake_task('gitlab:backup:create') + $stdout = orig_stdout + + @backup_tar = (tars_glob - existing_tars).first + end + + after :all do + FileUtils.rm(@backup_tar) + Dir.chdir @origin_cd + end + + it "does not contain skipped item" do + tar_contents, exit_status = Gitlab::Popen.popen( + %W{tar -tvf #{@backup_tar} db uploads repositories} + ) + + expect(tar_contents).to match('db/') + expect(tar_contents).to match('uploads/') + expect(tar_contents).not_to match('repositories/') + end + + it 'does not invoke repositories restore' do + Rake::Task["gitlab:shell:setup"].stub invoke: true + allow($stdout).to receive :write + + expect(Rake::Task["gitlab:backup:db:restore"]).to receive :invoke + expect(Rake::Task["gitlab:backup:repo:restore"]).not_to receive :invoke + expect(Rake::Task["gitlab:shell:setup"]).to receive :invoke + expect { run_rake_task('gitlab:backup:restore') }.to_not raise_error + end + end end # gitlab:app namespace diff --git a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb new file mode 100644 index 0000000000..22e746870d --- /dev/null +++ b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb @@ -0,0 +1,27 @@ +require 'spec_helper' +require 'rake' + +describe 'gitlab:mail_google_schema_whitelisting rake task' do + before :all do + Rake.application.rake_require "tasks/gitlab/task_helpers" + Rake.application.rake_require "tasks/gitlab/mail_google_schema_whitelisting" + # empty task as env is already loaded + Rake::Task.define_task :environment + end + + describe 'call' do + before do + # avoid writing task output to spec progress + allow($stdout).to receive :write + end + + let :run_rake_task do + Rake::Task["gitlab:mail_google_schema_whitelisting"].reenable + Rake.application.invoke_task "gitlab:mail_google_schema_whitelisting" + end + + it 'should run the task without errors' do + expect { run_rake_task }.to_not raise_error + end + end +end diff --git a/spec/workers/fork_registration_worker_spec.rb b/spec/workers/fork_registration_worker_spec.rb new file mode 100644 index 0000000000..cc6f574b29 --- /dev/null +++ b/spec/workers/fork_registration_worker_spec.rb @@ -0,0 +1,10 @@ + +require 'spec_helper' + +describe ForkRegistrationWorker do + context "as a resque worker" do + it "reponds to #perform" do + expect(ForkRegistrationWorker.new).to respond_to(:perform) + end + end +end diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb index e6bf79b853..df1a2b84a5 100644 --- a/spec/workers/post_receive_spec.rb +++ b/spec/workers/post_receive_spec.rb @@ -1,10 +1,13 @@ require 'spec_helper' describe PostReceive do - + let(:changes) { "123456 789012 refs/heads/tést\n654321 210987 refs/tags/tag" } + let(:wrongly_encoded_changes) { changes.encode("ISO-8859-1").force_encoding("UTF-8") } + let(:base64_changes) { Base64.encode64(wrongly_encoded_changes) } + context "as a resque worker" do it "reponds to #perform" do - PostReceive.new.should respond_to(:perform) + expect(PostReceive.new).to respond_to(:perform) end end @@ -14,25 +17,25 @@ describe PostReceive do let(:key_id) { key.shell_id } it "fetches the correct project" do - Project.should_receive(:find_with_namespace).with(project.path_with_namespace).and_return(project) - PostReceive.new.perform(pwd(project), 'sha-old', 'sha-new', 'refs/heads/master', key_id) + expect(Project).to receive(:find_with_namespace).with(project.path_with_namespace).and_return(project) + PostReceive.new.perform(pwd(project), key_id, base64_changes) end it "does not run if the author is not in the project" do - Key.stub(:find_by).with(hash_including(id: anything())) { nil } + allow(Key).to receive(:find_by).with(hash_including(id: anything())) { nil } - project.should_not_receive(:execute_hooks) + expect(project).not_to receive(:execute_hooks) - PostReceive.new.perform(pwd(project), 'sha-old', 'sha-new', 'refs/heads/master', key_id).should be_false + expect(PostReceive.new.perform(pwd(project), key_id, base64_changes)).to be_falsey end it "asks the project to trigger all hooks" do Project.stub(find_with_namespace: project) - project.should_receive(:execute_hooks) - project.should_receive(:execute_services) - project.should_receive(:update_merge_requests) + expect(project).to receive(:execute_hooks).twice + expect(project).to receive(:execute_services).twice + expect(project).to receive(:update_merge_requests) - PostReceive.new.perform(pwd(project), 'sha-old', 'sha-new', 'refs/heads/master', key_id) + PostReceive.new.perform(pwd(project), key_id, base64_changes) end end diff --git a/spec/workers/repository_archive_worker_spec.rb b/spec/workers/repository_archive_worker_spec.rb new file mode 100644 index 0000000000..c2362058cf --- /dev/null +++ b/spec/workers/repository_archive_worker_spec.rb @@ -0,0 +1,80 @@ +require 'spec_helper' + +describe RepositoryArchiveWorker do + let(:project) { create(:project) } + subject { RepositoryArchiveWorker.new } + + before do + allow(Project).to receive(:find).and_return(project) + end + + describe "#perform" do + it "cleans old archives" do + expect(project.repository).to receive(:clean_old_archives) + + subject.perform(project.id, "master", "zip") + end + + context "when the repository doesn't have an archive file path" do + before do + allow(project.repository).to receive(:archive_file_path).and_return(nil) + end + + it "doesn't archive the repo" do + expect(project.repository).not_to receive(:archive_repo) + + subject.perform(project.id, "master", "zip") + end + end + + context "when the repository has an archive file path" do + let(:file_path) { "/archive.zip" } + let(:pid_file_path) { "/archive.zip.pid" } + + before do + allow(project.repository).to receive(:archive_file_path).and_return(file_path) + allow(project.repository).to receive(:archive_pid_file_path).and_return(pid_file_path) + end + + context "when the archive file already exists" do + before do + allow(File).to receive(:exist?).with(file_path).and_return(true) + end + + it "doesn't archive the repo" do + expect(project.repository).not_to receive(:archive_repo) + + subject.perform(project.id, "master", "zip") + end + end + + context "when the archive file doesn't exist yet" do + before do + allow(File).to receive(:exist?).with(file_path).and_return(false) + allow(File).to receive(:exist?).with(pid_file_path).and_return(true) + end + + context "when the archive pid file doesn't exist yet" do + before do + allow(File).to receive(:exist?).with(pid_file_path).and_return(false) + end + + it "archives the repo" do + expect(project.repository).to receive(:archive_repo) + + subject.perform(project.id, "master", "zip") + end + end + + context "when the archive pid file already exists" do + it "doesn't archive the repo" do + expect(project.repository).not_to receive(:archive_repo) + + subject.perform(project.id, "master", "zip") + end + end + end + end + end +end + diff --git a/vendor/assets/javascripts/chart-lib.min.js b/vendor/assets/javascripts/chart-lib.min.js new file mode 100644 index 0000000000..3a0a2c8734 --- /dev/null +++ b/vendor/assets/javascripts/chart-lib.min.js @@ -0,0 +1,11 @@ +/*! + * Chart.js + * http://chartjs.org/ + * Version: 1.0.2 + * + * Copyright 2015 Nick Downie + * Released under the MIT license + * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md + */ +(function(){"use strict";var t=this,i=t.Chart,e=function(t){this.canvas=t.canvas,this.ctx=t;var i=function(t,i){return t["offset"+i]?t["offset"+i]:document.defaultView.getComputedStyle(t).getPropertyValue(i)},e=this.width=i(t.canvas,"Width"),n=this.height=i(t.canvas,"Height");t.canvas.width=e,t.canvas.height=n;var e=this.width=t.canvas.width,n=this.height=t.canvas.height;return this.aspectRatio=this.width/this.height,s.retinaScale(this),this};e.defaults={global:{animation:!0,animationSteps:60,animationEasing:"easeOutQuart",showScale:!0,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleIntegersOnly:!0,scaleBeginAtZero:!1,scaleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",responsive:!1,maintainAspectRatio:!0,showTooltips:!0,customTooltips:!1,tooltipEvents:["mousemove","touchstart","touchmove","mouseout"],tooltipFillColor:"rgba(0,0,0,0.8)",tooltipFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",tooltipFontSize:14,tooltipFontStyle:"normal",tooltipFontColor:"#fff",tooltipTitleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",tooltipTitleFontSize:14,tooltipTitleFontStyle:"bold",tooltipTitleFontColor:"#fff",tooltipYPadding:6,tooltipXPadding:6,tooltipCaretSize:8,tooltipCornerRadius:6,tooltipXOffset:10,tooltipTemplate:"<%if (label){%><%=label%>: <%}%><%= value %>",multiTooltipTemplate:"<%= value %>",multiTooltipKeyBackground:"#fff",onAnimationProgress:function(){},onAnimationComplete:function(){}}},e.types={};var s=e.helpers={},n=s.each=function(t,i,e){var s=Array.prototype.slice.call(arguments,3);if(t)if(t.length===+t.length){var n;for(n=0;n=0;s--){var n=t[s];if(i(n))return n}},s.inherits=function(t){var i=this,e=t&&t.hasOwnProperty("constructor")?t.constructor:function(){return i.apply(this,arguments)},s=function(){this.constructor=e};return s.prototype=i.prototype,e.prototype=new s,e.extend=r,t&&a(e.prototype,t),e.__super__=i.prototype,e}),c=s.noop=function(){},u=s.uid=function(){var t=0;return function(){return"chart-"+t++}}(),d=s.warn=function(t){window.console&&"function"==typeof window.console.warn&&console.warn(t)},p=s.amd="function"==typeof define&&define.amd,f=s.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},g=s.max=function(t){return Math.max.apply(Math,t)},m=s.min=function(t){return Math.min.apply(Math,t)},v=(s.cap=function(t,i,e){if(f(i)){if(t>i)return i}else if(f(e)&&e>t)return e;return t},s.getDecimalPlaces=function(t){return t%1!==0&&f(t)?t.toString().split(".")[1].length:0}),S=s.radians=function(t){return t*(Math.PI/180)},x=(s.getAngleFromPoint=function(t,i){var e=i.x-t.x,s=i.y-t.y,n=Math.sqrt(e*e+s*s),o=2*Math.PI+Math.atan2(s,e);return 0>e&&0>s&&(o+=2*Math.PI),{angle:o,distance:n}},s.aliasPixel=function(t){return t%2===0?0:.5}),y=(s.splineCurve=function(t,i,e,s){var n=Math.sqrt(Math.pow(i.x-t.x,2)+Math.pow(i.y-t.y,2)),o=Math.sqrt(Math.pow(e.x-i.x,2)+Math.pow(e.y-i.y,2)),a=s*n/(n+o),h=s*o/(n+o);return{inner:{x:i.x-a*(e.x-t.x),y:i.y-a*(e.y-t.y)},outer:{x:i.x+h*(e.x-t.x),y:i.y+h*(e.y-t.y)}}},s.calculateOrderOfMagnitude=function(t){return Math.floor(Math.log(t)/Math.LN10)}),C=(s.calculateScaleRange=function(t,i,e,s,n){var o=2,a=Math.floor(i/(1.5*e)),h=o>=a,l=g(t),r=m(t);l===r&&(l+=.5,r>=.5&&!s?r-=.5:l+=.5);for(var c=Math.abs(l-r),u=y(c),d=Math.ceil(l/(1*Math.pow(10,u)))*Math.pow(10,u),p=s?0:Math.floor(r/(1*Math.pow(10,u)))*Math.pow(10,u),f=d-p,v=Math.pow(10,u),S=Math.round(f/v);(S>a||a>2*S)&&!h;)if(S>a)v*=2,S=Math.round(f/v),S%1!==0&&(h=!0);else if(n&&u>=0){if(v/2%1!==0)break;v/=2,S=Math.round(f/v)}else v/=2,S=Math.round(f/v);return h&&(S=o,v=f/S),{steps:S,stepValue:v,min:p,max:p+S*v}},s.template=function(t,i){function e(t,i){var e=/\W/.test(t)?new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+t.replace(/[\r\t\n]/g," ").split("<%").join(" ").replace(/((^|%>)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split(" ").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');"):s[t]=s[t];return i?e(i):e}if(t instanceof Function)return t(i);var s={};return e(t,i)}),w=(s.generateLabels=function(t,i,e,s){var o=new Array(i);return labelTemplateString&&n(o,function(i,n){o[n]=C(t,{value:e+s*(n+1)})}),o},s.easingEffects={linear:function(t){return t},easeInQuad:function(t){return t*t},easeOutQuad:function(t){return-1*t*(t-2)},easeInOutQuad:function(t){return(t/=.5)<1?.5*t*t:-0.5*(--t*(t-2)-1)},easeInCubic:function(t){return t*t*t},easeOutCubic:function(t){return 1*((t=t/1-1)*t*t+1)},easeInOutCubic:function(t){return(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2)},easeInQuart:function(t){return t*t*t*t},easeOutQuart:function(t){return-1*((t=t/1-1)*t*t*t-1)},easeInOutQuart:function(t){return(t/=.5)<1?.5*t*t*t*t:-0.5*((t-=2)*t*t*t-2)},easeInQuint:function(t){return 1*(t/=1)*t*t*t*t},easeOutQuint:function(t){return 1*((t=t/1-1)*t*t*t*t+1)},easeInOutQuint:function(t){return(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)},easeInSine:function(t){return-1*Math.cos(t/1*(Math.PI/2))+1},easeOutSine:function(t){return 1*Math.sin(t/1*(Math.PI/2))},easeInOutSine:function(t){return-0.5*(Math.cos(Math.PI*t/1)-1)},easeInExpo:function(t){return 0===t?1:1*Math.pow(2,10*(t/1-1))},easeOutExpo:function(t){return 1===t?1:1*(-Math.pow(2,-10*t/1)+1)},easeInOutExpo:function(t){return 0===t?0:1===t?1:(t/=.5)<1?.5*Math.pow(2,10*(t-1)):.5*(-Math.pow(2,-10*--t)+2)},easeInCirc:function(t){return t>=1?t:-1*(Math.sqrt(1-(t/=1)*t)-1)},easeOutCirc:function(t){return 1*Math.sqrt(1-(t=t/1-1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-0.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var i=1.70158,e=0,s=1;return 0===t?0:1==(t/=1)?1:(e||(e=.3),st?-.5*s*Math.pow(2,10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e):s*Math.pow(2,-10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e)*.5+1)},easeInBack:function(t){var i=1.70158;return 1*(t/=1)*t*((i+1)*t-i)},easeOutBack:function(t){var i=1.70158;return 1*((t=t/1-1)*t*((i+1)*t+i)+1)},easeInOutBack:function(t){var i=1.70158;return(t/=.5)<1?.5*t*t*(((i*=1.525)+1)*t-i):.5*((t-=2)*t*(((i*=1.525)+1)*t+i)+2)},easeInBounce:function(t){return 1-w.easeOutBounce(1-t)},easeOutBounce:function(t){return(t/=1)<1/2.75?7.5625*t*t:2/2.75>t?1*(7.5625*(t-=1.5/2.75)*t+.75):2.5/2.75>t?1*(7.5625*(t-=2.25/2.75)*t+.9375):1*(7.5625*(t-=2.625/2.75)*t+.984375)},easeInOutBounce:function(t){return.5>t?.5*w.easeInBounce(2*t):.5*w.easeOutBounce(2*t-1)+.5}}),b=s.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)}}(),P=s.cancelAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return window.clearTimeout(t,1e3/60)}}(),L=(s.animationLoop=function(t,i,e,s,n,o){var a=0,h=w[e]||w.linear,l=function(){a++;var e=a/i,r=h(e);t.call(o,r,e,a),s.call(o,r,e),i>a?o.animationFrame=b(l):n.apply(o)};b(l)},s.getRelativePosition=function(t){var i,e,s=t.originalEvent||t,n=t.currentTarget||t.srcElement,o=n.getBoundingClientRect();return s.touches?(i=s.touches[0].clientX-o.left,e=s.touches[0].clientY-o.top):(i=s.clientX-o.left,e=s.clientY-o.top),{x:i,y:e}},s.addEvent=function(t,i,e){t.addEventListener?t.addEventListener(i,e):t.attachEvent?t.attachEvent("on"+i,e):t["on"+i]=e}),k=s.removeEvent=function(t,i,e){t.removeEventListener?t.removeEventListener(i,e,!1):t.detachEvent?t.detachEvent("on"+i,e):t["on"+i]=c},F=(s.bindEvents=function(t,i,e){t.events||(t.events={}),n(i,function(i){t.events[i]=function(){e.apply(t,arguments)},L(t.chart.canvas,i,t.events[i])})},s.unbindEvents=function(t,i){n(i,function(i,e){k(t.chart.canvas,e,i)})}),R=s.getMaximumWidth=function(t){var i=t.parentNode;return i.clientWidth},T=s.getMaximumHeight=function(t){var i=t.parentNode;return i.clientHeight},A=(s.getMaximumSize=s.getMaximumWidth,s.retinaScale=function(t){var i=t.ctx,e=t.canvas.width,s=t.canvas.height;window.devicePixelRatio&&(i.canvas.style.width=e+"px",i.canvas.style.height=s+"px",i.canvas.height=s*window.devicePixelRatio,i.canvas.width=e*window.devicePixelRatio,i.scale(window.devicePixelRatio,window.devicePixelRatio))}),M=s.clear=function(t){t.ctx.clearRect(0,0,t.width,t.height)},W=s.fontString=function(t,i,e){return i+" "+t+"px "+e},z=s.longestText=function(t,i,e){t.font=i;var s=0;return n(e,function(i){var e=t.measureText(i).width;s=e>s?e:s}),s},B=s.drawRoundedRectangle=function(t,i,e,s,n,o){t.beginPath(),t.moveTo(i+o,e),t.lineTo(i+s-o,e),t.quadraticCurveTo(i+s,e,i+s,e+o),t.lineTo(i+s,e+n-o),t.quadraticCurveTo(i+s,e+n,i+s-o,e+n),t.lineTo(i+o,e+n),t.quadraticCurveTo(i,e+n,i,e+n-o),t.lineTo(i,e+o),t.quadraticCurveTo(i,e,i+o,e),t.closePath()};e.instances={},e.Type=function(t,i,s){this.options=i,this.chart=s,this.id=u(),e.instances[this.id]=this,i.responsive&&this.resize(),this.initialize.call(this,t)},a(e.Type.prototype,{initialize:function(){return this},clear:function(){return M(this.chart),this},stop:function(){return P(this.animationFrame),this},resize:function(t){this.stop();var i=this.chart.canvas,e=R(this.chart.canvas),s=this.options.maintainAspectRatio?e/this.chart.aspectRatio:T(this.chart.canvas);return i.width=this.chart.width=e,i.height=this.chart.height=s,A(this.chart),"function"==typeof t&&t.apply(this,Array.prototype.slice.call(arguments,1)),this},reflow:c,render:function(t){return t&&this.reflow(),this.options.animation&&!t?s.animationLoop(this.draw,this.options.animationSteps,this.options.animationEasing,this.options.onAnimationProgress,this.options.onAnimationComplete,this):(this.draw(),this.options.onAnimationComplete.call(this)),this},generateLegend:function(){return C(this.options.legendTemplate,this)},destroy:function(){this.clear(),F(this,this.events);var t=this.chart.canvas;t.width=this.chart.width,t.height=this.chart.height,t.style.removeProperty?(t.style.removeProperty("width"),t.style.removeProperty("height")):(t.style.removeAttribute("width"),t.style.removeAttribute("height")),delete e.instances[this.id]},showTooltip:function(t,i){"undefined"==typeof this.activeElements&&(this.activeElements=[]);var o=function(t){var i=!1;return t.length!==this.activeElements.length?i=!0:(n(t,function(t,e){t!==this.activeElements[e]&&(i=!0)},this),i)}.call(this,t);if(o||i){if(this.activeElements=t,this.draw(),this.options.customTooltips&&this.options.customTooltips(!1),t.length>0)if(this.datasets&&this.datasets.length>1){for(var a,h,r=this.datasets.length-1;r>=0&&(a=this.datasets[r].points||this.datasets[r].bars||this.datasets[r].segments,h=l(a,t[0]),-1===h);r--);var c=[],u=[],d=function(){var t,i,e,n,o,a=[],l=[],r=[];return s.each(this.datasets,function(i){t=i.points||i.bars||i.segments,t[h]&&t[h].hasValue()&&a.push(t[h])}),s.each(a,function(t){l.push(t.x),r.push(t.y),c.push(s.template(this.options.multiTooltipTemplate,t)),u.push({fill:t._saved.fillColor||t.fillColor,stroke:t._saved.strokeColor||t.strokeColor})},this),o=m(r),e=g(r),n=m(l),i=g(l),{x:n>this.chart.width/2?n:i,y:(o+e)/2}}.call(this,h);new e.MultiTooltip({x:d.x,y:d.y,xPadding:this.options.tooltipXPadding,yPadding:this.options.tooltipYPadding,xOffset:this.options.tooltipXOffset,fillColor:this.options.tooltipFillColor,textColor:this.options.tooltipFontColor,fontFamily:this.options.tooltipFontFamily,fontStyle:this.options.tooltipFontStyle,fontSize:this.options.tooltipFontSize,titleTextColor:this.options.tooltipTitleFontColor,titleFontFamily:this.options.tooltipTitleFontFamily,titleFontStyle:this.options.tooltipTitleFontStyle,titleFontSize:this.options.tooltipTitleFontSize,cornerRadius:this.options.tooltipCornerRadius,labels:c,legendColors:u,legendColorBackground:this.options.multiTooltipKeyBackground,title:t[0].label,chart:this.chart,ctx:this.chart.ctx,custom:this.options.customTooltips}).draw()}else n(t,function(t){var i=t.tooltipPosition();new e.Tooltip({x:Math.round(i.x),y:Math.round(i.y),xPadding:this.options.tooltipXPadding,yPadding:this.options.tooltipYPadding,fillColor:this.options.tooltipFillColor,textColor:this.options.tooltipFontColor,fontFamily:this.options.tooltipFontFamily,fontStyle:this.options.tooltipFontStyle,fontSize:this.options.tooltipFontSize,caretHeight:this.options.tooltipCaretSize,cornerRadius:this.options.tooltipCornerRadius,text:C(this.options.tooltipTemplate,t),chart:this.chart,custom:this.options.customTooltips}).draw()},this);return this}},toBase64Image:function(){return this.chart.canvas.toDataURL.apply(this.chart.canvas,arguments)}}),e.Type.extend=function(t){var i=this,s=function(){return i.apply(this,arguments)};if(s.prototype=o(i.prototype),a(s.prototype,t),s.extend=e.Type.extend,t.name||i.prototype.name){var n=t.name||i.prototype.name,l=e.defaults[i.prototype.name]?o(e.defaults[i.prototype.name]):{};e.defaults[n]=a(l,t.defaults),e.types[n]=s,e.prototype[n]=function(t,i){var o=h(e.defaults.global,e.defaults[n],i||{});return new s(t,o,this)}}else d("Name not provided for this chart, so it hasn't been registered");return i},e.Element=function(t){a(this,t),this.initialize.apply(this,arguments),this.save()},a(e.Element.prototype,{initialize:function(){},restore:function(t){return t?n(t,function(t){this[t]=this._saved[t]},this):a(this,this._saved),this},save:function(){return this._saved=o(this),delete this._saved._saved,this},update:function(t){return n(t,function(t,i){this._saved[i]=this[i],this[i]=t},this),this},transition:function(t,i){return n(t,function(t,e){this[e]=(t-this._saved[e])*i+this._saved[e]},this),this},tooltipPosition:function(){return{x:this.x,y:this.y}},hasValue:function(){return f(this.value)}}),e.Element.extend=r,e.Point=e.Element.extend({display:!0,inRange:function(t,i){var e=this.hitDetectionRadius+this.radius;return Math.pow(t-this.x,2)+Math.pow(i-this.y,2)=this.startAngle&&e.angle<=this.endAngle,o=e.distance>=this.innerRadius&&e.distance<=this.outerRadius;return n&&o},tooltipPosition:function(){var t=this.startAngle+(this.endAngle-this.startAngle)/2,i=(this.outerRadius-this.innerRadius)/2+this.innerRadius;return{x:this.x+Math.cos(t)*i,y:this.y+Math.sin(t)*i}},draw:function(t){var i=this.ctx;i.beginPath(),i.arc(this.x,this.y,this.outerRadius,this.startAngle,this.endAngle),i.arc(this.x,this.y,this.innerRadius,this.endAngle,this.startAngle,!0),i.closePath(),i.strokeStyle=this.strokeColor,i.lineWidth=this.strokeWidth,i.fillStyle=this.fillColor,i.fill(),i.lineJoin="bevel",this.showStroke&&i.stroke()}}),e.Rectangle=e.Element.extend({draw:function(){var t=this.ctx,i=this.width/2,e=this.x-i,s=this.x+i,n=this.base-(this.base-this.y),o=this.strokeWidth/2;this.showStroke&&(e+=o,s-=o,n+=o),t.beginPath(),t.fillStyle=this.fillColor,t.strokeStyle=this.strokeColor,t.lineWidth=this.strokeWidth,t.moveTo(e,this.base),t.lineTo(e,n),t.lineTo(s,n),t.lineTo(s,this.base),t.fill(),this.showStroke&&t.stroke()},height:function(){return this.base-this.y},inRange:function(t,i){return t>=this.x-this.width/2&&t<=this.x+this.width/2&&i>=this.y&&i<=this.base}}),e.Tooltip=e.Element.extend({draw:function(){var t=this.chart.ctx;t.font=W(this.fontSize,this.fontStyle,this.fontFamily),this.xAlign="center",this.yAlign="above";var i=this.caretPadding=2,e=t.measureText(this.text).width+2*this.xPadding,s=this.fontSize+2*this.yPadding,n=s+this.caretHeight+i;this.x+e/2>this.chart.width?this.xAlign="left":this.x-e/2<0&&(this.xAlign="right"),this.y-n<0&&(this.yAlign="below");var o=this.x-e/2,a=this.y-n;if(t.fillStyle=this.fillColor,this.custom)this.custom(this);else{switch(this.yAlign){case"above":t.beginPath(),t.moveTo(this.x,this.y-i),t.lineTo(this.x+this.caretHeight,this.y-(i+this.caretHeight)),t.lineTo(this.x-this.caretHeight,this.y-(i+this.caretHeight)),t.closePath(),t.fill();break;case"below":a=this.y+i+this.caretHeight,t.beginPath(),t.moveTo(this.x,this.y+i),t.lineTo(this.x+this.caretHeight,this.y+i+this.caretHeight),t.lineTo(this.x-this.caretHeight,this.y+i+this.caretHeight),t.closePath(),t.fill()}switch(this.xAlign){case"left":o=this.x-e+(this.cornerRadius+this.caretHeight);break;case"right":o=this.x-(this.cornerRadius+this.caretHeight)}B(t,o,a,e,s,this.cornerRadius),t.fill(),t.fillStyle=this.textColor,t.textAlign="center",t.textBaseline="middle",t.fillText(this.text,o+e/2,a+s/2)}}}),e.MultiTooltip=e.Element.extend({initialize:function(){this.font=W(this.fontSize,this.fontStyle,this.fontFamily),this.titleFont=W(this.titleFontSize,this.titleFontStyle,this.titleFontFamily),this.height=this.labels.length*this.fontSize+(this.labels.length-1)*(this.fontSize/2)+2*this.yPadding+1.5*this.titleFontSize,this.ctx.font=this.titleFont;var t=this.ctx.measureText(this.title).width,i=z(this.ctx,this.font,this.labels)+this.fontSize+3,e=g([i,t]);this.width=e+2*this.xPadding;var s=this.height/2;this.y-s<0?this.y=s:this.y+s>this.chart.height&&(this.y=this.chart.height-s),this.x>this.chart.width/2?this.x-=this.xOffset+this.width:this.x+=this.xOffset},getLineHeight:function(t){var i=this.y-this.height/2+this.yPadding,e=t-1;return 0===t?i+this.titleFontSize/2:i+(1.5*this.fontSize*e+this.fontSize/2)+1.5*this.titleFontSize},draw:function(){if(this.custom)this.custom(this);else{B(this.ctx,this.x,this.y-this.height/2,this.width,this.height,this.cornerRadius);var t=this.ctx;t.fillStyle=this.fillColor,t.fill(),t.closePath(),t.textAlign="left",t.textBaseline="middle",t.fillStyle=this.titleTextColor,t.font=this.titleFont,t.fillText(this.title,this.x+this.xPadding,this.getLineHeight(0)),t.font=this.font,s.each(this.labels,function(i,e){t.fillStyle=this.textColor,t.fillText(i,this.x+this.xPadding+this.fontSize+3,this.getLineHeight(e+1)),t.fillStyle=this.legendColorBackground,t.fillRect(this.x+this.xPadding,this.getLineHeight(e+1)-this.fontSize/2,this.fontSize,this.fontSize),t.fillStyle=this.legendColors[e].fill,t.fillRect(this.x+this.xPadding,this.getLineHeight(e+1)-this.fontSize/2,this.fontSize,this.fontSize)},this)}}}),e.Scale=e.Element.extend({initialize:function(){this.fit()},buildYLabels:function(){this.yLabels=[];for(var t=v(this.stepValue),i=0;i<=this.steps;i++)this.yLabels.push(C(this.templateString,{value:(this.min+i*this.stepValue).toFixed(t)}));this.yLabelWidth=this.display&&this.showLabels?z(this.ctx,this.font,this.yLabels):0},addXLabel:function(t){this.xLabels.push(t),this.valuesCount++,this.fit()},removeXLabel:function(){this.xLabels.shift(),this.valuesCount--,this.fit()},fit:function(){this.startPoint=this.display?this.fontSize:0,this.endPoint=this.display?this.height-1.5*this.fontSize-5:this.height,this.startPoint+=this.padding,this.endPoint-=this.padding;var t,i=this.endPoint-this.startPoint;for(this.calculateYRange(i),this.buildYLabels(),this.calculateXLabelRotation();i>this.endPoint-this.startPoint;)i=this.endPoint-this.startPoint,t=this.yLabelWidth,this.calculateYRange(i),this.buildYLabels(),tthis.yLabelWidth+10?e/2:this.yLabelWidth+10,this.xLabelRotation=0,this.display){var n,o=z(this.ctx,this.font,this.xLabels);this.xLabelWidth=o;for(var a=Math.floor(this.calculateX(1)-this.calculateX(0))-6;this.xLabelWidth>a&&0===this.xLabelRotation||this.xLabelWidth>a&&this.xLabelRotation<=90&&this.xLabelRotation>0;)n=Math.cos(S(this.xLabelRotation)),t=n*e,i=n*s,t+this.fontSize/2>this.yLabelWidth+8&&(this.xScalePaddingLeft=t+this.fontSize/2),this.xScalePaddingRight=this.fontSize/2,this.xLabelRotation++,this.xLabelWidth=n*o;this.xLabelRotation>0&&(this.endPoint-=Math.sin(S(this.xLabelRotation))*o+3)}else this.xLabelWidth=0,this.xScalePaddingRight=this.padding,this.xScalePaddingLeft=this.padding},calculateYRange:c,drawingArea:function(){return this.startPoint-this.endPoint},calculateY:function(t){var i=this.drawingArea()/(this.min-this.max);return this.endPoint-i*(t-this.min)},calculateX:function(t){var i=(this.xLabelRotation>0,this.width-(this.xScalePaddingLeft+this.xScalePaddingRight)),e=i/Math.max(this.valuesCount-(this.offsetGridLines?0:1),1),s=e*t+this.xScalePaddingLeft;return this.offsetGridLines&&(s+=e/2),Math.round(s)},update:function(t){s.extend(this,t),this.fit()},draw:function(){var t=this.ctx,i=(this.endPoint-this.startPoint)/this.steps,e=Math.round(this.xScalePaddingLeft);this.display&&(t.fillStyle=this.textColor,t.font=this.font,n(this.yLabels,function(n,o){var a=this.endPoint-i*o,h=Math.round(a),l=this.showHorizontalLines;t.textAlign="right",t.textBaseline="middle",this.showLabels&&t.fillText(n,e-10,a),0!==o||l||(l=!0),l&&t.beginPath(),o>0?(t.lineWidth=this.gridLineWidth,t.strokeStyle=this.gridLineColor):(t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor),h+=s.aliasPixel(t.lineWidth),l&&(t.moveTo(e,h),t.lineTo(this.width,h),t.stroke(),t.closePath()),t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor,t.beginPath(),t.moveTo(e-5,h),t.lineTo(e,h),t.stroke(),t.closePath()},this),n(this.xLabels,function(i,e){var s=this.calculateX(e)+x(this.lineWidth),n=this.calculateX(e-(this.offsetGridLines?.5:0))+x(this.lineWidth),o=this.xLabelRotation>0,a=this.showVerticalLines;0!==e||a||(a=!0),a&&t.beginPath(),e>0?(t.lineWidth=this.gridLineWidth,t.strokeStyle=this.gridLineColor):(t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor),a&&(t.moveTo(n,this.endPoint),t.lineTo(n,this.startPoint-3),t.stroke(),t.closePath()),t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor,t.beginPath(),t.moveTo(n,this.endPoint),t.lineTo(n,this.endPoint+5),t.stroke(),t.closePath(),t.save(),t.translate(s,o?this.endPoint+12:this.endPoint+8),t.rotate(-1*S(this.xLabelRotation)),t.font=this.font,t.textAlign=o?"right":"center",t.textBaseline=o?"middle":"top",t.fillText(i,0,0),t.restore()},this))}}),e.RadialScale=e.Element.extend({initialize:function(){this.size=m([this.height,this.width]),this.drawingArea=this.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2},calculateCenterOffset:function(t){var i=this.drawingArea/(this.max-this.min);return(t-this.min)*i},update:function(){this.lineArc?this.drawingArea=this.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2:this.setScaleSize(),this.buildYLabels()},buildYLabels:function(){this.yLabels=[];for(var t=v(this.stepValue),i=0;i<=this.steps;i++)this.yLabels.push(C(this.templateString,{value:(this.min+i*this.stepValue).toFixed(t)}))},getCircumference:function(){return 2*Math.PI/this.valuesCount},setScaleSize:function(){var t,i,e,s,n,o,a,h,l,r,c,u,d=m([this.height/2-this.pointLabelFontSize-5,this.width/2]),p=this.width,g=0;for(this.ctx.font=W(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily),i=0;ip&&(p=t.x+s,n=i),t.x-sp&&(p=t.x+e,n=i):i>this.valuesCount/2&&t.x-e0){var s,n=e*(this.drawingArea/this.steps),o=this.yCenter-n;if(this.lineWidth>0)if(t.strokeStyle=this.lineColor,t.lineWidth=this.lineWidth,this.lineArc)t.beginPath(),t.arc(this.xCenter,this.yCenter,n,0,2*Math.PI),t.closePath(),t.stroke();else{t.beginPath();for(var a=0;a=0;i--){if(this.angleLineWidth>0){var e=this.getPointPosition(i,this.calculateCenterOffset(this.max));t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(e.x,e.y),t.stroke(),t.closePath()}var s=this.getPointPosition(i,this.calculateCenterOffset(this.max)+5);t.font=W(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily),t.fillStyle=this.pointLabelFontColor;var o=this.labels.length,a=this.labels.length/2,h=a/2,l=h>i||i>o-h,r=i===h||i===o-h;t.textAlign=0===i?"center":i===a?"center":a>i?"left":"right",t.textBaseline=r?"middle":l?"bottom":"top",t.fillText(this.labels[i],s.x,s.y)}}}}}),s.addEvent(window,"resize",function(){var t;return function(){clearTimeout(t),t=setTimeout(function(){n(e.instances,function(t){t.options.responsive&&t.resize(t.render,!0)})},50)}}()),p?define(function(){return e}):"object"==typeof module&&module.exports&&(module.exports=e),t.Chart=e,e.noConflict=function(){return t.Chart=i,e}}).call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={scaleBeginAtZero:!0,scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,scaleShowHorizontalLines:!0,scaleShowVerticalLines:!0,barShowStroke:!0,barStrokeWidth:2,barValueSpacing:5,barDatasetSpacing:1,legendTemplate:'
      <% for (var i=0; i
    • <%if(datasets[i].label){%><%=datasets[i].label%><%}%>
    • <%}%>
    '};i.Type.extend({name:"Bar",defaults:s,initialize:function(t){var s=this.options;this.ScaleClass=i.Scale.extend({offsetGridLines:!0,calculateBarX:function(t,i,e){var n=this.calculateBaseWidth(),o=this.calculateX(e)-n/2,a=this.calculateBarWidth(t);return o+a*i+i*s.barDatasetSpacing+a/2},calculateBaseWidth:function(){return this.calculateX(1)-this.calculateX(0)-2*s.barValueSpacing},calculateBarWidth:function(t){var i=this.calculateBaseWidth()-(t-1)*s.barDatasetSpacing;return i/t}}),this.datasets=[],this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getBarsAtEvent(t):[];this.eachBars(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),this.BarClass=i.Rectangle.extend({strokeWidth:this.options.barStrokeWidth,showStroke:this.options.barShowStroke,ctx:this.chart.ctx}),e.each(t.datasets,function(i){var s={label:i.label||null,fillColor:i.fillColor,strokeColor:i.strokeColor,bars:[]};this.datasets.push(s),e.each(i.data,function(e,n){s.bars.push(new this.BarClass({value:e,label:t.labels[n],datasetLabel:i.label,strokeColor:i.strokeColor,fillColor:i.fillColor,highlightFill:i.highlightFill||i.fillColor,highlightStroke:i.highlightStroke||i.strokeColor}))},this)},this),this.buildScale(t.labels),this.BarClass.prototype.base=this.scale.endPoint,this.eachBars(function(t,i,s){e.extend(t,{width:this.scale.calculateBarWidth(this.datasets.length),x:this.scale.calculateBarX(this.datasets.length,s,i),y:this.scale.endPoint}),t.save()},this),this.render()},update:function(){this.scale.update(),e.each(this.activeElements,function(t){t.restore(["fillColor","strokeColor"])}),this.eachBars(function(t){t.save()}),this.render()},eachBars:function(t){e.each(this.datasets,function(i,s){e.each(i.bars,t,this,s)},this)},getBarsAtEvent:function(t){for(var i,s=[],n=e.getRelativePosition(t),o=function(t){s.push(t.bars[i])},a=0;a<% for (var i=0; i
  • <%if(segments[i].label){%><%=segments[i].label%><%}%>
  • <%}%>'};i.Type.extend({name:"Doughnut",defaults:s,initialize:function(t){this.segments=[],this.outerRadius=(e.min([this.chart.width,this.chart.height])-this.options.segmentStrokeWidth/2)/2,this.SegmentArc=i.Arc.extend({ctx:this.chart.ctx,x:this.chart.width/2,y:this.chart.height/2}),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getSegmentsAtEvent(t):[];e.each(this.segments,function(t){t.restore(["fillColor"])}),e.each(i,function(t){t.fillColor=t.highlightColor}),this.showTooltip(i)}),this.calculateTotal(t),e.each(t,function(t,i){this.addData(t,i,!0)},this),this.render()},getSegmentsAtEvent:function(t){var i=[],s=e.getRelativePosition(t);return e.each(this.segments,function(t){t.inRange(s.x,s.y)&&i.push(t)},this),i},addData:function(t,i,e){var s=i||this.segments.length;this.segments.splice(s,0,new this.SegmentArc({value:t.value,outerRadius:this.options.animateScale?0:this.outerRadius,innerRadius:this.options.animateScale?0:this.outerRadius/100*this.options.percentageInnerCutout,fillColor:t.color,highlightColor:t.highlight||t.color,showStroke:this.options.segmentShowStroke,strokeWidth:this.options.segmentStrokeWidth,strokeColor:this.options.segmentStrokeColor,startAngle:1.5*Math.PI,circumference:this.options.animateRotate?0:this.calculateCircumference(t.value),label:t.label})),e||(this.reflow(),this.update())},calculateCircumference:function(t){return 2*Math.PI*(Math.abs(t)/this.total)},calculateTotal:function(t){this.total=0,e.each(t,function(t){this.total+=Math.abs(t.value)},this)},update:function(){this.calculateTotal(this.segments),e.each(this.activeElements,function(t){t.restore(["fillColor"])}),e.each(this.segments,function(t){t.save()}),this.render()},removeData:function(t){var i=e.isNumber(t)?t:this.segments.length-1;this.segments.splice(i,1),this.reflow(),this.update()},reflow:function(){e.extend(this.SegmentArc.prototype,{x:this.chart.width/2,y:this.chart.height/2}),this.outerRadius=(e.min([this.chart.width,this.chart.height])-this.options.segmentStrokeWidth/2)/2,e.each(this.segments,function(t){t.update({outerRadius:this.outerRadius,innerRadius:this.outerRadius/100*this.options.percentageInnerCutout})},this)},draw:function(t){var i=t?t:1;this.clear(),e.each(this.segments,function(t,e){t.transition({circumference:this.calculateCircumference(t.value),outerRadius:this.outerRadius,innerRadius:this.outerRadius/100*this.options.percentageInnerCutout},i),t.endAngle=t.startAngle+t.circumference,t.draw(),0===e&&(t.startAngle=1.5*Math.PI),e<% for (var i=0; i
  • <%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>'};i.Type.extend({name:"Line",defaults:s,initialize:function(t){this.PointClass=i.Point.extend({strokeWidth:this.options.pointDotStrokeWidth,radius:this.options.pointDotRadius,display:this.options.pointDot,hitDetectionRadius:this.options.pointHitDetectionRadius,ctx:this.chart.ctx,inRange:function(t){return Math.pow(t-this.x,2)0&&ithis.scale.endPoint?t.controlPoints.outer.y=this.scale.endPoint:t.controlPoints.outer.ythis.scale.endPoint?t.controlPoints.inner.y=this.scale.endPoint:t.controlPoints.inner.y0&&(s.lineTo(h[h.length-1].x,this.scale.endPoint),s.lineTo(h[0].x,this.scale.endPoint),s.fillStyle=t.fillColor,s.closePath(),s.fill()),e.each(h,function(t){t.draw()})},this)}})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,s={scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)",scaleBeginAtZero:!0,scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,scaleShowLine:!0,segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,legendTemplate:'
      <% for (var i=0; i
    • <%if(segments[i].label){%><%=segments[i].label%><%}%>
    • <%}%>
    '};i.Type.extend({name:"PolarArea",defaults:s,initialize:function(t){this.segments=[],this.SegmentArc=i.Arc.extend({showStroke:this.options.segmentShowStroke,strokeWidth:this.options.segmentStrokeWidth,strokeColor:this.options.segmentStrokeColor,ctx:this.chart.ctx,innerRadius:0,x:this.chart.width/2,y:this.chart.height/2}),this.scale=new i.RadialScale({display:this.options.showScale,fontStyle:this.options.scaleFontStyle,fontSize:this.options.scaleFontSize,fontFamily:this.options.scaleFontFamily,fontColor:this.options.scaleFontColor,showLabels:this.options.scaleShowLabels,showLabelBackdrop:this.options.scaleShowLabelBackdrop,backdropColor:this.options.scaleBackdropColor,backdropPaddingY:this.options.scaleBackdropPaddingY,backdropPaddingX:this.options.scaleBackdropPaddingX,lineWidth:this.options.scaleShowLine?this.options.scaleLineWidth:0,lineColor:this.options.scaleLineColor,lineArc:!0,width:this.chart.width,height:this.chart.height,xCenter:this.chart.width/2,yCenter:this.chart.height/2,ctx:this.chart.ctx,templateString:this.options.scaleLabel,valuesCount:t.length}),this.updateScaleRange(t),this.scale.update(),e.each(t,function(t,i){this.addData(t,i,!0)},this),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getSegmentsAtEvent(t):[];e.each(this.segments,function(t){t.restore(["fillColor"])}),e.each(i,function(t){t.fillColor=t.highlightColor}),this.showTooltip(i)}),this.render()},getSegmentsAtEvent:function(t){var i=[],s=e.getRelativePosition(t);return e.each(this.segments,function(t){t.inRange(s.x,s.y)&&i.push(t)},this),i},addData:function(t,i,e){var s=i||this.segments.length;this.segments.splice(s,0,new this.SegmentArc({fillColor:t.color,highlightColor:t.highlight||t.color,label:t.label,value:t.value,outerRadius:this.options.animateScale?0:this.scale.calculateCenterOffset(t.value),circumference:this.options.animateRotate?0:this.scale.getCircumference(),startAngle:1.5*Math.PI})),e||(this.reflow(),this.update())},removeData:function(t){var i=e.isNumber(t)?t:this.segments.length-1;this.segments.splice(i,1),this.reflow(),this.update()},calculateTotal:function(t){this.total=0,e.each(t,function(t){this.total+=t.value},this),this.scale.valuesCount=this.segments.length},updateScaleRange:function(t){var i=[];e.each(t,function(t){i.push(t.value)});var s=this.options.scaleOverride?{steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}:e.calculateScaleRange(i,e.min([this.chart.width,this.chart.height])/2,this.options.scaleFontSize,this.options.scaleBeginAtZero,this.options.scaleIntegersOnly);e.extend(this.scale,s,{size:e.min([this.chart.width,this.chart.height]),xCenter:this.chart.width/2,yCenter:this.chart.height/2})},update:function(){this.calculateTotal(this.segments),e.each(this.segments,function(t){t.save()}),this.reflow(),this.render()},reflow:function(){e.extend(this.SegmentArc.prototype,{x:this.chart.width/2,y:this.chart.height/2}),this.updateScaleRange(this.segments),this.scale.update(),e.extend(this.scale,{xCenter:this.chart.width/2,yCenter:this.chart.height/2}),e.each(this.segments,function(t){t.update({outerRadius:this.scale.calculateCenterOffset(t.value)})},this)},draw:function(t){var i=t||1;this.clear(),e.each(this.segments,function(t,e){t.transition({circumference:this.scale.getCircumference(),outerRadius:this.scale.calculateCenterOffset(t.value)},i),t.endAngle=t.startAngle+t.circumference,0===e&&(t.startAngle=1.5*Math.PI),e<% for (var i=0; i
  • <%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>'},initialize:function(t){this.PointClass=i.Point.extend({strokeWidth:this.options.pointDotStrokeWidth,radius:this.options.pointDotRadius,display:this.options.pointDot,hitDetectionRadius:this.options.pointHitDetectionRadius,ctx:this.chart.ctx}),this.datasets=[],this.buildScale(t),this.options.showTooltips&&e.bindEvents(this,this.options.tooltipEvents,function(t){var i="mouseout"!==t.type?this.getPointsAtEvent(t):[];this.eachPoints(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),e.each(t.datasets,function(i){var s={label:i.label||null,fillColor:i.fillColor,strokeColor:i.strokeColor,pointColor:i.pointColor,pointStrokeColor:i.pointStrokeColor,points:[]};this.datasets.push(s),e.each(i.data,function(e,n){var o;this.scale.animation||(o=this.scale.getPointPosition(n,this.scale.calculateCenterOffset(e))),s.points.push(new this.PointClass({value:e,label:t.labels[n],datasetLabel:i.label,x:this.options.animation?this.scale.xCenter:o.x,y:this.options.animation?this.scale.yCenter:o.y,strokeColor:i.pointStrokeColor,fillColor:i.pointColor,highlightFill:i.pointHighlightFill||i.pointColor,highlightStroke:i.pointHighlightStroke||i.pointStrokeColor}))},this)},this),this.render()},eachPoints:function(t){e.each(this.datasets,function(i){e.each(i.points,t,this)},this)},getPointsAtEvent:function(t){var i=e.getRelativePosition(t),s=e.getAngleFromPoint({x:this.scale.xCenter,y:this.scale.yCenter},i),n=2*Math.PI/this.scale.valuesCount,o=Math.round((s.angle-1.5*Math.PI)/n),a=[];return(o>=this.scale.valuesCount||0>o)&&(o=0),s.distance<=this.scale.drawingArea&&e.each(this.datasets,function(t){a.push(t.points[o])}),a},buildScale:function(t){this.scale=new i.RadialScale({display:this.options.showScale,fontStyle:this.options.scaleFontStyle,fontSize:this.options.scaleFontSize,fontFamily:this.options.scaleFontFamily,fontColor:this.options.scaleFontColor,showLabels:this.options.scaleShowLabels,showLabelBackdrop:this.options.scaleShowLabelBackdrop,backdropColor:this.options.scaleBackdropColor,backdropPaddingY:this.options.scaleBackdropPaddingY,backdropPaddingX:this.options.scaleBackdropPaddingX,lineWidth:this.options.scaleShowLine?this.options.scaleLineWidth:0,lineColor:this.options.scaleLineColor,angleLineColor:this.options.angleLineColor,angleLineWidth:this.options.angleShowLineOut?this.options.angleLineWidth:0,pointLabelFontColor:this.options.pointLabelFontColor,pointLabelFontSize:this.options.pointLabelFontSize,pointLabelFontFamily:this.options.pointLabelFontFamily,pointLabelFontStyle:this.options.pointLabelFontStyle,height:this.chart.height,width:this.chart.width,xCenter:this.chart.width/2,yCenter:this.chart.height/2,ctx:this.chart.ctx,templateString:this.options.scaleLabel,labels:t.labels,valuesCount:t.datasets[0].data.length}),this.scale.setScaleSize(),this.updateScaleRange(t.datasets),this.scale.buildYLabels()},updateScaleRange:function(t){var i=function(){var i=[];return e.each(t,function(t){t.data?i=i.concat(t.data):e.each(t.points,function(t){i.push(t.value)})}),i}(),s=this.options.scaleOverride?{steps:this.options.scaleSteps,stepValue:this.options.scaleStepWidth,min:this.options.scaleStartValue,max:this.options.scaleStartValue+this.options.scaleSteps*this.options.scaleStepWidth}:e.calculateScaleRange(i,e.min([this.chart.width,this.chart.height])/2,this.options.scaleFontSize,this.options.scaleBeginAtZero,this.options.scaleIntegersOnly);e.extend(this.scale,s)},addData:function(t,i){this.scale.valuesCount++,e.each(t,function(t,e){var s=this.scale.getPointPosition(this.scale.valuesCount,this.scale.calculateCenterOffset(t));this.datasets[e].points.push(new this.PointClass({value:t,label:i,x:s.x,y:s.y,strokeColor:this.datasets[e].pointStrokeColor,fillColor:this.datasets[e].pointColor}))},this),this.scale.labels.push(i),this.reflow(),this.update()},removeData:function(){this.scale.valuesCount--,this.scale.labels.shift(),e.each(this.datasets,function(t){t.points.shift()},this),this.reflow(),this.update()},update:function(){this.eachPoints(function(t){t.save()}),this.reflow(),this.render()},reflow:function(){e.extend(this.scale,{width:this.chart.width,height:this.chart.height,size:e.min([this.chart.width,this.chart.height]),xCenter:this.chart.width/2,yCenter:this.chart.height/2}),this.updateScaleRange(this.datasets),this.scale.setScaleSize(),this.scale.buildYLabels()},draw:function(t){var i=t||1,s=this.chart.ctx;this.clear(),this.scale.draw(),e.each(this.datasets,function(t){e.each(t.points,function(t,e){t.hasValue()&&t.transition(this.scale.getPointPosition(e,this.scale.calculateCenterOffset(t.value)),i)},this),s.lineWidth=this.options.datasetStrokeWidth,s.strokeStyle=t.strokeColor,s.beginPath(),e.each(t.points,function(t,i){0===i?s.moveTo(t.x,t.y):s.lineTo(t.x,t.y)},this),s.closePath(),s.stroke(),s.fillStyle=t.fillColor,s.fill(),e.each(t.points,function(t){t.hasValue()&&t.draw()})},this)}})}.call(this); \ No newline at end of file diff --git a/vendor/assets/javascripts/highlight.pack.js b/vendor/assets/javascripts/highlight.pack.js deleted file mode 100644 index 17b457bf74..0000000000 --- a/vendor/assets/javascripts/highlight.pack.js +++ /dev/null @@ -1 +0,0 @@ -var hljs=new function(){function j(v){return v.replace(/&/gm,"&").replace(//gm,">")}function t(v){return v.nodeName.toLowerCase()}function h(w,x){var v=w&&w.exec(x);return v&&v.index==0}function r(w){var v=(w.className+" "+(w.parentNode?w.parentNode.className:"")).split(/\s+/);v=v.map(function(x){return x.replace(/^lang(uage)?-/,"")});return v.filter(function(x){return i(x)||x=="no-highlight"})[0]}function o(x,y){var v={};for(var w in x){v[w]=x[w]}if(y){for(var w in y){v[w]=y[w]}}return v}function u(x){var v=[];(function w(y,z){for(var A=y.firstChild;A;A=A.nextSibling){if(A.nodeType==3){z+=A.nodeValue.length}else{if(t(A)=="br"){z+=1}else{if(A.nodeType==1){v.push({event:"start",offset:z,node:A});z=w(A,z);v.push({event:"stop",offset:z,node:A})}}}}return z})(x,0);return v}function q(w,y,C){var x=0;var F="";var z=[];function B(){if(!w.length||!y.length){return w.length?w:y}if(w[0].offset!=y[0].offset){return(w[0].offset"}function E(G){F+=""}function v(G){(G.event=="start"?A:E)(G.node)}while(w.length||y.length){var D=B();F+=j(C.substr(x,D[0].offset-x));x=D[0].offset;if(D==w){z.reverse().forEach(E);do{v(D.splice(0,1)[0]);D=B()}while(D==w&&D.length&&D[0].offset==x);z.reverse().forEach(A)}else{if(D[0].event=="start"){z.push(D[0].node)}else{z.pop()}v(D.splice(0,1)[0])}}return F+j(C.substr(x))}function m(y){function v(z){return(z&&z.source)||z}function w(A,z){return RegExp(v(A),"m"+(y.cI?"i":"")+(z?"g":""))}function x(D,C){if(D.compiled){return}D.compiled=true;D.k=D.k||D.bK;if(D.k){var z={};var E=function(G,F){if(y.cI){F=F.toLowerCase()}F.split(" ").forEach(function(H){var I=H.split("|");z[I[0]]=[G,I[1]?Number(I[1]):1]})};if(typeof D.k=="string"){E("keyword",D.k)}else{Object.keys(D.k).forEach(function(F){E(F,D.k[F])})}D.k=z}D.lR=w(D.l||/\b[A-Za-z0-9_]+\b/,true);if(C){if(D.bK){D.b="\\b("+D.bK.split(" ").join("|")+")\\b"}if(!D.b){D.b=/\B|\b/}D.bR=w(D.b);if(!D.e&&!D.eW){D.e=/\B|\b/}if(D.e){D.eR=w(D.e)}D.tE=v(D.e)||"";if(D.eW&&C.tE){D.tE+=(D.e?"|":"")+C.tE}}if(D.i){D.iR=w(D.i)}if(D.r===undefined){D.r=1}if(!D.c){D.c=[]}var B=[];D.c.forEach(function(F){if(F.v){F.v.forEach(function(G){B.push(o(F,G))})}else{B.push(F=="self"?D:F)}});D.c=B;D.c.forEach(function(F){x(F,D)});if(D.starts){x(D.starts,C)}var A=D.c.map(function(F){return F.bK?"\\.?("+F.b+")\\.?":F.b}).concat([D.tE,D.i]).map(v).filter(Boolean);D.t=A.length?w(A.join("|"),true):{exec:function(F){return null}};D.continuation={}}x(y)}function c(S,L,J,R){function v(U,V){for(var T=0;T";U+=Z+'">';return U+X+Y}function N(){if(!I.k){return j(C)}var T="";var W=0;I.lR.lastIndex=0;var U=I.lR.exec(C);while(U){T+=j(C.substr(W,U.index-W));var V=E(I,U);if(V){H+=V[1];T+=w(V[0],j(U[0]))}else{T+=j(U[0])}W=I.lR.lastIndex;U=I.lR.exec(C)}return T+j(C.substr(W))}function F(){if(I.sL&&!f[I.sL]){return j(C)}var T=I.sL?c(I.sL,C,true,I.continuation.top):e(C);if(I.r>0){H+=T.r}if(I.subLanguageMode=="continuous"){I.continuation.top=T.top}return w(T.language,T.value,false,true)}function Q(){return I.sL!==undefined?F():N()}function P(V,U){var T=V.cN?w(V.cN,"",true):"";if(V.rB){D+=T;C=""}else{if(V.eB){D+=j(U)+T;C=""}else{D+=T;C=U}}I=Object.create(V,{parent:{value:I}})}function G(T,X){C+=T;if(X===undefined){D+=Q();return 0}var V=v(X,I);if(V){D+=Q();P(V,X);return V.rB?0:X.length}var W=z(I,X);if(W){var U=I;if(!(U.rE||U.eE)){C+=X}D+=Q();do{if(I.cN){D+=""}H+=I.r;I=I.parent}while(I!=W.parent);if(U.eE){D+=j(X)}C="";if(W.starts){P(W.starts,"")}return U.rE?0:X.length}if(A(X,I)){throw new Error('Illegal lexeme "'+X+'" for mode "'+(I.cN||"")+'"')}C+=X;return X.length||1}var M=i(S);if(!M){throw new Error('Unknown language: "'+S+'"')}m(M);var I=R||M;var D="";for(var K=I;K!=M;K=K.parent){if(K.cN){D+=w(K.cN,D,true)}}var C="";var H=0;try{var B,y,x=0;while(true){I.t.lastIndex=x;B=I.t.exec(L);if(!B){break}y=G(L.substr(x,B.index-x),B[0]);x=B.index+y}G(L.substr(x));for(var K=I;K.parent;K=K.parent){if(K.cN){D+=""}}return{r:H,value:D,language:S,top:I}}catch(O){if(O.message.indexOf("Illegal")!=-1){return{r:0,value:j(L)}}else{throw O}}}function e(y,x){x=x||b.languages||Object.keys(f);var v={r:0,value:j(y)};var w=v;x.forEach(function(z){if(!i(z)){return}var A=c(z,y,false);A.language=z;if(A.r>w.r){w=A}if(A.r>v.r){w=v;v=A}});if(w.language){v.second_best=w}return v}function g(v){if(b.tabReplace){v=v.replace(/^((<[^>]+>|\t)+)/gm,function(w,z,y,x){return z.replace(/\t/g,b.tabReplace)})}if(b.useBR){v=v.replace(/\n/g,"
    ")}return v}function p(z){var y=b.useBR?z.innerHTML.replace(/\n/g,"").replace(/
    |
    ]*>/g,"\n").replace(/<[^>]*>/g,""):z.textContent;var A=r(z);if(A=="no-highlight"){return}var v=A?c(A,y,true):e(y);var w=u(z);if(w.length){var x=document.createElementNS("http://www.w3.org/1999/xhtml","pre");x.innerHTML=v.value;v.value=q(w,u(x),y)}v.value=g(v.value);z.innerHTML=v.value;z.className+=" hljs "+(!A&&v.language||"");z.result={language:v.language,re:v.r};if(v.second_best){z.second_best={language:v.second_best.language,re:v.second_best.r}}}var b={classPrefix:"hljs-",tabReplace:null,useBR:false,languages:undefined};function s(v){b=o(b,v)}function l(){if(l.called){return}l.called=true;var v=document.querySelectorAll("pre code");Array.prototype.forEach.call(v,p)}function a(){addEventListener("DOMContentLoaded",l,false);addEventListener("load",l,false)}var f={};var n={};function d(v,x){var w=f[v]=x(this);if(w.aliases){w.aliases.forEach(function(y){n[y]=v})}}function k(){return Object.keys(f)}function i(v){return f[v]||f[n[v]]}this.highlight=c;this.highlightAuto=e;this.fixMarkup=g;this.highlightBlock=p;this.configure=s;this.initHighlighting=l;this.initHighlightingOnLoad=a;this.registerLanguage=d;this.listLanguages=k;this.getLanguage=i;this.inherit=o;this.IR="[a-zA-Z][a-zA-Z0-9_]*";this.UIR="[a-zA-Z_][a-zA-Z0-9_]*";this.NR="\\b\\d+(\\.\\d+)?";this.CNR="(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)";this.BNR="\\b(0b[01]+)";this.RSR="!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~";this.BE={b:"\\\\[\\s\\S]",r:0};this.ASM={cN:"string",b:"'",e:"'",i:"\\n",c:[this.BE]};this.QSM={cN:"string",b:'"',e:'"',i:"\\n",c:[this.BE]};this.PWM={b:/\b(a|an|the|are|I|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such)\b/};this.CLCM={cN:"comment",b:"//",e:"$",c:[this.PWM]};this.CBCM={cN:"comment",b:"/\\*",e:"\\*/",c:[this.PWM]};this.HCM={cN:"comment",b:"#",e:"$",c:[this.PWM]};this.NM={cN:"number",b:this.NR,r:0};this.CNM={cN:"number",b:this.CNR,r:0};this.BNM={cN:"number",b:this.BNR,r:0};this.CSSNM={cN:"number",b:this.NR+"(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",r:0};this.RM={cN:"regexp",b:/\//,e:/\/[gim]*/,i:/\n/,c:[this.BE,{b:/\[/,e:/\]/,r:0,c:[this.BE]}]};this.TM={cN:"title",b:this.IR,r:0};this.UTM={cN:"title",b:this.UIR,r:0}}();hljs.registerLanguage("bash",function(b){var a={cN:"variable",v:[{b:/\$[\w\d#@][\w\d_]*/},{b:/\$\{(.*?)\}/}]};var d={cN:"string",b:/"/,e:/"/,c:[b.BE,a,{cN:"variable",b:/\$\(/,e:/\)/,c:[b.BE]}]};var c={cN:"string",b:/'/,e:/'/};return{aliases:["sh","zsh"],l:/-?[a-z\.]+/,k:{keyword:"if then else elif fi for break continue while in do done exit return set declare case esac export exec",literal:"true false",built_in:"printf echo read cd pwd pushd popd dirs let eval unset typeset readonly getopts source shopt caller type hash bind help sudo",operator:"-ne -eq -lt -gt -f -d -e -s -l -a"},c:[{cN:"shebang",b:/^#![^\n]+sh\s*$/,r:10},{cN:"function",b:/\w[\w\d_]*\s*\(\s*\)\s*\{/,rB:true,c:[b.inherit(b.TM,{b:/\w[\w\d_]*/})],r:0},b.HCM,b.NM,d,c,a]}});hljs.registerLanguage("fix",function(a){return{c:[{b:/[^\u2401\u0001]+/,e:/[\u2401\u0001]/,eE:true,rB:true,rE:false,c:[{b:/([^\u2401\u0001=]+)/,e:/=([^\u2401\u0001=]+)/,rE:true,rB:false,cN:"attribute"},{b:/=/,e:/([\u2401\u0001])/,eE:true,eB:true,cN:"string"}]}],cI:true}});hljs.registerLanguage("nsis",function(a){var c={cN:"symbol",b:"\\$(ADMINTOOLS|APPDATA|CDBURN_AREA|CMDLINE|COMMONFILES32|COMMONFILES64|COMMONFILES|COOKIES|DESKTOP|DOCUMENTS|EXEDIR|EXEFILE|EXEPATH|FAVORITES|FONTS|HISTORY|HWNDPARENT|INSTDIR|INTERNET_CACHE|LANGUAGE|LOCALAPPDATA|MUSIC|NETHOOD|OUTDIR|PICTURES|PLUGINSDIR|PRINTHOOD|PROFILE|PROGRAMFILES32|PROGRAMFILES64|PROGRAMFILES|QUICKLAUNCH|RECENT|RESOURCES_LOCALIZED|RESOURCES|SENDTO|SMPROGRAMS|SMSTARTUP|STARTMENU|SYSDIR|TEMP|TEMPLATES|VIDEOS|WINDIR)"};var b={cN:"constant",b:"\\$+{[a-zA-Z0-9_]+}"};var f={cN:"variable",b:"\\$+[a-zA-Z0-9_]+",i:"\\(\\){}"};var e={cN:"constant",b:"\\$+\\([a-zA-Z0-9_]+\\)"};var g={cN:"params",b:"(ARCHIVE|FILE_ATTRIBUTE_ARCHIVE|FILE_ATTRIBUTE_NORMAL|FILE_ATTRIBUTE_OFFLINE|FILE_ATTRIBUTE_READONLY|FILE_ATTRIBUTE_SYSTEM|FILE_ATTRIBUTE_TEMPORARY|HKCR|HKCU|HKDD|HKEY_CLASSES_ROOT|HKEY_CURRENT_CONFIG|HKEY_CURRENT_USER|HKEY_DYN_DATA|HKEY_LOCAL_MACHINE|HKEY_PERFORMANCE_DATA|HKEY_USERS|HKLM|HKPD|HKU|IDABORT|IDCANCEL|IDIGNORE|IDNO|IDOK|IDRETRY|IDYES|MB_ABORTRETRYIGNORE|MB_DEFBUTTON1|MB_DEFBUTTON2|MB_DEFBUTTON3|MB_DEFBUTTON4|MB_ICONEXCLAMATION|MB_ICONINFORMATION|MB_ICONQUESTION|MB_ICONSTOP|MB_OK|MB_OKCANCEL|MB_RETRYCANCEL|MB_RIGHT|MB_RTLREADING|MB_SETFOREGROUND|MB_TOPMOST|MB_USERICON|MB_YESNO|NORMAL|OFFLINE|READONLY|SHCTX|SHELL_CONTEXT|SYSTEM|TEMPORARY)"};var d={cN:"constant",b:"\\!(addincludedir|addplugindir|appendfile|cd|define|delfile|echo|else|endif|error|execute|finalize|getdllversionsystem|ifdef|ifmacrodef|ifmacrondef|ifndef|if|include|insertmacro|macroend|macro|packhdr|searchparse|searchreplace|tempfile|undef|verbose|warning)"};return{cI:false,k:{keyword:"Abort AddBrandingImage AddSize AllowRootDirInstall AllowSkipFiles AutoCloseWindow BGFont BGGradient BrandingText BringToFront Call CallInstDLL Caption ChangeUI CheckBitmap ClearErrors CompletedText ComponentText CopyFiles CRCCheck CreateDirectory CreateFont CreateShortCut Delete DeleteINISec DeleteINIStr DeleteRegKey DeleteRegValue DetailPrint DetailsButtonText DirText DirVar DirVerify EnableWindow EnumRegKey EnumRegValue Exch Exec ExecShell ExecWait ExpandEnvStrings File FileBufSize FileClose FileErrorText FileOpen FileRead FileReadByte FileReadUTF16LE FileReadWord FileSeek FileWrite FileWriteByte FileWriteUTF16LE FileWriteWord FindClose FindFirst FindNext FindWindow FlushINI FunctionEnd GetCurInstType GetCurrentAddress GetDlgItem GetDLLVersion GetDLLVersionLocal GetErrorLevel GetFileTime GetFileTimeLocal GetFullPathName GetFunctionAddress GetInstDirError GetLabelAddress GetTempFileName Goto HideWindow Icon IfAbort IfErrors IfFileExists IfRebootFlag IfSilent InitPluginsDir InstallButtonText InstallColors InstallDir InstallDirRegKey InstProgressFlags InstType InstTypeGetText InstTypeSetText IntCmp IntCmpU IntFmt IntOp IsWindow LangString LicenseBkColor LicenseData LicenseForceSelection LicenseLangString LicenseText LoadLanguageFile LockWindow LogSet LogText ManifestDPIAware ManifestSupportedOS MessageBox MiscButtonText Name Nop OutFile Page PageCallbacks PageExEnd Pop Push Quit ReadEnvStr ReadINIStr ReadRegDWORD ReadRegStr Reboot RegDLL Rename RequestExecutionLevel ReserveFile Return RMDir SearchPath SectionEnd SectionGetFlags SectionGetInstTypes SectionGetSize SectionGetText SectionGroupEnd SectionIn SectionSetFlags SectionSetInstTypes SectionSetSize SectionSetText SendMessage SetAutoClose SetBrandingImage SetCompress SetCompressor SetCompressorDictSize SetCtlColors SetCurInstType SetDatablockOptimize SetDateSave SetDetailsPrint SetDetailsView SetErrorLevel SetErrors SetFileAttributes SetFont SetOutPath SetOverwrite SetPluginUnload SetRebootFlag SetRegView SetShellVarContext SetSilent ShowInstDetails ShowUninstDetails ShowWindow SilentInstall SilentUnInstall Sleep SpaceTexts StrCmp StrCmpS StrCpy StrLen SubCaption SubSectionEnd Unicode UninstallButtonText UninstallCaption UninstallIcon UninstallSubCaption UninstallText UninstPage UnRegDLL Var VIAddVersionKey VIFileVersion VIProductVersion WindowIcon WriteINIStr WriteRegBin WriteRegDWORD WriteRegExpandStr WriteRegStr WriteUninstaller XPStyle",literal:"admin all auto both colored current false force hide highest lastused leave listonly none normal notset off on open print show silent silentlog smooth textonly true user "},c:[a.HCM,a.CBCM,{cN:"string",b:'"',e:'"',i:"\\n",c:[{cN:"symbol",b:"\\$(\\\\(n|r|t)|\\$)"},c,b,f,e]},{cN:"comment",b:";",e:"$",r:0},{cN:"function",bK:"Function PageEx Section SectionGroup SubSection",e:"$"},d,b,f,e,g,a.NM,{cN:"literal",b:a.IR+"::"+a.IR}]}});hljs.registerLanguage("haxe",function(a){var c="[a-zA-Z_$][a-zA-Z0-9_$]*";var b="([*]|[a-zA-Z_$][a-zA-Z0-9_$]*)";return{aliases:["hx"],k:{keyword:"break callback case cast catch class continue default do dynamic else enum extends extern for function here if implements import in inline interface never new override package private public return static super switch this throw trace try typedef untyped using var while",literal:"true false null"},c:[a.ASM,a.QSM,a.CLCM,a.CBCM,a.CNM,{cN:"class",bK:"class interface",e:"{",eE:true,c:[{bK:"extends implements"},a.TM]},{cN:"preprocessor",b:"#",e:"$",k:"if else elseif end error"},{cN:"function",bK:"function",e:"[{;]",eE:true,i:"\\S",c:[a.TM,{cN:"params",b:"\\(",e:"\\)",c:[a.ASM,a.QSM,a.CLCM,a.CBCM]},{cN:"type",b:":",e:b,r:10}]}]}});hljs.registerLanguage("erlang",function(i){var c="[a-z'][a-zA-Z0-9_']*";var o="("+c+":"+c+"|"+c+")";var f={keyword:"after and andalso|10 band begin bnot bor bsl bzr bxor case catch cond div end fun let not of orelse|10 query receive rem try when xor",literal:"false true"};var l={cN:"comment",b:"%",e:"$"};var e={cN:"number",b:"\\b(\\d+#[a-fA-F0-9]+|\\d+(\\.\\d+)?([eE][-+]?\\d+)?)",r:0};var g={b:"fun\\s+"+c+"/\\d+"};var n={b:o+"\\(",e:"\\)",rB:true,r:0,c:[{cN:"function_name",b:o,r:0},{b:"\\(",e:"\\)",eW:true,rE:true,r:0}]};var h={cN:"tuple",b:"{",e:"}",r:0};var a={cN:"variable",b:"\\b_([A-Z][A-Za-z0-9_]*)?",r:0};var m={cN:"variable",b:"[A-Z][a-zA-Z0-9_]*",r:0};var b={b:"#"+i.UIR,r:0,rB:true,c:[{cN:"record_name",b:"#"+i.UIR,r:0},{b:"{",e:"}",r:0}]};var k={bK:"fun receive if try case",e:"end",k:f};k.c=[l,g,i.inherit(i.ASM,{cN:""}),k,n,i.QSM,e,h,a,m,b];var j=[l,g,k,n,i.QSM,e,h,a,m,b];n.c[1].c=j;h.c=j;b.c[1].c=j;var d={cN:"params",b:"\\(",e:"\\)",c:j};return{aliases:["erl"],k:f,i:"(",rB:true,i:"\\(|#|//|/\\*|\\\\|:|;",c:[d,i.inherit(i.TM,{b:c})],starts:{e:";|\\.",k:f,c:j}},l,{cN:"pp",b:"^-",e:"\\.",r:0,eE:true,rB:true,l:"-"+i.IR,k:"-module -record -undef -export -ifdef -ifndef -author -copyright -doc -vsn -import -include -include_lib -compile -define -else -endif -file -behaviour -behavior -spec",c:[d]},e,i.QSM,b,a,m,h,{b:/\.$/}]}});hljs.registerLanguage("cs",function(b){var a="abstract as base bool break byte case catch char checked const continue decimal default delegate do double else enum event explicit extern false finally fixed float for foreach goto if implicit in int interface internal is lock long new null object operator out override params private protected public readonly ref return sbyte sealed short sizeof stackalloc static string struct switch this throw true try typeof uint ulong unchecked unsafe ushort using virtual volatile void while async await ascending descending from get group into join let orderby partial select set value var where yield";return{aliases:["csharp"],k:a,i:/::/,c:[{cN:"comment",b:"///",e:"$",rB:true,c:[{cN:"xmlDocTag",v:[{b:"///",r:0},{b:""},{b:""}]}]},b.CLCM,b.CBCM,{cN:"preprocessor",b:"#",e:"$",k:"if else elif endif define undef warning error line region endregion pragma checksum"},{cN:"string",b:'@"',e:'"',c:[{b:'""'}]},b.ASM,b.QSM,b.CNM,{bK:"protected public private internal",e:/[{;=]/,k:a,c:[{bK:"class namespace interface",starts:{c:[b.TM]}},{b:b.IR+"\\s*\\(",rB:true,c:[b.TM]}]}]}});hljs.registerLanguage("protobuf",function(a){return{k:{keyword:"package import option optional required repeated group",built_in:"double float int32 int64 uint32 uint64 sint32 sint64 fixed32 fixed64 sfixed32 sfixed64 bool string bytes",literal:"true false"},c:[a.QSM,a.NM,a.CLCM,{cN:"class",bK:"message enum service",e:/\{/,i:/\n/,c:[a.inherit(a.TM,{starts:{eW:true,eE:true}})]},{cN:"function",bK:"rpc",e:/;/,eE:true,k:"rpc returns"},{cN:"constant",b:/^\s*[A-Z_]+/,e:/\s*=/,eE:true}]}});hljs.registerLanguage("vim",function(a){return{l:/[!#@\w]+/,k:{keyword:"N|0 P|0 X|0 a|0 ab abc abo al am an|0 ar arga argd arge argdo argg argl argu as au aug aun b|0 bN ba bad bd be bel bf bl bm bn bo bp br brea breaka breakd breakl bro bufdo buffers bun bw c|0 cN cNf ca cabc caddb cad caddf cal cat cb cc ccl cd ce cex cf cfir cgetb cgete cg changes chd che checkt cl cla clo cm cmapc cme cn cnew cnf cno cnorea cnoreme co col colo com comc comp con conf cope cp cpf cq cr cs cst cu cuna cunme cw d|0 delm deb debugg delc delf dif diffg diffo diffp diffpu diffs diffthis dig di dl dell dj dli do doautoa dp dr ds dsp e|0 ea ec echoe echoh echom echon el elsei em en endfo endf endt endw ene ex exe exi exu f|0 files filet fin fina fini fir fix fo foldc foldd folddoc foldo for fu g|0 go gr grepa gu gv ha h|0 helpf helpg helpt hi hid his i|0 ia iabc if ij il im imapc ime ino inorea inoreme int is isp iu iuna iunme j|0 ju k|0 keepa kee keepj lN lNf l|0 lad laddb laddf la lan lat lb lc lch lcl lcs le lefta let lex lf lfir lgetb lgete lg lgr lgrepa lh ll lla lli lmak lm lmapc lne lnew lnf ln loadk lo loc lockv lol lope lp lpf lr ls lt lu lua luad luaf lv lvimgrepa lw m|0 ma mak map mapc marks mat me menut mes mk mks mksp mkv mkvie mod mz mzf nbc nb nbs n|0 new nm nmapc nme nn nnoreme noa no noh norea noreme norm nu nun nunme ol o|0 om omapc ome on ono onoreme opt ou ounme ow p|0 profd prof pro promptr pc ped pe perld po popu pp pre prev ps pt ptN ptf ptj ptl ptn ptp ptr pts pu pw py3 python3 py3d py3f py pyd pyf q|0 quita qa r|0 rec red redi redr redraws reg res ret retu rew ri rightb rub rubyd rubyf rund ru rv s|0 sN san sa sal sav sb sbN sba sbf sbl sbm sbn sbp sbr scrip scripte scs se setf setg setl sf sfir sh sim sig sil sl sla sm smap smapc sme sn sni sno snor snoreme sor so spelld spe spelli spellr spellu spellw sp spr sre st sta startg startr star stopi stj sts sun sunm sunme sus sv sw sy synti sync t|0 tN tabN tabc tabdo tabe tabf tabfir tabl tabm tabnew tabn tabo tabp tabr tabs tab ta tags tc tcld tclf te tf th tj tl tm tn to tp tr try ts tu u|0 undoj undol una unh unl unlo unm unme uns up v|0 ve verb vert vim vimgrepa vi viu vie vm vmapc vme vne vn vnoreme vs vu vunme windo w|0 wN wa wh wi winc winp wn wp wq wqa ws wu wv x|0 xa xmapc xm xme xn xnoreme xu xunme y|0 z|0 ~ Next Print append abbreviate abclear aboveleft all amenu anoremenu args argadd argdelete argedit argglobal arglocal argument ascii autocmd augroup aunmenu buffer bNext ball badd bdelete behave belowright bfirst blast bmodified bnext botright bprevious brewind break breakadd breakdel breaklist browse bunload bwipeout change cNext cNfile cabbrev cabclear caddbuffer caddexpr caddfile call catch cbuffer cclose center cexpr cfile cfirst cgetbuffer cgetexpr cgetfile chdir checkpath checktime clist clast close cmap cmapclear cmenu cnext cnewer cnfile cnoremap cnoreabbrev cnoremenu copy colder colorscheme command comclear compiler continue confirm copen cprevious cpfile cquit crewind cscope cstag cunmap cunabbrev cunmenu cwindow delete delmarks debug debuggreedy delcommand delfunction diffupdate diffget diffoff diffpatch diffput diffsplit digraphs display deletel djump dlist doautocmd doautoall deletep drop dsearch dsplit edit earlier echo echoerr echohl echomsg else elseif emenu endif endfor endfunction endtry endwhile enew execute exit exusage file filetype find finally finish first fixdel fold foldclose folddoopen folddoclosed foldopen function global goto grep grepadd gui gvim hardcopy help helpfind helpgrep helptags highlight hide history insert iabbrev iabclear ijump ilist imap imapclear imenu inoremap inoreabbrev inoremenu intro isearch isplit iunmap iunabbrev iunmenu join jumps keepalt keepmarks keepjumps lNext lNfile list laddexpr laddbuffer laddfile last language later lbuffer lcd lchdir lclose lcscope left leftabove lexpr lfile lfirst lgetbuffer lgetexpr lgetfile lgrep lgrepadd lhelpgrep llast llist lmake lmap lmapclear lnext lnewer lnfile lnoremap loadkeymap loadview lockmarks lockvar lolder lopen lprevious lpfile lrewind ltag lunmap luado luafile lvimgrep lvimgrepadd lwindow move mark make mapclear match menu menutranslate messages mkexrc mksession mkspell mkvimrc mkview mode mzscheme mzfile nbclose nbkey nbsart next nmap nmapclear nmenu nnoremap nnoremenu noautocmd noremap nohlsearch noreabbrev noremenu normal number nunmap nunmenu oldfiles open omap omapclear omenu only onoremap onoremenu options ounmap ounmenu ownsyntax print profdel profile promptfind promptrepl pclose pedit perl perldo pop popup ppop preserve previous psearch ptag ptNext ptfirst ptjump ptlast ptnext ptprevious ptrewind ptselect put pwd py3do py3file python pydo pyfile quit quitall qall read recover redo redir redraw redrawstatus registers resize retab return rewind right rightbelow ruby rubydo rubyfile rundo runtime rviminfo substitute sNext sandbox sargument sall saveas sbuffer sbNext sball sbfirst sblast sbmodified sbnext sbprevious sbrewind scriptnames scriptencoding scscope set setfiletype setglobal setlocal sfind sfirst shell simalt sign silent sleep slast smagic smapclear smenu snext sniff snomagic snoremap snoremenu sort source spelldump spellgood spellinfo spellrepall spellundo spellwrong split sprevious srewind stop stag startgreplace startreplace startinsert stopinsert stjump stselect sunhide sunmap sunmenu suspend sview swapname syntax syntime syncbind tNext tabNext tabclose tabedit tabfind tabfirst tablast tabmove tabnext tabonly tabprevious tabrewind tag tcl tcldo tclfile tearoff tfirst throw tjump tlast tmenu tnext topleft tprevious trewind tselect tunmenu undo undojoin undolist unabbreviate unhide unlet unlockvar unmap unmenu unsilent update vglobal version verbose vertical vimgrep vimgrepadd visual viusage view vmap vmapclear vmenu vnew vnoremap vnoremenu vsplit vunmap vunmenu write wNext wall while winsize wincmd winpos wnext wprevious wqall wsverb wundo wviminfo xit xall xmapclear xmap xmenu xnoremap xnoremenu xunmap xunmenu yank",built_in:"abs acos add and append argc argidx argv asin atan atan2 browse browsedir bufexists buflisted bufloaded bufname bufnr bufwinnr byte2line byteidx call ceil changenr char2nr cindent clearmatches col complete complete_add complete_check confirm copy cos cosh count cscope_connection cursor deepcopy delete did_filetype diff_filler diff_hlID empty escape eval eventhandler executable exists exp expand extend feedkeys filereadable filewritable filter finddir findfile float2nr floor fmod fnameescape fnamemodify foldclosed foldclosedend foldlevel foldtext foldtextresult foreground function garbagecollect get getbufline getbufvar getchar getcharmod getcmdline getcmdpos getcmdtype getcwd getfontname getfperm getfsize getftime getftype getline getloclist getmatches getpid getpos getqflist getreg getregtype gettabvar gettabwinvar getwinposx getwinposy getwinvar glob globpath has has_key haslocaldir hasmapto histadd histdel histget histnr hlexists hlID hostname iconv indent index input inputdialog inputlist inputrestore inputsave inputsecret insert invert isdirectory islocked items join keys len libcall libcallnr line line2byte lispindent localtime log log10 luaeval map maparg mapcheck match matchadd matcharg matchdelete matchend matchlist matchstr max min mkdir mode mzeval nextnonblank nr2char or pathshorten pow prevnonblank printf pumvisible py3eval pyeval range readfile reltime reltimestr remote_expr remote_foreground remote_peek remote_read remote_send remove rename repeat resolve reverse round screenattr screenchar screencol screenrow search searchdecl searchpair searchpairpos searchpos server2client serverlist setbufvar setcmdpos setline setloclist setmatches setpos setqflist setreg settabvar settabwinvar setwinvar sha256 shellescape shiftwidth simplify sin sinh sort soundfold spellbadword spellsuggest split sqrt str2float str2nr strchars strdisplaywidth strftime stridx string strlen strpart strridx strtrans strwidth submatch substitute synconcealed synID synIDattr synIDtrans synstack system tabpagebuflist tabpagenr tabpagewinnr tagfiles taglist tan tanh tempname tolower toupper tr trunc type undofile undotree values virtcol visualmode wildmenumode winbufnr wincol winheight winline winnr winrestcmd winrestview winsaveview winwidth writefile xor"},i:/[{:]/,c:[a.NM,a.ASM,{cN:"string",b:/"((\\")|[^"\n])*("|\n)/},{cN:"variable",b:/[bwtglsav]:[\w\d_]*/},{cN:"function",bK:"function function!",e:"$",r:0,c:[a.TM,{cN:"params",b:"\\(",e:"\\)"}]}]}});hljs.registerLanguage("brainfuck",function(b){var a={cN:"literal",b:"[\\+\\-]",r:0};return{aliases:["bf"],c:[{cN:"comment",b:"[^\\[\\]\\.,\\+\\-<> \r\n]",rE:true,e:"[\\[\\]\\.,\\+\\-<> \r\n]",r:0},{cN:"title",b:"[\\[\\]]",r:0},{cN:"string",b:"[\\.,]",r:0},{b:/\+\+|\-\-/,rB:true,c:[a]},a]}});hljs.registerLanguage("ruby",function(f){var j="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?";var i="and false then defined module in return redo if BEGIN retry end for true self when next until do begin unless END rescue nil else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor";var b={cN:"yardoctag",b:"@[A-Za-z]+"};var c={cN:"value",b:"#<",e:">"};var k={cN:"comment",v:[{b:"#",e:"$",c:[b]},{b:"^\\=begin",e:"^\\=end",c:[b],r:10},{b:"^__END__",e:"\\n$"}]};var d={cN:"subst",b:"#\\{",e:"}",k:i};var e={cN:"string",c:[f.BE,d],v:[{b:/'/,e:/'/},{b:/"/,e:/"/},{b:"%[qw]?\\(",e:"\\)"},{b:"%[qw]?\\[",e:"\\]"},{b:"%[qw]?{",e:"}"},{b:"%[qw]?<",e:">"},{b:"%[qw]?/",e:"/"},{b:"%[qw]?%",e:"%"},{b:"%[qw]?-",e:"-"},{b:"%[qw]?\\|",e:"\\|"},{b:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/}]};var a={cN:"params",b:"\\(",e:"\\)",k:i};var h=[e,c,k,{cN:"class",bK:"class module",e:"$|;",i:/=/,c:[f.inherit(f.TM,{b:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{cN:"inheritance",b:"<\\s*",c:[{cN:"parent",b:"("+f.IR+"::)?"+f.IR}]},k]},{cN:"function",bK:"def",e:" |$|;",r:0,c:[f.inherit(f.TM,{b:j}),a,k]},{cN:"constant",b:"(::)?(\\b[A-Z]\\w*(::)?)+",r:0},{cN:"symbol",b:":",c:[e,{b:j}],r:0},{cN:"symbol",b:f.UIR+"(\\!|\\?)?:",r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{cN:"variable",b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{b:"("+f.RSR+")\\s*",c:[c,k,{cN:"regexp",c:[f.BE,d],i:/\n/,v:[{b:"/",e:"/[a-z]*"},{b:"%r{",e:"}[a-z]*"},{b:"%r\\(",e:"\\)[a-z]*"},{b:"%r!",e:"![a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}],r:0}];d.c=h;a.c=h;var g=[{r:1,cN:"output",b:"^\\s*=> ",e:"$",rB:true,c:[{cN:"status",b:"^\\s*=>"},{b:" ",e:"$",c:h}]},{r:1,cN:"input",b:"^[^ ][^=>]*>+ ",e:"$",rB:true,c:[{cN:"prompt",b:"^[^ ][^=>]*>+"},{b:" ",e:"$",c:h}]}];return{aliases:["rb","gemspec","podspec","thor","irb"],k:i,c:g.concat(h)}});hljs.registerLanguage("nimrod",function(a){return{k:{keyword:"addr and as asm bind block break|0 case|0 cast const|0 continue|0 converter discard distinct|10 div do elif else|0 end|0 enum|0 except export finally for from generic if|0 import|0 in include|0 interface is isnot|10 iterator|10 let|0 macro method|10 mixin mod nil not notin|10 object|0 of or out proc|10 ptr raise ref|10 return shl shr static template|10 try|0 tuple type|0 using|0 var|0 when while|0 with without xor yield",literal:"shared guarded stdin stdout stderr result|10 true false"},c:[{cN:"decorator",b:/{\./,e:/\.}/,r:10},{cN:"string",b:/[a-zA-Z]\w*"/,e:/"/,c:[{b:/""/}]},{cN:"string",b:/([a-zA-Z]\w*)?"""/,e:/"""/},{cN:"string",b:/"/,e:/"/,i:/\n/,c:[{b:/\\./}]},{cN:"type",b:/\b[A-Z]\w+\b/,r:0},{cN:"type",b:/\b(int|int8|int16|int32|int64|uint|uint8|uint16|uint32|uint64|float|float32|float64|bool|char|string|cstring|pointer|expr|stmt|void|auto|any|range|array|openarray|varargs|seq|set|clong|culong|cchar|cschar|cshort|cint|csize|clonglong|cfloat|cdouble|clongdouble|cuchar|cushort|cuint|culonglong|cstringarray|semistatic)\b/},{cN:"number",b:/\b(0[xX][0-9a-fA-F][_0-9a-fA-F]*)('?[iIuU](8|16|32|64))?/,r:0},{cN:"number",b:/\b(0o[0-7][_0-7]*)('?[iIuUfF](8|16|32|64))?/,r:0},{cN:"number",b:/\b(0(b|B)[01][_01]*)('?[iIuUfF](8|16|32|64))?/,r:0},{cN:"number",b:/\b(\d[_\d]*)('?[iIuUfF](8|16|32|64))?/,r:0},a.HCM]}});hljs.registerLanguage("rust",function(a){return{aliases:["rs"],k:"alignof as be box break const continue crate do else enum extern false fn for if impl in let loop match mod mut offsetof once priv proc pub pure ref return self sizeof static struct super trait true type typeof unsafe unsized use virtual while yield int i8 i16 i32 i64 uint u8 u32 u64 float f32 f64 str char bool",i:""}]}});hljs.registerLanguage("ruleslanguage",function(a){return{k:{keyword:"BILL_PERIOD BILL_START BILL_STOP RS_EFFECTIVE_START RS_EFFECTIVE_STOP RS_JURIS_CODE RS_OPCO_CODE INTDADDATTRIBUTE|5 INTDADDVMSG|5 INTDBLOCKOP|5 INTDBLOCKOPNA|5 INTDCLOSE|5 INTDCOUNT|5 INTDCOUNTSTATUSCODE|5 INTDCREATEMASK|5 INTDCREATEDAYMASK|5 INTDCREATEFACTORMASK|5 INTDCREATEHANDLE|5 INTDCREATEOVERRIDEDAYMASK|5 INTDCREATEOVERRIDEMASK|5 INTDCREATESTATUSCODEMASK|5 INTDCREATETOUPERIOD|5 INTDDELETE|5 INTDDIPTEST|5 INTDEXPORT|5 INTDGETERRORCODE|5 INTDGETERRORMESSAGE|5 INTDISEQUAL|5 INTDJOIN|5 INTDLOAD|5 INTDLOADACTUALCUT|5 INTDLOADDATES|5 INTDLOADHIST|5 INTDLOADLIST|5 INTDLOADLISTDATES|5 INTDLOADLISTENERGY|5 INTDLOADLISTHIST|5 INTDLOADRELATEDCHANNEL|5 INTDLOADSP|5 INTDLOADSTAGING|5 INTDLOADUOM|5 INTDLOADUOMDATES|5 INTDLOADUOMHIST|5 INTDLOADVERSION|5 INTDOPEN|5 INTDREADFIRST|5 INTDREADNEXT|5 INTDRECCOUNT|5 INTDRELEASE|5 INTDREPLACE|5 INTDROLLAVG|5 INTDROLLPEAK|5 INTDSCALAROP|5 INTDSCALE|5 INTDSETATTRIBUTE|5 INTDSETDSTPARTICIPANT|5 INTDSETSTRING|5 INTDSETVALUE|5 INTDSETVALUESTATUS|5 INTDSHIFTSTARTTIME|5 INTDSMOOTH|5 INTDSORT|5 INTDSPIKETEST|5 INTDSUBSET|5 INTDTOU|5 INTDTOURELEASE|5 INTDTOUVALUE|5 INTDUPDATESTATS|5 INTDVALUE|5 STDEV INTDDELETEEX|5 INTDLOADEXACTUAL|5 INTDLOADEXCUT|5 INTDLOADEXDATES|5 INTDLOADEX|5 INTDLOADEXRELATEDCHANNEL|5 INTDSAVEEX|5 MVLOAD|5 MVLOADACCT|5 MVLOADACCTDATES|5 MVLOADACCTHIST|5 MVLOADDATES|5 MVLOADHIST|5 MVLOADLIST|5 MVLOADLISTDATES|5 MVLOADLISTHIST|5 IF FOR NEXT DONE SELECT END CALL ABORT CLEAR CHANNEL FACTOR LIST NUMBER OVERRIDE SET WEEK DISTRIBUTIONNODE ELSE WHEN THEN OTHERWISE IENUM CSV INCLUDE LEAVE RIDER SAVE DELETE NOVALUE SECTION WARN SAVE_UPDATE DETERMINANT LABEL REPORT REVENUE EACH IN FROM TOTAL CHARGE BLOCK AND OR CSV_FILE RATE_CODE AUXILIARY_DEMAND UIDACCOUNT RS BILL_PERIOD_SELECT HOURS_PER_MONTH INTD_ERROR_STOP SEASON_SCHEDULE_NAME ACCOUNTFACTOR ARRAYUPPERBOUND CALLSTOREDPROC GETADOCONNECTION GETCONNECT GETDATASOURCE GETQUALIFIER GETUSERID HASVALUE LISTCOUNT LISTOP LISTUPDATE LISTVALUE PRORATEFACTOR RSPRORATE SETBINPATH SETDBMONITOR WQ_OPEN BILLINGHOURS DATE DATEFROMFLOAT DATETIMEFROMSTRING DATETIMETOSTRING DATETOFLOAT DAY DAYDIFF DAYNAME DBDATETIME HOUR MINUTE MONTH MONTHDIFF MONTHHOURS MONTHNAME ROUNDDATE SAMEWEEKDAYLASTYEAR SECOND WEEKDAY WEEKDIFF YEAR YEARDAY YEARSTR COMPSUM HISTCOUNT HISTMAX HISTMIN HISTMINNZ HISTVALUE MAXNRANGE MAXRANGE MINRANGE COMPIKVA COMPKVA COMPKVARFROMKQKW COMPLF IDATTR FLAG LF2KW LF2KWH MAXKW POWERFACTOR READING2USAGE AVGSEASON MAXSEASON MONTHLYMERGE SEASONVALUE SUMSEASON ACCTREADDATES ACCTTABLELOAD CONFIGADD CONFIGGET CREATEOBJECT CREATEREPORT EMAILCLIENT EXPBLKMDMUSAGE EXPMDMUSAGE EXPORT_USAGE FACTORINEFFECT GETUSERSPECIFIEDSTOP INEFFECT ISHOLIDAY RUNRATE SAVE_PROFILE SETREPORTTITLE USEREXIT WATFORRUNRATE TO TABLE ACOS ASIN ATAN ATAN2 BITAND CEIL COS COSECANT COSH COTANGENT DIVQUOT DIVREM EXP FABS FLOOR FMOD FREPM FREXPN LOG LOG10 MAX MAXN MIN MINNZ MODF POW ROUND ROUND2VALUE ROUNDINT SECANT SIN SINH SQROOT TAN TANH FLOAT2STRING FLOAT2STRINGNC INSTR LEFT LEN LTRIM MID RIGHT RTRIM STRING STRINGNC TOLOWER TOUPPER TRIM NUMDAYS READ_DATE STAGING",built_in:"IDENTIFIER OPTIONS XML_ELEMENT XML_OP XML_ELEMENT_OF DOMDOCCREATE DOMDOCLOADFILE DOMDOCLOADXML DOMDOCSAVEFILE DOMDOCGETROOT DOMDOCADDPI DOMNODEGETNAME DOMNODEGETTYPE DOMNODEGETVALUE DOMNODEGETCHILDCT DOMNODEGETFIRSTCHILD DOMNODEGETSIBLING DOMNODECREATECHILDELEMENT DOMNODESETATTRIBUTE DOMNODEGETCHILDELEMENTCT DOMNODEGETFIRSTCHILDELEMENT DOMNODEGETSIBLINGELEMENT DOMNODEGETATTRIBUTECT DOMNODEGETATTRIBUTEI DOMNODEGETATTRIBUTEBYNAME DOMNODEGETBYNAME"},c:[a.CLCM,a.CBCM,a.ASM,a.QSM,a.CNM,{cN:"array",b:"#[a-zA-Z .]+"}]}});hljs.registerLanguage("rib",function(a){return{k:"ArchiveRecord AreaLightSource Atmosphere Attribute AttributeBegin AttributeEnd Basis Begin Blobby Bound Clipping ClippingPlane Color ColorSamples ConcatTransform Cone CoordinateSystem CoordSysTransform CropWindow Curves Cylinder DepthOfField Detail DetailRange Disk Displacement Display End ErrorHandler Exposure Exterior Format FrameAspectRatio FrameBegin FrameEnd GeneralPolygon GeometricApproximation Geometry Hider Hyperboloid Identity Illuminate Imager Interior LightSource MakeCubeFaceEnvironment MakeLatLongEnvironment MakeShadow MakeTexture Matte MotionBegin MotionEnd NuPatch ObjectBegin ObjectEnd ObjectInstance Opacity Option Orientation Paraboloid Patch PatchMesh Perspective PixelFilter PixelSamples PixelVariance Points PointsGeneralPolygons PointsPolygons Polygon Procedural Projection Quantize ReadArchive RelativeDetail ReverseOrientation Rotate Scale ScreenWindow ShadingInterpolation ShadingRate Shutter Sides Skew SolidBegin SolidEnd Sphere SubdivisionMesh Surface TextureCoordinates Torus Transform TransformBegin TransformEnd TransformPoints Translate TrimCurve WorldBegin WorldEnd",i:"",e:",\\s+",rB:true,eW:true,c:[{cN:"symbol",b:":\\w+"},{cN:"string",b:'"',e:'"'},{cN:"string",b:"'",e:"'"},{b:"\\w+",r:0}]}]},{b:"\\(\\s*",e:"\\s*\\)",eE:true,c:[{b:"\\w+\\s*=",e:"\\s+",rB:true,eW:true,c:[{cN:"attribute",b:"\\w+",r:0},{cN:"string",b:'"',e:'"'},{cN:"string",b:"'",e:"'"},{b:"\\w+",r:0}]}]}]},{cN:"bullet",b:"^\\s*[=~]\\s*",r:0},{b:"#{",starts:{e:"}",sL:"ruby"}}]}});hljs.registerLanguage("javascript",function(a){return{aliases:["js"],k:{keyword:"in if for while finally var new function do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document"},c:[{cN:"pi",b:/^\s*('|")use strict('|")/,r:10},a.ASM,a.QSM,a.CLCM,a.CBCM,a.CNM,{b:"("+a.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[a.CLCM,a.CBCM,a.RM,{b:/;/,r:0,sL:"xml"}],r:0},{cN:"function",bK:"function",e:/\{/,eE:true,c:[a.inherit(a.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,c:[a.CLCM,a.CBCM],i:/["'\(]/}],i:/\[|%/},{b:/\$[(.]/},{b:"\\."+a.IR,r:0}]}});hljs.registerLanguage("glsl",function(a){return{k:{keyword:"atomic_uint attribute bool break bvec2 bvec3 bvec4 case centroid coherent const continue default discard dmat2 dmat2x2 dmat2x3 dmat2x4 dmat3 dmat3x2 dmat3x3 dmat3x4 dmat4 dmat4x2 dmat4x3 dmat4x4 do double dvec2 dvec3 dvec4 else flat float for highp if iimage1D iimage1DArray iimage2D iimage2DArray iimage2DMS iimage2DMSArray iimage2DRect iimage3D iimageBuffer iimageCube iimageCubeArray image1D image1DArray image2D image2DArray image2DMS image2DMSArray image2DRect image3D imageBuffer imageCube imageCubeArray in inout int invariant isampler1D isampler1DArray isampler2D isampler2DArray isampler2DMS isampler2DMSArray isampler2DRect isampler3D isamplerBuffer isamplerCube isamplerCubeArray ivec2 ivec3 ivec4 layout lowp mat2 mat2x2 mat2x3 mat2x4 mat3 mat3x2 mat3x3 mat3x4 mat4 mat4x2 mat4x3 mat4x4 mediump noperspective out patch precision readonly restrict return sample sampler1D sampler1DArray sampler1DArrayShadow sampler1DShadow sampler2D sampler2DArray sampler2DArrayShadow sampler2DMS sampler2DMSArray sampler2DRect sampler2DRectShadow sampler2DShadow sampler3D samplerBuffer samplerCube samplerCubeArray samplerCubeArrayShadow samplerCubeShadow smooth struct subroutine switch uimage1D uimage1DArray uimage2D uimage2DArray uimage2DMS uimage2DMSArray uimage2DRect uimage3D uimageBuffer uimageCube uimageCubeArray uint uniform usampler1D usampler1DArray usampler2D usampler2DArray usampler2DMS usampler2DMSArray usampler2DRect usampler3D usamplerBuffer usamplerCube usamplerCubeArray uvec2 uvec3 uvec4 varying vec2 vec3 vec4 void volatile while writeonly",built_in:"gl_BackColor gl_BackLightModelProduct gl_BackLightProduct gl_BackMaterial gl_BackSecondaryColor gl_ClipDistance gl_ClipPlane gl_ClipVertex gl_Color gl_DepthRange gl_EyePlaneQ gl_EyePlaneR gl_EyePlaneS gl_EyePlaneT gl_Fog gl_FogCoord gl_FogFragCoord gl_FragColor gl_FragCoord gl_FragData gl_FragDepth gl_FrontColor gl_FrontFacing gl_FrontLightModelProduct gl_FrontLightProduct gl_FrontMaterial gl_FrontSecondaryColor gl_InstanceID gl_InvocationID gl_Layer gl_LightModel gl_LightSource gl_MaxAtomicCounterBindings gl_MaxAtomicCounterBufferSize gl_MaxClipDistances gl_MaxClipPlanes gl_MaxCombinedAtomicCounterBuffers gl_MaxCombinedAtomicCounters gl_MaxCombinedImageUniforms gl_MaxCombinedImageUnitsAndFragmentOutputs gl_MaxCombinedTextureImageUnits gl_MaxDrawBuffers gl_MaxFragmentAtomicCounterBuffers gl_MaxFragmentAtomicCounters gl_MaxFragmentImageUniforms gl_MaxFragmentInputComponents gl_MaxFragmentUniformComponents gl_MaxFragmentUniformVectors gl_MaxGeometryAtomicCounterBuffers gl_MaxGeometryAtomicCounters gl_MaxGeometryImageUniforms gl_MaxGeometryInputComponents gl_MaxGeometryOutputComponents gl_MaxGeometryOutputVertices gl_MaxGeometryTextureImageUnits gl_MaxGeometryTotalOutputComponents gl_MaxGeometryUniformComponents gl_MaxGeometryVaryingComponents gl_MaxImageSamples gl_MaxImageUnits gl_MaxLights gl_MaxPatchVertices gl_MaxProgramTexelOffset gl_MaxTessControlAtomicCounterBuffers gl_MaxTessControlAtomicCounters gl_MaxTessControlImageUniforms gl_MaxTessControlInputComponents gl_MaxTessControlOutputComponents gl_MaxTessControlTextureImageUnits gl_MaxTessControlTotalOutputComponents gl_MaxTessControlUniformComponents gl_MaxTessEvaluationAtomicCounterBuffers gl_MaxTessEvaluationAtomicCounters gl_MaxTessEvaluationImageUniforms gl_MaxTessEvaluationInputComponents gl_MaxTessEvaluationOutputComponents gl_MaxTessEvaluationTextureImageUnits gl_MaxTessEvaluationUniformComponents gl_MaxTessGenLevel gl_MaxTessPatchComponents gl_MaxTextureCoords gl_MaxTextureImageUnits gl_MaxTextureUnits gl_MaxVaryingComponents gl_MaxVaryingFloats gl_MaxVaryingVectors gl_MaxVertexAtomicCounterBuffers gl_MaxVertexAtomicCounters gl_MaxVertexAttribs gl_MaxVertexImageUniforms gl_MaxVertexOutputComponents gl_MaxVertexTextureImageUnits gl_MaxVertexUniformComponents gl_MaxVertexUniformVectors gl_MaxViewports gl_MinProgramTexelOffsetgl_ModelViewMatrix gl_ModelViewMatrixInverse gl_ModelViewMatrixInverseTranspose gl_ModelViewMatrixTranspose gl_ModelViewProjectionMatrix gl_ModelViewProjectionMatrixInverse gl_ModelViewProjectionMatrixInverseTranspose gl_ModelViewProjectionMatrixTranspose gl_MultiTexCoord0 gl_MultiTexCoord1 gl_MultiTexCoord2 gl_MultiTexCoord3 gl_MultiTexCoord4 gl_MultiTexCoord5 gl_MultiTexCoord6 gl_MultiTexCoord7 gl_Normal gl_NormalMatrix gl_NormalScale gl_ObjectPlaneQ gl_ObjectPlaneR gl_ObjectPlaneS gl_ObjectPlaneT gl_PatchVerticesIn gl_PerVertex gl_Point gl_PointCoord gl_PointSize gl_Position gl_PrimitiveID gl_PrimitiveIDIn gl_ProjectionMatrix gl_ProjectionMatrixInverse gl_ProjectionMatrixInverseTranspose gl_ProjectionMatrixTranspose gl_SampleID gl_SampleMask gl_SampleMaskIn gl_SamplePosition gl_SecondaryColor gl_TessCoord gl_TessLevelInner gl_TessLevelOuter gl_TexCoord gl_TextureEnvColor gl_TextureMatrixInverseTranspose gl_TextureMatrixTranspose gl_Vertex gl_VertexID gl_ViewportIndex gl_in gl_out EmitStreamVertex EmitVertex EndPrimitive EndStreamPrimitive abs acos acosh all any asin asinh atan atanh atomicCounter atomicCounterDecrement atomicCounterIncrement barrier bitCount bitfieldExtract bitfieldInsert bitfieldReverse ceil clamp cos cosh cross dFdx dFdy degrees determinant distance dot equal exp exp2 faceforward findLSB findMSB floatBitsToInt floatBitsToUint floor fma fract frexp ftransform fwidth greaterThan greaterThanEqual imageAtomicAdd imageAtomicAnd imageAtomicCompSwap imageAtomicExchange imageAtomicMax imageAtomicMin imageAtomicOr imageAtomicXor imageLoad imageStore imulExtended intBitsToFloat interpolateAtCentroid interpolateAtOffset interpolateAtSample inverse inversesqrt isinf isnan ldexp length lessThan lessThanEqual log log2 matrixCompMult max memoryBarrier min mix mod modf noise1 noise2 noise3 noise4 normalize not notEqual outerProduct packDouble2x32 packHalf2x16 packSnorm2x16 packSnorm4x8 packUnorm2x16 packUnorm4x8 pow radians reflect refract round roundEven shadow1D shadow1DLod shadow1DProj shadow1DProjLod shadow2D shadow2DLod shadow2DProj shadow2DProjLod sign sin sinh smoothstep sqrt step tan tanh texelFetch texelFetchOffset texture texture1D texture1DLod texture1DProj texture1DProjLod texture2D texture2DLod texture2DProj texture2DProjLod texture3D texture3DLod texture3DProj texture3DProjLod textureCube textureCubeLod textureGather textureGatherOffset textureGatherOffsets textureGrad textureGradOffset textureLod textureLodOffset textureOffset textureProj textureProjGrad textureProjGradOffset textureProjLod textureProjLodOffset textureProjOffset textureQueryLod textureSize transpose trunc uaddCarry uintBitsToFloat umulExtended unpackDouble2x32 unpackHalf2x16 unpackSnorm2x16 unpackSnorm4x8 unpackUnorm2x16 unpackUnorm4x8 usubBorrow gl_TextureMatrix gl_TextureMatrixInverse",literal:"true false"},i:'"',c:[a.CLCM,a.CBCM,a.CNM,{cN:"preprocessor",b:"#",e:"$"}]}});hljs.registerLanguage("rsl",function(a){return{k:{keyword:"float color point normal vector matrix while for if do return else break extern continue",built_in:"abs acos ambient area asin atan atmosphere attribute calculatenormal ceil cellnoise clamp comp concat cos degrees depth Deriv diffuse distance Du Dv environment exp faceforward filterstep floor format fresnel incident length lightsource log match max min mod noise normalize ntransform opposite option phong pnoise pow printf ptlined radians random reflect refract renderinfo round setcomp setxcomp setycomp setzcomp shadow sign sin smoothstep specular specularbrdf spline sqrt step tan texture textureinfo trace transform vtransform xcomp ycomp zcomp"},i:"/,sL:"php",subLanguageMode:"continuous"};var b={eW:true,i:/]+/}]}]}]};return{aliases:["html","xhtml","rss","atom","xsl","plist"],cI:true,c:[{cN:"doctype",b:"",r:10,c:[{b:"\\[",e:"\\]"}]},{cN:"comment",b:"",r:10},{cN:"cdata",b:"<\\!\\[CDATA\\[",e:"\\]\\]>",r:10},{cN:"tag",b:"|$)",e:">",k:{title:"style"},c:[b],starts:{e:"",rE:true,sL:"css"}},{cN:"tag",b:"|$)",e:">",k:{title:"script"},c:[b],starts:{e:"<\/script>",rE:true,sL:"javascript"}},{b:"<%",e:"%>",sL:"vbscript"},d,{cN:"pi",b:/<\?\w+/,e:/\?>/,r:10},{cN:"tag",b:"",c:[{cN:"title",b:"[^ /><]+",r:0},b]}]}});hljs.registerLanguage("markdown",function(a){return{aliases:["md","mkdown","mkd"],c:[{cN:"header",v:[{b:"^#{1,6}",e:"$"},{b:"^.+?\\n[=-]{2,}$"}]},{b:"<",e:">",sL:"xml",r:0},{cN:"bullet",b:"^([*+-]|(\\d+\\.))\\s+"},{cN:"strong",b:"[*_]{2}.+?[*_]{2}"},{cN:"emphasis",v:[{b:"\\*.+?\\*"},{b:"_.+?_",r:0}]},{cN:"blockquote",b:"^>\\s+",e:"$"},{cN:"code",v:[{b:"`.+?`"},{b:"^( {4}|\t)",e:"$",r:0}]},{cN:"horizontal_rule",b:"^[-\\*]{3,}",e:"$"},{b:"\\[.+?\\][\\(\\[].+?[\\)\\]]",rB:true,c:[{cN:"link_label",b:"\\[",e:"\\]",eB:true,rE:true,r:0},{cN:"link_url",b:"\\]\\(",e:"\\)",eB:true,eE:true},{cN:"link_reference",b:"\\]\\[",e:"\\]",eB:true,eE:true}],r:10},{b:"^\\[.+\\]:",e:"$",rB:true,c:[{cN:"link_reference",b:"\\[",e:"\\]",eB:true,eE:true},{cN:"link_url",b:"\\s",e:"$"}]}]}});hljs.registerLanguage("css",function(a){var b="[a-zA-Z-][a-zA-Z0-9_-]*";var c={cN:"function",b:b+"\\(",rB:true,eE:true,e:"\\("};return{cI:true,i:"[=/|']",c:[a.CBCM,{cN:"id",b:"\\#[A-Za-z0-9_-]+"},{cN:"class",b:"\\.[A-Za-z0-9_-]+",r:0},{cN:"attr_selector",b:"\\[",e:"\\]",i:"$"},{cN:"pseudo",b:":(:)?[a-zA-Z0-9\\_\\-\\+\\(\\)\\\"\\']+"},{cN:"at_rule",b:"@(font-face|page)",l:"[a-z-]+",k:"font-face page"},{cN:"at_rule",b:"@",e:"[{;]",c:[{cN:"keyword",b:/\S+/},{b:/\s/,eW:true,eE:true,r:0,c:[c,a.ASM,a.QSM,a.CSSNM]}]},{cN:"tag",b:b,r:0},{cN:"rules",b:"{",e:"}",i:"[^\\s]",r:0,c:[a.CBCM,{cN:"rule",b:"[^\\s]",rB:true,e:";",eW:true,c:[{cN:"attribute",b:"[A-Z\\_\\.\\-]+",e:":",eE:true,i:"[^\\s]",starts:{cN:"value",eW:true,eE:true,c:[c,a.CSSNM,a.QSM,a.ASM,a.CBCM,{cN:"hexcolor",b:"#[0-9A-Fa-f]+"},{cN:"important",b:"!important"}]}}]}]}]}});hljs.registerLanguage("capnproto",function(a){return{aliases:["capnp"],k:{keyword:"struct enum interface union group import using const annotation extends in of on as with from fixed",built_in:"Void Bool Int8 Int16 Int32 Int64 UInt8 UInt16 UInt32 UInt64 Float32 Float64 Text Data AnyPointer AnyStruct Capability List",literal:"true false"},c:[a.QSM,a.NM,a.HCM,{cN:"shebang",b:/@0x[\w\d]{16};/,i:/\n/},{cN:"number",b:/@\d+\b/},{cN:"class",bK:"struct enum",e:/\{/,i:/\n/,c:[a.inherit(a.TM,{starts:{eW:true,eE:true}})]},{cN:"class",bK:"interface",e:/\{/,i:/\n/,c:[a.inherit(a.TM,{starts:{eW:true,eE:true}})]}]}});hljs.registerLanguage("lisp",function(i){var l="[a-zA-Z_\\-\\+\\*\\/\\<\\=\\>\\&\\#][a-zA-Z0-9_\\-\\+\\*\\/\\<\\=\\>\\&\\#!]*";var m="(\\-|\\+)?\\d+(\\.\\d+|\\/\\d+)?((d|e|f|l|s)(\\+|\\-)?\\d+)?";var k={cN:"shebang",b:"^#!",e:"$"};var b={cN:"literal",b:"\\b(t{1}|nil)\\b"};var e={cN:"number",v:[{b:m,r:0},{b:"#b[0-1]+(/[0-1]+)?"},{b:"#o[0-7]+(/[0-7]+)?"},{b:"#x[0-9a-f]+(/[0-9a-f]+)?"},{b:"#c\\("+m+" +"+m,e:"\\)"}]};var h=i.inherit(i.QSM,{i:null});var n={cN:"comment",b:";",e:"$"};var g={cN:"variable",b:"\\*",e:"\\*"};var o={cN:"keyword",b:"[:&]"+l};var d={b:"\\(",e:"\\)",c:["self",b,h,e]};var a={cN:"quoted",c:[e,h,g,o,d],v:[{b:"['`]\\(",e:"\\)"},{b:"\\(quote ",e:"\\)",k:{title:"quote"}}]};var c={cN:"quoted",b:"'"+l};var j={cN:"list",b:"\\(",e:"\\)"};var f={eW:true,r:0};j.c=[{cN:"title",b:l},f];f.c=[a,c,j,b,e,h,n,g,o];return{i:/\S/,c:[e,k,b,h,n,a,c,j]}});hljs.registerLanguage("profile",function(a){return{c:[a.CNM,{cN:"built_in",b:"{",e:"}$",eB:true,eE:true,c:[a.ASM,a.QSM],r:0},{cN:"filename",b:"[a-zA-Z_][\\da-zA-Z_]+\\.[\\da-zA-Z_]{1,3}",e:":",eE:true},{cN:"header",b:"(ncalls|tottime|cumtime)",e:"$",k:"ncalls tottime|10 cumtime|10 filename",r:10},{cN:"summary",b:"function calls",e:"$",c:[a.CNM],r:10},a.ASM,a.QSM,{cN:"function",b:"\\(",e:"\\)$",c:[a.UTM],r:0}]}});hljs.registerLanguage("http",function(a){return{i:"\\S",c:[{cN:"status",b:"^HTTP/[0-9\\.]+",e:"$",c:[{cN:"number",b:"\\b\\d{3}\\b"}]},{cN:"request",b:"^[A-Z]+ (.*?) HTTP/[0-9\\.]+$",rB:true,e:"$",c:[{cN:"string",b:" ",e:" ",eB:true,eE:true}]},{cN:"attribute",b:"^\\w",e:": ",eE:true,i:"\\n|\\s|=",starts:{cN:"string",e:"$"}},{b:"\\n\\n",starts:{sL:"",eW:true}}]}});hljs.registerLanguage("java",function(b){var a="false synchronized int abstract float private char boolean static null if const for true while long throw strictfp finally protected import native final return void enum else break transient new catch instanceof byte super volatile case assert short package default double public try this switch continue throws";return{aliases:["jsp"],k:a,i:/<\//,c:[{cN:"javadoc",b:"/\\*\\*",e:"\\*/",c:[{cN:"javadoctag",b:"(^|\\s)@[A-Za-z]+"}],r:10},b.CLCM,b.CBCM,b.ASM,b.QSM,{bK:"protected public private",e:/[{;=]/,k:a,c:[{cN:"class",bK:"class interface",eW:true,eE:true,i:/[:"\[\]]/,c:[{bK:"extends implements",r:10},b.UTM]},{b:b.UIR+"\\s*\\(",rB:true,c:[b.UTM]}]},b.CNM,{cN:"annotation",b:"@[A-Za-z]+"}]}});hljs.registerLanguage("gherkin",function(a){return{k:"Feature Background Ability Business Need Scenario Scenarios Scenario Outline Scenario Template Examples Given And Then But When",c:[{cN:"keyword",b:"\\*"},{cN:"comment",b:"@[^@\r\n\t ]+",e:"$"},{cN:"string",b:"\\|",e:"\\$"},{cN:"variable",b:"<",e:">",},a.HCM,{cN:"string",b:'"""',e:'"""'},a.QSM]}});hljs.registerLanguage("fsharp",function(a){return{aliases:["fs"],k:"abstract and as assert base begin class default delegate do done downcast downto elif else end exception extern false finally for fun function global if in inherit inline interface internal lazy let match member module mutable namespace new null of open or override private public rec return sig static struct then to true try type upcast use val void when while with yield",c:[{cN:"string",b:'@"',e:'"',c:[{b:'""'}]},{cN:"string",b:'"""',e:'"""'},{cN:"comment",b:"\\(\\*",e:"\\*\\)"},{cN:"class",bK:"type",e:"\\(|=|$",eE:true,c:[a.UTM]},{cN:"annotation",b:"\\[<",e:">\\]"},{cN:"attribute",b:"\\B('[A-Za-z])\\b",c:[a.BE]},a.CLCM,a.inherit(a.QSM,{i:null}),a.CNM]}});hljs.registerLanguage("mathematica",function(a){return{aliases:["mma"],l:"(\\$|\\b)"+a.IR+"\\b",k:"AbelianGroup Abort AbortKernels AbortProtect Above Abs Absolute AbsoluteCorrelation AbsoluteCorrelationFunction AbsoluteCurrentValue AbsoluteDashing AbsoluteFileName AbsoluteOptions AbsolutePointSize AbsoluteThickness AbsoluteTime AbsoluteTiming AccountingForm Accumulate Accuracy AccuracyGoal ActionDelay ActionMenu ActionMenuBox ActionMenuBoxOptions Active ActiveItem ActiveStyle AcyclicGraphQ AddOnHelpPath AddTo AdjacencyGraph AdjacencyList AdjacencyMatrix AdjustmentBox AdjustmentBoxOptions AdjustTimeSeriesForecast AffineTransform After AiryAi AiryAiPrime AiryAiZero AiryBi AiryBiPrime AiryBiZero AlgebraicIntegerQ AlgebraicNumber AlgebraicNumberDenominator AlgebraicNumberNorm AlgebraicNumberPolynomial AlgebraicNumberTrace AlgebraicRules AlgebraicRulesData Algebraics AlgebraicUnitQ Alignment AlignmentMarker AlignmentPoint All AllowedDimensions AllowGroupClose AllowInlineCells AllowKernelInitialization AllowReverseGroupClose AllowScriptLevelChange AlphaChannel AlternatingGroup AlternativeHypothesis Alternatives AmbientLight Analytic AnchoredSearch And AndersonDarlingTest AngerJ AngleBracket AngularGauge Animate AnimationCycleOffset AnimationCycleRepetitions AnimationDirection AnimationDisplayTime AnimationRate AnimationRepetitions AnimationRunning Animator AnimatorBox AnimatorBoxOptions AnimatorElements Annotation Annuity AnnuityDue Antialiasing Antisymmetric Apart ApartSquareFree Appearance AppearanceElements AppellF1 Append AppendTo Apply ArcCos ArcCosh ArcCot ArcCoth ArcCsc ArcCsch ArcSec ArcSech ArcSin ArcSinDistribution ArcSinh ArcTan ArcTanh Arg ArgMax ArgMin ArgumentCountQ ARIMAProcess ArithmeticGeometricMean ARMAProcess ARProcess Array ArrayComponents ArrayDepth ArrayFlatten ArrayPad ArrayPlot ArrayQ ArrayReshape ArrayRules Arrays Arrow Arrow3DBox ArrowBox Arrowheads AspectRatio AspectRatioFixed Assert Assuming Assumptions AstronomicalData Asynchronous AsynchronousTaskObject AsynchronousTasks AtomQ Attributes AugmentedSymmetricPolynomial AutoAction AutoDelete AutoEvaluateEvents AutoGeneratedPackage AutoIndent AutoIndentSpacings AutoItalicWords AutoloadPath AutoMatch Automatic AutomaticImageSize AutoMultiplicationSymbol AutoNumberFormatting AutoOpenNotebooks AutoOpenPalettes AutorunSequencing AutoScaling AutoScroll AutoSpacing AutoStyleOptions AutoStyleWords Axes AxesEdge AxesLabel AxesOrigin AxesStyle Axis BabyMonsterGroupB Back Background BackgroundTasksSettings Backslash Backsubstitution Backward Band BandpassFilter BandstopFilter BarabasiAlbertGraphDistribution BarChart BarChart3D BarLegend BarlowProschanImportance BarnesG BarOrigin BarSpacing BartlettHannWindow BartlettWindow BaseForm Baseline BaselinePosition BaseStyle BatesDistribution BattleLemarieWavelet Because BeckmannDistribution Beep Before Begin BeginDialogPacket BeginFrontEndInteractionPacket BeginPackage BellB BellY Below BenfordDistribution BeniniDistribution BenktanderGibratDistribution BenktanderWeibullDistribution BernoulliB BernoulliDistribution BernoulliGraphDistribution BernoulliProcess BernsteinBasis BesselFilterModel BesselI BesselJ BesselJZero BesselK BesselY BesselYZero Beta BetaBinomialDistribution BetaDistribution BetaNegativeBinomialDistribution BetaPrimeDistribution BetaRegularized BetweennessCentrality BezierCurve BezierCurve3DBox BezierCurve3DBoxOptions BezierCurveBox BezierCurveBoxOptions BezierFunction BilateralFilter Binarize BinaryFormat BinaryImageQ BinaryRead BinaryReadList BinaryWrite BinCounts BinLists Binomial BinomialDistribution BinomialProcess BinormalDistribution BiorthogonalSplineWavelet BipartiteGraphQ BirnbaumImportance BirnbaumSaundersDistribution BitAnd BitClear BitGet BitLength BitNot BitOr BitSet BitShiftLeft BitShiftRight BitXor Black BlackmanHarrisWindow BlackmanNuttallWindow BlackmanWindow Blank BlankForm BlankNullSequence BlankSequence Blend Block BlockRandom BlomqvistBeta BlomqvistBetaTest Blue Blur BodePlot BohmanWindow Bold Bookmarks Boole BooleanConsecutiveFunction BooleanConvert BooleanCountingFunction BooleanFunction BooleanGraph BooleanMaxterms BooleanMinimize BooleanMinterms Booleans BooleanTable BooleanVariables BorderDimensions BorelTannerDistribution Bottom BottomHatTransform BoundaryStyle Bounds Box BoxBaselineShift BoxData BoxDimensions Boxed Boxes BoxForm BoxFormFormatTypes BoxFrame BoxID BoxMargins BoxMatrix BoxRatios BoxRotation BoxRotationPoint BoxStyle BoxWhiskerChart Bra BracketingBar BraKet BrayCurtisDistance BreadthFirstScan Break Brown BrownForsytheTest BrownianBridgeProcess BrowserCategory BSplineBasis BSplineCurve BSplineCurve3DBox BSplineCurveBox BSplineCurveBoxOptions BSplineFunction BSplineSurface BSplineSurface3DBox BubbleChart BubbleChart3D BubbleScale BubbleSizes BulletGauge BusinessDayQ ButterflyGraph ButterworthFilterModel Button ButtonBar ButtonBox ButtonBoxOptions ButtonCell ButtonContents ButtonData ButtonEvaluator ButtonExpandable ButtonFrame ButtonFunction ButtonMargins ButtonMinHeight ButtonNote ButtonNotebook ButtonSource ButtonStyle ButtonStyleMenuListing Byte ByteCount ByteOrdering C CachedValue CacheGraphics CalendarData CalendarType CallPacket CanberraDistance Cancel CancelButton CandlestickChart Cap CapForm CapitalDifferentialD CardinalBSplineBasis CarmichaelLambda Cases Cashflow Casoratian Catalan CatalanNumber Catch CauchyDistribution CauchyWindow CayleyGraph CDF CDFDeploy CDFInformation CDFWavelet Ceiling Cell CellAutoOverwrite CellBaseline CellBoundingBox CellBracketOptions CellChangeTimes CellContents CellContext CellDingbat CellDynamicExpression CellEditDuplicate CellElementsBoundingBox CellElementSpacings CellEpilog CellEvaluationDuplicate CellEvaluationFunction CellEventActions CellFrame CellFrameColor CellFrameLabelMargins CellFrameLabels CellFrameMargins CellGroup CellGroupData CellGrouping CellGroupingRules CellHorizontalScrolling CellID CellLabel CellLabelAutoDelete CellLabelMargins CellLabelPositioning CellMargins CellObject CellOpen CellPrint CellProlog Cells CellSize CellStyle CellTags CellularAutomaton CensoredDistribution Censoring Center CenterDot CentralMoment CentralMomentGeneratingFunction CForm ChampernowneNumber ChanVeseBinarize Character CharacterEncoding CharacterEncodingsPath CharacteristicFunction CharacteristicPolynomial CharacterRange Characters ChartBaseStyle ChartElementData ChartElementDataFunction ChartElementFunction ChartElements ChartLabels ChartLayout ChartLegends ChartStyle Chebyshev1FilterModel Chebyshev2FilterModel ChebyshevDistance ChebyshevT ChebyshevU Check CheckAbort CheckAll Checkbox CheckboxBar CheckboxBox CheckboxBoxOptions ChemicalData ChessboardDistance ChiDistribution ChineseRemainder ChiSquareDistribution ChoiceButtons ChoiceDialog CholeskyDecomposition Chop Circle CircleBox CircleDot CircleMinus CirclePlus CircleTimes CirculantGraph CityData Clear ClearAll ClearAttributes ClearSystemCache ClebschGordan ClickPane Clip ClipboardNotebook ClipFill ClippingStyle ClipPlanes ClipRange Clock ClockGauge ClockwiseContourIntegral Close Closed CloseKernels ClosenessCentrality Closing ClosingAutoSave ClosingEvent ClusteringComponents CMYKColor Coarse Coefficient CoefficientArrays CoefficientDomain CoefficientList CoefficientRules CoifletWavelet Collect Colon ColonForm ColorCombine ColorConvert ColorData ColorDataFunction ColorFunction ColorFunctionScaling Colorize ColorNegate ColorOutput ColorProfileData ColorQuantize ColorReplace ColorRules ColorSelectorSettings ColorSeparate ColorSetter ColorSetterBox ColorSetterBoxOptions ColorSlider ColorSpace Column ColumnAlignments ColumnBackgrounds ColumnForm ColumnLines ColumnsEqual ColumnSpacings ColumnWidths CommonDefaultFormatTypes Commonest CommonestFilter CommonUnits CommunityBoundaryStyle CommunityGraphPlot CommunityLabels CommunityRegionStyle CompatibleUnitQ CompilationOptions CompilationTarget Compile Compiled CompiledFunction Complement CompleteGraph CompleteGraphQ CompleteKaryTree CompletionsListPacket Complex Complexes ComplexExpand ComplexInfinity ComplexityFunction ComponentMeasurements ComponentwiseContextMenu Compose ComposeList ComposeSeries Composition CompoundExpression CompoundPoissonDistribution CompoundPoissonProcess CompoundRenewalProcess Compress CompressedData Condition ConditionalExpression Conditioned Cone ConeBox ConfidenceLevel ConfidenceRange ConfidenceTransform ConfigurationPath Congruent Conjugate ConjugateTranspose Conjunction Connect ConnectedComponents ConnectedGraphQ ConnesWindow ConoverTest ConsoleMessage ConsoleMessagePacket ConsolePrint Constant ConstantArray Constants ConstrainedMax ConstrainedMin ContentPadding ContentsBoundingBox ContentSelectable ContentSize Context ContextMenu Contexts ContextToFilename ContextToFileName Continuation Continue ContinuedFraction ContinuedFractionK ContinuousAction ContinuousMarkovProcess ContinuousTimeModelQ ContinuousWaveletData ContinuousWaveletTransform ContourDetect ContourGraphics ContourIntegral ContourLabels ContourLines ContourPlot ContourPlot3D Contours ContourShading ContourSmoothing ContourStyle ContraharmonicMean Control ControlActive ControlAlignment ControllabilityGramian ControllabilityMatrix ControllableDecomposition ControllableModelQ ControllerDuration ControllerInformation ControllerInformationData ControllerLinking ControllerManipulate ControllerMethod ControllerPath ControllerState ControlPlacement ControlsRendering ControlType Convergents ConversionOptions ConversionRules ConvertToBitmapPacket ConvertToPostScript ConvertToPostScriptPacket Convolve ConwayGroupCo1 ConwayGroupCo2 ConwayGroupCo3 CoordinateChartData CoordinatesToolOptions CoordinateTransform CoordinateTransformData CoprimeQ Coproduct CopulaDistribution Copyable CopyDirectory CopyFile CopyTag CopyToClipboard CornerFilter CornerNeighbors Correlation CorrelationDistance CorrelationFunction CorrelationTest Cos Cosh CoshIntegral CosineDistance CosineWindow CosIntegral Cot Coth Count CounterAssignments CounterBox CounterBoxOptions CounterClockwiseContourIntegral CounterEvaluator CounterFunction CounterIncrements CounterStyle CounterStyleMenuListing CountRoots CountryData Covariance CovarianceEstimatorFunction CovarianceFunction CoxianDistribution CoxIngersollRossProcess CoxModel CoxModelFit CramerVonMisesTest CreateArchive CreateDialog CreateDirectory CreateDocument CreateIntermediateDirectories CreatePalette CreatePalettePacket CreateScheduledTask CreateTemporary CreateWindow CriticalityFailureImportance CriticalitySuccessImportance CriticalSection Cross CrossingDetect CrossMatrix Csc Csch CubeRoot Cubics Cuboid CuboidBox Cumulant CumulantGeneratingFunction Cup CupCap Curl CurlyDoubleQuote CurlyQuote CurrentImage CurrentlySpeakingPacket CurrentValue CurvatureFlowFilter CurveClosed Cyan CycleGraph CycleIndexPolynomial Cycles CyclicGroup Cyclotomic Cylinder CylinderBox CylindricalDecomposition D DagumDistribution DamerauLevenshteinDistance DampingFactor Darker Dashed Dashing DataCompression DataDistribution DataRange DataReversed Date DateDelimiters DateDifference DateFunction DateList DateListLogPlot DateListPlot DatePattern DatePlus DateRange DateString DateTicksFormat DaubechiesWavelet DavisDistribution DawsonF DayCount DayCountConvention DayMatchQ DayName DayPlus DayRange DayRound DeBruijnGraph Debug DebugTag Decimal DeclareKnownSymbols DeclarePackage Decompose Decrement DedekindEta Default DefaultAxesStyle DefaultBaseStyle DefaultBoxStyle DefaultButton DefaultColor DefaultControlPlacement DefaultDuplicateCellStyle DefaultDuration DefaultElement DefaultFaceGridsStyle DefaultFieldHintStyle DefaultFont DefaultFontProperties DefaultFormatType DefaultFormatTypeForStyle DefaultFrameStyle DefaultFrameTicksStyle DefaultGridLinesStyle DefaultInlineFormatType DefaultInputFormatType DefaultLabelStyle DefaultMenuStyle DefaultNaturalLanguage DefaultNewCellStyle DefaultNewInlineCellStyle DefaultNotebook DefaultOptions DefaultOutputFormatType DefaultStyle DefaultStyleDefinitions DefaultTextFormatType DefaultTextInlineFormatType DefaultTicksStyle DefaultTooltipStyle DefaultValues Defer DefineExternal DefineInputStreamMethod DefineOutputStreamMethod Definition Degree DegreeCentrality DegreeGraphDistribution DegreeLexicographic DegreeReverseLexicographic Deinitialization Del Deletable Delete DeleteBorderComponents DeleteCases DeleteContents DeleteDirectory DeleteDuplicates DeleteFile DeleteSmallComponents DeleteWithContents DeletionWarning Delimiter DelimiterFlashTime DelimiterMatching Delimiters Denominator DensityGraphics DensityHistogram DensityPlot DependentVariables Deploy Deployed Depth DepthFirstScan Derivative DerivativeFilter DescriptorStateSpace DesignMatrix Det DGaussianWavelet DiacriticalPositioning Diagonal DiagonalMatrix Dialog DialogIndent DialogInput DialogLevel DialogNotebook DialogProlog DialogReturn DialogSymbols Diamond DiamondMatrix DiceDissimilarity DictionaryLookup DifferenceDelta DifferenceOrder DifferenceRoot DifferenceRootReduce Differences DifferentialD DifferentialRoot DifferentialRootReduce DifferentiatorFilter DigitBlock DigitBlockMinimum DigitCharacter DigitCount DigitQ DihedralGroup Dilation Dimensions DiracComb DiracDelta DirectedEdge DirectedEdges DirectedGraph DirectedGraphQ DirectedInfinity Direction Directive Directory DirectoryName DirectoryQ DirectoryStack DirichletCharacter DirichletConvolve DirichletDistribution DirichletL DirichletTransform DirichletWindow DisableConsolePrintPacket DiscreteChirpZTransform DiscreteConvolve DiscreteDelta DiscreteHadamardTransform DiscreteIndicator DiscreteLQEstimatorGains DiscreteLQRegulatorGains DiscreteLyapunovSolve DiscreteMarkovProcess DiscretePlot DiscretePlot3D DiscreteRatio DiscreteRiccatiSolve DiscreteShift DiscreteTimeModelQ DiscreteUniformDistribution DiscreteVariables DiscreteWaveletData DiscreteWaveletPacketTransform DiscreteWaveletTransform Discriminant Disjunction Disk DiskBox DiskMatrix Dispatch DispersionEstimatorFunction Display DisplayAllSteps DisplayEndPacket DisplayFlushImagePacket DisplayForm DisplayFunction DisplayPacket DisplayRules DisplaySetSizePacket DisplayString DisplayTemporary DisplayWith DisplayWithRef DisplayWithVariable DistanceFunction DistanceTransform Distribute Distributed DistributedContexts DistributeDefinitions DistributionChart DistributionDomain DistributionFitTest DistributionParameterAssumptions DistributionParameterQ Dithering Div Divergence Divide DivideBy Dividers Divisible Divisors DivisorSigma DivisorSum DMSList DMSString Do DockedCells DocumentNotebook DominantColors DOSTextFormat Dot DotDashed DotEqual Dotted DoubleBracketingBar DoubleContourIntegral DoubleDownArrow DoubleLeftArrow DoubleLeftRightArrow DoubleLeftTee DoubleLongLeftArrow DoubleLongLeftRightArrow DoubleLongRightArrow DoubleRightArrow DoubleRightTee DoubleUpArrow DoubleUpDownArrow DoubleVerticalBar DoublyInfinite Down DownArrow DownArrowBar DownArrowUpArrow DownLeftRightVector DownLeftTeeVector DownLeftVector DownLeftVectorBar DownRightTeeVector DownRightVector DownRightVectorBar Downsample DownTee DownTeeArrow DownValues DragAndDrop DrawEdges DrawFrontFaces DrawHighlighted Drop DSolve Dt DualLinearProgramming DualSystemsModel DumpGet DumpSave DuplicateFreeQ Dynamic DynamicBox DynamicBoxOptions DynamicEvaluationTimeout DynamicLocation DynamicModule DynamicModuleBox DynamicModuleBoxOptions DynamicModuleParent DynamicModuleValues DynamicName DynamicNamespace DynamicReference DynamicSetting DynamicUpdating DynamicWrapper DynamicWrapperBox DynamicWrapperBoxOptions E EccentricityCentrality EdgeAdd EdgeBetweennessCentrality EdgeCapacity EdgeCapForm EdgeColor EdgeConnectivity EdgeCost EdgeCount EdgeCoverQ EdgeDashing EdgeDelete EdgeDetect EdgeForm EdgeIndex EdgeJoinForm EdgeLabeling EdgeLabels EdgeLabelStyle EdgeList EdgeOpacity EdgeQ EdgeRenderingFunction EdgeRules EdgeShapeFunction EdgeStyle EdgeThickness EdgeWeight Editable EditButtonSettings EditCellTagsSettings EditDistance EffectiveInterest Eigensystem Eigenvalues EigenvectorCentrality Eigenvectors Element ElementData Eliminate EliminationOrder EllipticE EllipticExp EllipticExpPrime EllipticF EllipticFilterModel EllipticK EllipticLog EllipticNomeQ EllipticPi EllipticReducedHalfPeriods EllipticTheta EllipticThetaPrime EmitSound EmphasizeSyntaxErrors EmpiricalDistribution Empty EmptyGraphQ EnableConsolePrintPacket Enabled Encode End EndAdd EndDialogPacket EndFrontEndInteractionPacket EndOfFile EndOfLine EndOfString EndPackage EngineeringForm Enter EnterExpressionPacket EnterTextPacket Entropy EntropyFilter Environment Epilog Equal EqualColumns EqualRows EqualTilde EquatedTo Equilibrium EquirippleFilterKernel Equivalent Erf Erfc Erfi ErlangB ErlangC ErlangDistribution Erosion ErrorBox ErrorBoxOptions ErrorNorm ErrorPacket ErrorsDialogSettings EstimatedDistribution EstimatedProcess EstimatorGains EstimatorRegulator EuclideanDistance EulerE EulerGamma EulerianGraphQ EulerPhi Evaluatable Evaluate Evaluated EvaluatePacket EvaluationCell EvaluationCompletionAction EvaluationElements EvaluationMode EvaluationMonitor EvaluationNotebook EvaluationObject EvaluationOrder Evaluator EvaluatorNames EvenQ EventData EventEvaluator EventHandler EventHandlerTag EventLabels ExactBlackmanWindow ExactNumberQ ExactRootIsolation ExampleData Except ExcludedForms ExcludePods Exclusions ExclusionsStyle Exists Exit ExitDialog Exp Expand ExpandAll ExpandDenominator ExpandFileName ExpandNumerator Expectation ExpectationE ExpectedValue ExpGammaDistribution ExpIntegralE ExpIntegralEi Exponent ExponentFunction ExponentialDistribution ExponentialFamily ExponentialGeneratingFunction ExponentialMovingAverage ExponentialPowerDistribution ExponentPosition ExponentStep Export ExportAutoReplacements ExportPacket ExportString Expression ExpressionCell ExpressionPacket ExpToTrig ExtendedGCD Extension ExtentElementFunction ExtentMarkers ExtentSize ExternalCall ExternalDataCharacterEncoding Extract ExtractArchive ExtremeValueDistribution FaceForm FaceGrids FaceGridsStyle Factor FactorComplete Factorial Factorial2 FactorialMoment FactorialMomentGeneratingFunction FactorialPower FactorInteger FactorList FactorSquareFree FactorSquareFreeList FactorTerms FactorTermsList Fail FailureDistribution False FARIMAProcess FEDisableConsolePrintPacket FeedbackSector FeedbackSectorStyle FeedbackType FEEnableConsolePrintPacket Fibonacci FieldHint FieldHintStyle FieldMasked FieldSize File FileBaseName FileByteCount FileDate FileExistsQ FileExtension FileFormat FileHash FileInformation FileName FileNameDepth FileNameDialogSettings FileNameDrop FileNameJoin FileNames FileNameSetter FileNameSplit FileNameTake FilePrint FileType FilledCurve FilledCurveBox Filling FillingStyle FillingTransform FilterRules FinancialBond FinancialData FinancialDerivative FinancialIndicator Find FindArgMax FindArgMin FindClique FindClusters FindCurvePath FindDistributionParameters FindDivisions FindEdgeCover FindEdgeCut FindEulerianCycle FindFaces FindFile FindFit FindGeneratingFunction FindGeoLocation FindGeometricTransform FindGraphCommunities FindGraphIsomorphism FindGraphPartition FindHamiltonianCycle FindIndependentEdgeSet FindIndependentVertexSet FindInstance FindIntegerNullVector FindKClan FindKClique FindKClub FindKPlex FindLibrary FindLinearRecurrence FindList FindMaximum FindMaximumFlow FindMaxValue FindMinimum FindMinimumCostFlow FindMinimumCut FindMinValue FindPermutation FindPostmanTour FindProcessParameters FindRoot FindSequenceFunction FindSettings FindShortestPath FindShortestTour FindThreshold FindVertexCover FindVertexCut Fine FinishDynamic FiniteAbelianGroupCount FiniteGroupCount FiniteGroupData First FirstPassageTimeDistribution FischerGroupFi22 FischerGroupFi23 FischerGroupFi24Prime FisherHypergeometricDistribution FisherRatioTest FisherZDistribution Fit FitAll FittedModel FixedPoint FixedPointList FlashSelection Flat Flatten FlattenAt FlatTopWindow FlipView Floor FlushPrintOutputPacket Fold FoldList Font FontColor FontFamily FontForm FontName FontOpacity FontPostScriptName FontProperties FontReencoding FontSize FontSlant FontSubstitutions FontTracking FontVariations FontWeight For ForAll Format FormatRules FormatType FormatTypeAutoConvert FormatValues FormBox FormBoxOptions FortranForm Forward ForwardBackward Fourier FourierCoefficient FourierCosCoefficient FourierCosSeries FourierCosTransform FourierDCT FourierDCTFilter FourierDCTMatrix FourierDST FourierDSTMatrix FourierMatrix FourierParameters FourierSequenceTransform FourierSeries FourierSinCoefficient FourierSinSeries FourierSinTransform FourierTransform FourierTrigSeries FractionalBrownianMotionProcess FractionalPart FractionBox FractionBoxOptions FractionLine Frame FrameBox FrameBoxOptions Framed FrameInset FrameLabel Frameless FrameMargins FrameStyle FrameTicks FrameTicksStyle FRatioDistribution FrechetDistribution FreeQ FrequencySamplingFilterKernel FresnelC FresnelS Friday FrobeniusNumber FrobeniusSolve FromCharacterCode FromCoefficientRules FromContinuedFraction FromDate FromDigits FromDMS Front FrontEndDynamicExpression FrontEndEventActions FrontEndExecute FrontEndObject FrontEndResource FrontEndResourceString FrontEndStackSize FrontEndToken FrontEndTokenExecute FrontEndValueCache FrontEndVersion FrontFaceColor FrontFaceOpacity Full FullAxes FullDefinition FullForm FullGraphics FullOptions FullSimplify Function FunctionExpand FunctionInterpolation FunctionSpace FussellVeselyImportance GaborFilter GaborMatrix GaborWavelet GainMargins GainPhaseMargins Gamma GammaDistribution GammaRegularized GapPenalty Gather GatherBy GaugeFaceElementFunction GaugeFaceStyle GaugeFrameElementFunction GaugeFrameSize GaugeFrameStyle GaugeLabels GaugeMarkers GaugeStyle GaussianFilter GaussianIntegers GaussianMatrix GaussianWindow GCD GegenbauerC General GeneralizedLinearModelFit GenerateConditions GeneratedCell GeneratedParameters GeneratingFunction Generic GenericCylindricalDecomposition GenomeData GenomeLookup GeodesicClosing GeodesicDilation GeodesicErosion GeodesicOpening GeoDestination GeodesyData GeoDirection GeoDistance GeoGridPosition GeometricBrownianMotionProcess GeometricDistribution GeometricMean GeometricMeanFilter GeometricTransformation GeometricTransformation3DBox GeometricTransformation3DBoxOptions GeometricTransformationBox GeometricTransformationBoxOptions GeoPosition GeoPositionENU GeoPositionXYZ GeoProjectionData GestureHandler GestureHandlerTag Get GetBoundingBoxSizePacket GetContext GetEnvironment GetFileName GetFrontEndOptionsDataPacket GetLinebreakInformationPacket GetMenusPacket GetPageBreakInformationPacket Glaisher GlobalClusteringCoefficient GlobalPreferences GlobalSession Glow GoldenRatio GompertzMakehamDistribution GoodmanKruskalGamma GoodmanKruskalGammaTest Goto Grad Gradient GradientFilter GradientOrientationFilter Graph GraphAssortativity GraphCenter GraphComplement GraphData GraphDensity GraphDiameter GraphDifference GraphDisjointUnion GraphDistance GraphDistanceMatrix GraphElementData GraphEmbedding GraphHighlight GraphHighlightStyle GraphHub Graphics Graphics3D Graphics3DBox Graphics3DBoxOptions GraphicsArray GraphicsBaseline GraphicsBox GraphicsBoxOptions GraphicsColor GraphicsColumn GraphicsComplex GraphicsComplex3DBox GraphicsComplex3DBoxOptions GraphicsComplexBox GraphicsComplexBoxOptions GraphicsContents GraphicsData GraphicsGrid GraphicsGridBox GraphicsGroup GraphicsGroup3DBox GraphicsGroup3DBoxOptions GraphicsGroupBox GraphicsGroupBoxOptions GraphicsGrouping GraphicsHighlightColor GraphicsRow GraphicsSpacing GraphicsStyle GraphIntersection GraphLayout GraphLinkEfficiency GraphPeriphery GraphPlot GraphPlot3D GraphPower GraphPropertyDistribution GraphQ GraphRadius GraphReciprocity GraphRoot GraphStyle GraphUnion Gray GrayLevel GreatCircleDistance Greater GreaterEqual GreaterEqualLess GreaterFullEqual GreaterGreater GreaterLess GreaterSlantEqual GreaterTilde Green Grid GridBaseline GridBox GridBoxAlignment GridBoxBackground GridBoxDividers GridBoxFrame GridBoxItemSize GridBoxItemStyle GridBoxOptions GridBoxSpacings GridCreationSettings GridDefaultElement GridElementStyleOptions GridFrame GridFrameMargins GridGraph GridLines GridLinesStyle GroebnerBasis GroupActionBase GroupCentralizer GroupElementFromWord GroupElementPosition GroupElementQ GroupElements GroupElementToWord GroupGenerators GroupMultiplicationTable GroupOrbits GroupOrder GroupPageBreakWithin GroupSetwiseStabilizer GroupStabilizer GroupStabilizerChain Gudermannian GumbelDistribution HaarWavelet HadamardMatrix HalfNormalDistribution HamiltonianGraphQ HammingDistance HammingWindow HankelH1 HankelH2 HankelMatrix HannPoissonWindow HannWindow HaradaNortonGroupHN HararyGraph HarmonicMean HarmonicMeanFilter HarmonicNumber Hash HashTable Haversine HazardFunction Head HeadCompose Heads HeavisideLambda HeavisidePi HeavisideTheta HeldGroupHe HeldPart HelpBrowserLookup HelpBrowserNotebook HelpBrowserSettings HermiteDecomposition HermiteH HermitianMatrixQ HessenbergDecomposition Hessian HexadecimalCharacter Hexahedron HexahedronBox HexahedronBoxOptions HiddenSurface HighlightGraph HighlightImage HighpassFilter HigmanSimsGroupHS HilbertFilter HilbertMatrix Histogram Histogram3D HistogramDistribution HistogramList HistogramTransform HistogramTransformInterpolation HitMissTransform HITSCentrality HodgeDual HoeffdingD HoeffdingDTest Hold HoldAll HoldAllComplete HoldComplete HoldFirst HoldForm HoldPattern HoldRest HolidayCalendar HomeDirectory HomePage Horizontal HorizontalForm HorizontalGauge HorizontalScrollPosition HornerForm HotellingTSquareDistribution HoytDistribution HTMLSave Hue HumpDownHump HumpEqual HurwitzLerchPhi HurwitzZeta HyperbolicDistribution HypercubeGraph HyperexponentialDistribution Hyperfactorial Hypergeometric0F1 Hypergeometric0F1Regularized Hypergeometric1F1 Hypergeometric1F1Regularized Hypergeometric2F1 Hypergeometric2F1Regularized HypergeometricDistribution HypergeometricPFQ HypergeometricPFQRegularized HypergeometricU Hyperlink HyperlinkCreationSettings Hyphenation HyphenationOptions HypoexponentialDistribution HypothesisTestData I Identity IdentityMatrix If IgnoreCase Im Image Image3D Image3DSlices ImageAccumulate ImageAdd ImageAdjust ImageAlign ImageApply ImageAspectRatio ImageAssemble ImageCache ImageCacheValid ImageCapture ImageChannels ImageClip ImageColorSpace ImageCompose ImageConvolve ImageCooccurrence ImageCorners ImageCorrelate ImageCorrespondingPoints ImageCrop ImageData ImageDataPacket ImageDeconvolve ImageDemosaic ImageDifference ImageDimensions ImageDistance ImageEffect ImageFeatureTrack ImageFileApply ImageFileFilter ImageFileScan ImageFilter ImageForestingComponents ImageForwardTransformation ImageHistogram ImageKeypoints ImageLevels ImageLines ImageMargins ImageMarkers ImageMeasurements ImageMultiply ImageOffset ImagePad ImagePadding ImagePartition ImagePeriodogram ImagePerspectiveTransformation ImageQ ImageRangeCache ImageReflect ImageRegion ImageResize ImageResolution ImageRotate ImageRotated ImageScaled ImageScan ImageSize ImageSizeAction ImageSizeCache ImageSizeMultipliers ImageSizeRaw ImageSubtract ImageTake ImageTransformation ImageTrim ImageType ImageValue ImageValuePositions Implies Import ImportAutoReplacements ImportString ImprovementImportance In IncidenceGraph IncidenceList IncidenceMatrix IncludeConstantBasis IncludeFileExtension IncludePods IncludeSingularTerm Increment Indent IndentingNewlineSpacings IndentMaxFraction IndependenceTest IndependentEdgeSetQ IndependentUnit IndependentVertexSetQ Indeterminate IndexCreationOptions Indexed IndexGraph IndexTag Inequality InexactNumberQ InexactNumbers Infinity Infix Information Inherited InheritScope Initialization InitializationCell InitializationCellEvaluation InitializationCellWarning InlineCounterAssignments InlineCounterIncrements InlineRules Inner Inpaint Input InputAliases InputAssumptions InputAutoReplacements InputField InputFieldBox InputFieldBoxOptions InputForm InputGrouping InputNamePacket InputNotebook InputPacket InputSettings InputStream InputString InputStringPacket InputToBoxFormPacket Insert InsertionPointObject InsertResults Inset Inset3DBox Inset3DBoxOptions InsetBox InsetBoxOptions Install InstallService InString Integer IntegerDigits IntegerExponent IntegerLength IntegerPart IntegerPartitions IntegerQ Integers IntegerString Integral Integrate Interactive InteractiveTradingChart Interlaced Interleaving InternallyBalancedDecomposition InterpolatingFunction InterpolatingPolynomial Interpolation InterpolationOrder InterpolationPoints InterpolationPrecision Interpretation InterpretationBox InterpretationBoxOptions InterpretationFunction InterpretTemplate InterquartileRange Interrupt InterruptSettings Intersection Interval IntervalIntersection IntervalMemberQ IntervalUnion Inverse InverseBetaRegularized InverseCDF InverseChiSquareDistribution InverseContinuousWaveletTransform InverseDistanceTransform InverseEllipticNomeQ InverseErf InverseErfc InverseFourier InverseFourierCosTransform InverseFourierSequenceTransform InverseFourierSinTransform InverseFourierTransform InverseFunction InverseFunctions InverseGammaDistribution InverseGammaRegularized InverseGaussianDistribution InverseGudermannian InverseHaversine InverseJacobiCD InverseJacobiCN InverseJacobiCS InverseJacobiDC InverseJacobiDN InverseJacobiDS InverseJacobiNC InverseJacobiND InverseJacobiNS InverseJacobiSC InverseJacobiSD InverseJacobiSN InverseLaplaceTransform InversePermutation InverseRadon InverseSeries InverseSurvivalFunction InverseWaveletTransform InverseWeierstrassP InverseZTransform Invisible InvisibleApplication InvisibleTimes IrreduciblePolynomialQ IsolatingInterval IsomorphicGraphQ IsotopeData Italic Item ItemBox ItemBoxOptions ItemSize ItemStyle ItoProcess JaccardDissimilarity JacobiAmplitude Jacobian JacobiCD JacobiCN JacobiCS JacobiDC JacobiDN JacobiDS JacobiNC JacobiND JacobiNS JacobiP JacobiSC JacobiSD JacobiSN JacobiSymbol JacobiZeta JankoGroupJ1 JankoGroupJ2 JankoGroupJ3 JankoGroupJ4 JarqueBeraALMTest JohnsonDistribution Join Joined JoinedCurve JoinedCurveBox JoinForm JordanDecomposition JordanModelDecomposition K KagiChart KaiserBesselWindow KaiserWindow KalmanEstimator KalmanFilter KarhunenLoeveDecomposition KaryTree KatzCentrality KCoreComponents KDistribution KelvinBei KelvinBer KelvinKei KelvinKer KendallTau KendallTauTest KernelExecute KernelMixtureDistribution KernelObject Kernels Ket Khinchin KirchhoffGraph KirchhoffMatrix KleinInvariantJ KnightTourGraph KnotData KnownUnitQ KolmogorovSmirnovTest KroneckerDelta KroneckerModelDecomposition KroneckerProduct KroneckerSymbol KuiperTest KumaraswamyDistribution Kurtosis KuwaharaFilter Label Labeled LabeledSlider LabelingFunction LabelStyle LaguerreL LambdaComponents LambertW LanczosWindow LandauDistribution Language LanguageCategory LaplaceDistribution LaplaceTransform Laplacian LaplacianFilter LaplacianGaussianFilter Large Larger Last Latitude LatitudeLongitude LatticeData LatticeReduce Launch LaunchKernels LayeredGraphPlot LayerSizeFunction LayoutInformation LCM LeafCount LeapYearQ LeastSquares LeastSquaresFilterKernel Left LeftArrow LeftArrowBar LeftArrowRightArrow LeftDownTeeVector LeftDownVector LeftDownVectorBar LeftRightArrow LeftRightVector LeftTee LeftTeeArrow LeftTeeVector LeftTriangle LeftTriangleBar LeftTriangleEqual LeftUpDownVector LeftUpTeeVector LeftUpVector LeftUpVectorBar LeftVector LeftVectorBar LegendAppearance Legended LegendFunction LegendLabel LegendLayout LegendMargins LegendMarkers LegendMarkerSize LegendreP LegendreQ LegendreType Length LengthWhile LerchPhi Less LessEqual LessEqualGreater LessFullEqual LessGreater LessLess LessSlantEqual LessTilde LetterCharacter LetterQ Level LeveneTest LeviCivitaTensor LevyDistribution Lexicographic LibraryFunction LibraryFunctionError LibraryFunctionInformation LibraryFunctionLoad LibraryFunctionUnload LibraryLoad LibraryUnload LicenseID LiftingFilterData LiftingWaveletTransform LightBlue LightBrown LightCyan Lighter LightGray LightGreen Lighting LightingAngle LightMagenta LightOrange LightPink LightPurple LightRed LightSources LightYellow Likelihood Limit LimitsPositioning LimitsPositioningTokens LindleyDistribution Line Line3DBox LinearFilter LinearFractionalTransform LinearModelFit LinearOffsetFunction LinearProgramming LinearRecurrence LinearSolve LinearSolveFunction LineBox LineBreak LinebreakAdjustments LineBreakChart LineBreakWithin LineColor LineForm LineGraph LineIndent LineIndentMaxFraction LineIntegralConvolutionPlot LineIntegralConvolutionScale LineLegend LineOpacity LineSpacing LineWrapParts LinkActivate LinkClose LinkConnect LinkConnectedQ LinkCreate LinkError LinkFlush LinkFunction LinkHost LinkInterrupt LinkLaunch LinkMode LinkObject LinkOpen LinkOptions LinkPatterns LinkProtocol LinkRead LinkReadHeld LinkReadyQ Links LinkWrite LinkWriteHeld LiouvilleLambda List Listable ListAnimate ListContourPlot ListContourPlot3D ListConvolve ListCorrelate ListCurvePathPlot ListDeconvolve ListDensityPlot Listen ListFourierSequenceTransform ListInterpolation ListLineIntegralConvolutionPlot ListLinePlot ListLogLinearPlot ListLogLogPlot ListLogPlot ListPicker ListPickerBox ListPickerBoxBackground ListPickerBoxOptions ListPlay ListPlot ListPlot3D ListPointPlot3D ListPolarPlot ListQ ListStreamDensityPlot ListStreamPlot ListSurfacePlot3D ListVectorDensityPlot ListVectorPlot ListVectorPlot3D ListZTransform Literal LiteralSearch LocalClusteringCoefficient LocalizeVariables LocationEquivalenceTest LocationTest Locator LocatorAutoCreate LocatorBox LocatorBoxOptions LocatorCentering LocatorPane LocatorPaneBox LocatorPaneBoxOptions LocatorRegion Locked Log Log10 Log2 LogBarnesG LogGamma LogGammaDistribution LogicalExpand LogIntegral LogisticDistribution LogitModelFit LogLikelihood LogLinearPlot LogLogisticDistribution LogLogPlot LogMultinormalDistribution LogNormalDistribution LogPlot LogRankTest LogSeriesDistribution LongEqual Longest LongestAscendingSequence LongestCommonSequence LongestCommonSequencePositions LongestCommonSubsequence LongestCommonSubsequencePositions LongestMatch LongForm Longitude LongLeftArrow LongLeftRightArrow LongRightArrow Loopback LoopFreeGraphQ LowerCaseQ LowerLeftArrow LowerRightArrow LowerTriangularize LowpassFilter LQEstimatorGains LQGRegulator LQOutputRegulatorGains LQRegulatorGains LUBackSubstitution LucasL LuccioSamiComponents LUDecomposition LyapunovSolve LyonsGroupLy MachineID MachineName MachineNumberQ MachinePrecision MacintoshSystemPageSetup Magenta Magnification Magnify MainSolve MaintainDynamicCaches Majority MakeBoxes MakeExpression MakeRules MangoldtLambda ManhattanDistance Manipulate Manipulator MannWhitneyTest MantissaExponent Manual Map MapAll MapAt MapIndexed MAProcess MapThread MarcumQ MardiaCombinedTest MardiaKurtosisTest MardiaSkewnessTest MarginalDistribution MarkovProcessProperties Masking MatchingDissimilarity MatchLocalNameQ MatchLocalNames MatchQ Material MathematicaNotation MathieuC MathieuCharacteristicA MathieuCharacteristicB MathieuCharacteristicExponent MathieuCPrime MathieuGroupM11 MathieuGroupM12 MathieuGroupM22 MathieuGroupM23 MathieuGroupM24 MathieuS MathieuSPrime MathMLForm MathMLText Matrices MatrixExp MatrixForm MatrixFunction MatrixLog MatrixPlot MatrixPower MatrixQ MatrixRank Max MaxBend MaxDetect MaxExtraBandwidths MaxExtraConditions MaxFeatures MaxFilter Maximize MaxIterations MaxMemoryUsed MaxMixtureKernels MaxPlotPoints MaxPoints MaxRecursion MaxStableDistribution MaxStepFraction MaxSteps MaxStepSize MaxValue MaxwellDistribution McLaughlinGroupMcL Mean MeanClusteringCoefficient MeanDegreeConnectivity MeanDeviation MeanFilter MeanGraphDistance MeanNeighborDegree MeanShift MeanShiftFilter Median MedianDeviation MedianFilter Medium MeijerG MeixnerDistribution MemberQ MemoryConstrained MemoryInUse Menu MenuAppearance MenuCommandKey MenuEvaluator MenuItem MenuPacket MenuSortingValue MenuStyle MenuView MergeDifferences Mesh MeshFunctions MeshRange MeshShading MeshStyle Message MessageDialog MessageList MessageName MessageOptions MessagePacket Messages MessagesNotebook MetaCharacters MetaInformation Method MethodOptions MexicanHatWavelet MeyerWavelet Min MinDetect MinFilter MinimalPolynomial MinimalStateSpaceModel Minimize Minors MinRecursion MinSize MinStableDistribution Minus MinusPlus MinValue Missing MissingDataMethod MittagLefflerE MixedRadix MixedRadixQuantity MixtureDistribution Mod Modal Mode Modular ModularLambda Module Modulus MoebiusMu Moment Momentary MomentConvert MomentEvaluate MomentGeneratingFunction Monday Monitor MonomialList MonomialOrder MonsterGroupM MorletWavelet MorphologicalBinarize MorphologicalBranchPoints MorphologicalComponents MorphologicalEulerNumber MorphologicalGraph MorphologicalPerimeter MorphologicalTransform Most MouseAnnotation MouseAppearance MouseAppearanceTag MouseButtons Mouseover MousePointerNote MousePosition MovingAverage MovingMedian MoyalDistribution MultiedgeStyle MultilaunchWarning MultiLetterItalics MultiLetterStyle MultilineFunction Multinomial MultinomialDistribution MultinormalDistribution MultiplicativeOrder Multiplicity Multiselection MultivariateHypergeometricDistribution MultivariatePoissonDistribution MultivariateTDistribution N NakagamiDistribution NameQ Names NamespaceBox Nand NArgMax NArgMin NBernoulliB NCache NDSolve NDSolveValue Nearest NearestFunction NeedCurrentFrontEndPackagePacket NeedCurrentFrontEndSymbolsPacket NeedlemanWunschSimilarity Needs Negative NegativeBinomialDistribution NegativeMultinomialDistribution NeighborhoodGraph Nest NestedGreaterGreater NestedLessLess NestedScriptRules NestList NestWhile NestWhileList NevilleThetaC NevilleThetaD NevilleThetaN NevilleThetaS NewPrimitiveStyle NExpectation Next NextPrime NHoldAll NHoldFirst NHoldRest NicholsGridLines NicholsPlot NIntegrate NMaximize NMaxValue NMinimize NMinValue NominalVariables NonAssociative NoncentralBetaDistribution NoncentralChiSquareDistribution NoncentralFRatioDistribution NoncentralStudentTDistribution NonCommutativeMultiply NonConstants None NonlinearModelFit NonlocalMeansFilter NonNegative NonPositive Nor NorlundB Norm Normal NormalDistribution NormalGrouping Normalize NormalizedSquaredEuclideanDistance NormalsFunction NormFunction Not NotCongruent NotCupCap NotDoubleVerticalBar Notebook NotebookApply NotebookAutoSave NotebookClose NotebookConvertSettings NotebookCreate NotebookCreateReturnObject NotebookDefault NotebookDelete NotebookDirectory NotebookDynamicExpression NotebookEvaluate NotebookEventActions NotebookFileName NotebookFind NotebookFindReturnObject NotebookGet NotebookGetLayoutInformationPacket NotebookGetMisspellingsPacket NotebookInformation NotebookInterfaceObject NotebookLocate NotebookObject NotebookOpen NotebookOpenReturnObject NotebookPath NotebookPrint NotebookPut NotebookPutReturnObject NotebookRead NotebookResetGeneratedCells Notebooks NotebookSave NotebookSaveAs NotebookSelection NotebookSetupLayoutInformationPacket NotebooksMenu NotebookWrite NotElement NotEqualTilde NotExists NotGreater NotGreaterEqual NotGreaterFullEqual NotGreaterGreater NotGreaterLess NotGreaterSlantEqual NotGreaterTilde NotHumpDownHump NotHumpEqual NotLeftTriangle NotLeftTriangleBar NotLeftTriangleEqual NotLess NotLessEqual NotLessFullEqual NotLessGreater NotLessLess NotLessSlantEqual NotLessTilde NotNestedGreaterGreater NotNestedLessLess NotPrecedes NotPrecedesEqual NotPrecedesSlantEqual NotPrecedesTilde NotReverseElement NotRightTriangle NotRightTriangleBar NotRightTriangleEqual NotSquareSubset NotSquareSubsetEqual NotSquareSuperset NotSquareSupersetEqual NotSubset NotSubsetEqual NotSucceeds NotSucceedsEqual NotSucceedsSlantEqual NotSucceedsTilde NotSuperset NotSupersetEqual NotTilde NotTildeEqual NotTildeFullEqual NotTildeTilde NotVerticalBar NProbability NProduct NProductFactors NRoots NSolve NSum NSumTerms Null NullRecords NullSpace NullWords Number NumberFieldClassNumber NumberFieldDiscriminant NumberFieldFundamentalUnits NumberFieldIntegralBasis NumberFieldNormRepresentatives NumberFieldRegulator NumberFieldRootsOfUnity NumberFieldSignature NumberForm NumberFormat NumberMarks NumberMultiplier NumberPadding NumberPoint NumberQ NumberSeparator NumberSigns NumberString Numerator NumericFunction NumericQ NuttallWindow NValues NyquistGridLines NyquistPlot O ObservabilityGramian ObservabilityMatrix ObservableDecomposition ObservableModelQ OddQ Off Offset OLEData On ONanGroupON OneIdentity Opacity Open OpenAppend Opener OpenerBox OpenerBoxOptions OpenerView OpenFunctionInspectorPacket Opening OpenRead OpenSpecialOptions OpenTemporary OpenWrite Operate OperatingSystem OptimumFlowData Optional OptionInspectorSettings OptionQ Options OptionsPacket OptionsPattern OptionValue OptionValueBox OptionValueBoxOptions Or Orange Order OrderDistribution OrderedQ Ordering Orderless OrnsteinUhlenbeckProcess Orthogonalize Out Outer OutputAutoOverwrite OutputControllabilityMatrix OutputControllableModelQ OutputForm OutputFormData OutputGrouping OutputMathEditExpression OutputNamePacket OutputResponse OutputSizeLimit OutputStream Over OverBar OverDot Overflow OverHat Overlaps Overlay OverlayBox OverlayBoxOptions Overscript OverscriptBox OverscriptBoxOptions OverTilde OverVector OwenT OwnValues PackingMethod PaddedForm Padding PadeApproximant PadLeft PadRight PageBreakAbove PageBreakBelow PageBreakWithin PageFooterLines PageFooters PageHeaderLines PageHeaders PageHeight PageRankCentrality PageWidth PairedBarChart PairedHistogram PairedSmoothHistogram PairedTTest PairedZTest PaletteNotebook PalettePath Pane PaneBox PaneBoxOptions Panel PanelBox PanelBoxOptions Paneled PaneSelector PaneSelectorBox PaneSelectorBoxOptions PaperWidth ParabolicCylinderD ParagraphIndent ParagraphSpacing ParallelArray ParallelCombine ParallelDo ParallelEvaluate Parallelization Parallelize ParallelMap ParallelNeeds ParallelProduct ParallelSubmit ParallelSum ParallelTable ParallelTry Parameter ParameterEstimator ParameterMixtureDistribution ParameterVariables ParametricFunction ParametricNDSolve ParametricNDSolveValue ParametricPlot ParametricPlot3D ParentConnect ParentDirectory ParentForm Parenthesize ParentList ParetoDistribution Part PartialCorrelationFunction PartialD ParticleData Partition PartitionsP PartitionsQ ParzenWindow PascalDistribution PassEventsDown PassEventsUp Paste PasteBoxFormInlineCells PasteButton Path PathGraph PathGraphQ Pattern PatternSequence PatternTest PauliMatrix PaulWavelet Pause PausedTime PDF PearsonChiSquareTest PearsonCorrelationTest PearsonDistribution PerformanceGoal PeriodicInterpolation Periodogram PeriodogramArray PermutationCycles PermutationCyclesQ PermutationGroup PermutationLength PermutationList PermutationListQ PermutationMax PermutationMin PermutationOrder PermutationPower PermutationProduct PermutationReplace Permutations PermutationSupport Permute PeronaMalikFilter Perpendicular PERTDistribution PetersenGraph PhaseMargins Pi Pick PIDData PIDDerivativeFilter PIDFeedforward PIDTune Piecewise PiecewiseExpand PieChart PieChart3D PillaiTrace PillaiTraceTest Pink Pivoting PixelConstrained PixelValue PixelValuePositions Placed Placeholder PlaceholderReplace Plain PlanarGraphQ Play PlayRange Plot Plot3D Plot3Matrix PlotDivision PlotJoined PlotLabel PlotLayout PlotLegends PlotMarkers PlotPoints PlotRange PlotRangeClipping PlotRangePadding PlotRegion PlotStyle Plus PlusMinus Pochhammer PodStates PodWidth Point Point3DBox PointBox PointFigureChart PointForm PointLegend PointSize PoissonConsulDistribution PoissonDistribution PoissonProcess PoissonWindow PolarAxes PolarAxesOrigin PolarGridLines PolarPlot PolarTicks PoleZeroMarkers PolyaAeppliDistribution PolyGamma Polygon Polygon3DBox Polygon3DBoxOptions PolygonBox PolygonBoxOptions PolygonHoleScale PolygonIntersections PolygonScale PolyhedronData PolyLog PolynomialExtendedGCD PolynomialForm PolynomialGCD PolynomialLCM PolynomialMod PolynomialQ PolynomialQuotient PolynomialQuotientRemainder PolynomialReduce PolynomialRemainder Polynomials PopupMenu PopupMenuBox PopupMenuBoxOptions PopupView PopupWindow Position Positive PositiveDefiniteMatrixQ PossibleZeroQ Postfix PostScript Power PowerDistribution PowerExpand PowerMod PowerModList PowerSpectralDensity PowersRepresentations PowerSymmetricPolynomial Precedence PrecedenceForm Precedes PrecedesEqual PrecedesSlantEqual PrecedesTilde Precision PrecisionGoal PreDecrement PredictionRoot PreemptProtect PreferencesPath Prefix PreIncrement Prepend PrependTo PreserveImageOptions Previous PriceGraphDistribution PrimaryPlaceholder Prime PrimeNu PrimeOmega PrimePi PrimePowerQ PrimeQ Primes PrimeZetaP PrimitiveRoot PrincipalComponents PrincipalValue Print PrintAction PrintForm PrintingCopies PrintingOptions PrintingPageRange PrintingStartingPageNumber PrintingStyleEnvironment PrintPrecision PrintTemporary Prism PrismBox PrismBoxOptions PrivateCellOptions PrivateEvaluationOptions PrivateFontOptions PrivateFrontEndOptions PrivateNotebookOptions PrivatePaths Probability ProbabilityDistribution ProbabilityPlot ProbabilityPr ProbabilityScalePlot ProbitModelFit ProcessEstimator ProcessParameterAssumptions ProcessParameterQ ProcessStateDomain ProcessTimeDomain Product ProductDistribution ProductLog ProgressIndicator ProgressIndicatorBox ProgressIndicatorBoxOptions Projection Prolog PromptForm Properties Property PropertyList PropertyValue Proportion Proportional Protect Protected ProteinData Pruning PseudoInverse Purple Put PutAppend Pyramid PyramidBox PyramidBoxOptions QBinomial QFactorial QGamma QHypergeometricPFQ QPochhammer QPolyGamma QRDecomposition QuadraticIrrationalQ Quantile QuantilePlot Quantity QuantityForm QuantityMagnitude QuantityQ QuantityUnit Quartics QuartileDeviation Quartiles QuartileSkewness QueueingNetworkProcess QueueingProcess QueueProperties Quiet Quit Quotient QuotientRemainder RadialityCentrality RadicalBox RadicalBoxOptions RadioButton RadioButtonBar RadioButtonBox RadioButtonBoxOptions Radon RamanujanTau RamanujanTauL RamanujanTauTheta RamanujanTauZ Random RandomChoice RandomComplex RandomFunction RandomGraph RandomImage RandomInteger RandomPermutation RandomPrime RandomReal RandomSample RandomSeed RandomVariate RandomWalkProcess Range RangeFilter RangeSpecification RankedMax RankedMin Raster Raster3D Raster3DBox Raster3DBoxOptions RasterArray RasterBox RasterBoxOptions Rasterize RasterSize Rational RationalFunctions Rationalize Rationals Ratios Raw RawArray RawBoxes RawData RawMedium RayleighDistribution Re Read ReadList ReadProtected Real RealBlockDiagonalForm RealDigits RealExponent Reals Reap Record RecordLists RecordSeparators Rectangle RectangleBox RectangleBoxOptions RectangleChart RectangleChart3D RecurrenceFilter RecurrenceTable RecurringDigitsForm Red Reduce RefBox ReferenceLineStyle ReferenceMarkers ReferenceMarkerStyle Refine ReflectionMatrix ReflectionTransform Refresh RefreshRate RegionBinarize RegionFunction RegionPlot RegionPlot3D RegularExpression Regularization Reinstall Release ReleaseHold ReliabilityDistribution ReliefImage ReliefPlot Remove RemoveAlphaChannel RemoveAsynchronousTask Removed RemoveInputStreamMethod RemoveOutputStreamMethod RemoveProperty RemoveScheduledTask RenameDirectory RenameFile RenderAll RenderingOptions RenewalProcess RenkoChart Repeated RepeatedNull RepeatedString Replace ReplaceAll ReplaceHeldPart ReplaceImageValue ReplaceList ReplacePart ReplacePixelValue ReplaceRepeated Resampling Rescale RescalingTransform ResetDirectory ResetMenusPacket ResetScheduledTask Residue Resolve Rest Resultant ResumePacket Return ReturnExpressionPacket ReturnInputFormPacket ReturnPacket ReturnTextPacket Reverse ReverseBiorthogonalSplineWavelet ReverseElement ReverseEquilibrium ReverseGraph ReverseUpEquilibrium RevolutionAxis RevolutionPlot3D RGBColor RiccatiSolve RiceDistribution RidgeFilter RiemannR RiemannSiegelTheta RiemannSiegelZ Riffle Right RightArrow RightArrowBar RightArrowLeftArrow RightCosetRepresentative RightDownTeeVector RightDownVector RightDownVectorBar RightTee RightTeeArrow RightTeeVector RightTriangle RightTriangleBar RightTriangleEqual RightUpDownVector RightUpTeeVector RightUpVector RightUpVectorBar RightVector RightVectorBar RiskAchievementImportance RiskReductionImportance RogersTanimotoDissimilarity Root RootApproximant RootIntervals RootLocusPlot RootMeanSquare RootOfUnityQ RootReduce Roots RootSum Rotate RotateLabel RotateLeft RotateRight RotationAction RotationBox RotationBoxOptions RotationMatrix RotationTransform Round RoundImplies RoundingRadius Row RowAlignments RowBackgrounds RowBox RowHeights RowLines RowMinHeight RowReduce RowsEqual RowSpacings RSolve RudvalisGroupRu Rule RuleCondition RuleDelayed RuleForm RulerUnits Run RunScheduledTask RunThrough RuntimeAttributes RuntimeOptions RussellRaoDissimilarity SameQ SameTest SampleDepth SampledSoundFunction SampledSoundList SampleRate SamplingPeriod SARIMAProcess SARMAProcess SatisfiabilityCount SatisfiabilityInstances SatisfiableQ Saturday Save Saveable SaveAutoDelete SaveDefinitions SawtoothWave Scale Scaled ScaleDivisions ScaledMousePosition ScaleOrigin ScalePadding ScaleRanges ScaleRangeStyle ScalingFunctions ScalingMatrix ScalingTransform Scan ScheduledTaskActiveQ ScheduledTaskData ScheduledTaskObject ScheduledTasks SchurDecomposition ScientificForm ScreenRectangle ScreenStyleEnvironment ScriptBaselineShifts ScriptLevel ScriptMinSize ScriptRules ScriptSizeMultipliers Scrollbars ScrollingOptions ScrollPosition Sec Sech SechDistribution SectionGrouping SectorChart SectorChart3D SectorOrigin SectorSpacing SeedRandom Select Selectable SelectComponents SelectedCells SelectedNotebook Selection SelectionAnimate SelectionCell SelectionCellCreateCell SelectionCellDefaultStyle SelectionCellParentStyle SelectionCreateCell SelectionDebuggerTag SelectionDuplicateCell SelectionEvaluate SelectionEvaluateCreateCell SelectionMove SelectionPlaceholder SelectionSetStyle SelectWithContents SelfLoops SelfLoopStyle SemialgebraicComponentInstances SendMail Sequence SequenceAlignment SequenceForm SequenceHold SequenceLimit Series SeriesCoefficient SeriesData SessionTime Set SetAccuracy SetAlphaChannel SetAttributes Setbacks SetBoxFormNamesPacket SetDelayed SetDirectory SetEnvironment SetEvaluationNotebook SetFileDate SetFileLoadingContext SetNotebookStatusLine SetOptions SetOptionsPacket SetPrecision SetProperty SetSelectedNotebook SetSharedFunction SetSharedVariable SetSpeechParametersPacket SetStreamPosition SetSystemOptions Setter SetterBar SetterBox SetterBoxOptions Setting SetValue Shading Shallow ShannonWavelet ShapiroWilkTest Share Sharpen ShearingMatrix ShearingTransform ShenCastanMatrix Short ShortDownArrow Shortest ShortestMatch ShortestPathFunction ShortLeftArrow ShortRightArrow ShortUpArrow Show ShowAutoStyles ShowCellBracket ShowCellLabel ShowCellTags ShowClosedCellArea ShowContents ShowControls ShowCursorTracker ShowGroupOpenCloseIcon ShowGroupOpener ShowInvisibleCharacters ShowPageBreaks ShowPredictiveInterface ShowSelection ShowShortBoxForm ShowSpecialCharacters ShowStringCharacters ShowSyntaxStyles ShrinkingDelay ShrinkWrapBoundingBox SiegelTheta SiegelTukeyTest Sign Signature SignedRankTest SignificanceLevel SignPadding SignTest SimilarityRules SimpleGraph SimpleGraphQ Simplify Sin Sinc SinghMaddalaDistribution SingleEvaluation SingleLetterItalics SingleLetterStyle SingularValueDecomposition SingularValueList SingularValuePlot SingularValues Sinh SinhIntegral SinIntegral SixJSymbol Skeleton SkeletonTransform SkellamDistribution Skewness SkewNormalDistribution Skip SliceDistribution Slider Slider2D Slider2DBox Slider2DBoxOptions SliderBox SliderBoxOptions SlideView Slot SlotSequence Small SmallCircle Smaller SmithDelayCompensator SmithWatermanSimilarity SmoothDensityHistogram SmoothHistogram SmoothHistogram3D SmoothKernelDistribution SocialMediaData Socket SokalSneathDissimilarity Solve SolveAlways SolveDelayed Sort SortBy Sound SoundAndGraphics SoundNote SoundVolume Sow Space SpaceForm Spacer Spacings Span SpanAdjustments SpanCharacterRounding SpanFromAbove SpanFromBoth SpanFromLeft SpanLineThickness SpanMaxSize SpanMinSize SpanningCharacters SpanSymmetric SparseArray SpatialGraphDistribution Speak SpeakTextPacket SpearmanRankTest SpearmanRho Spectrogram SpectrogramArray Specularity SpellingCorrection SpellingDictionaries SpellingDictionariesPath SpellingOptions SpellingSuggestionsPacket Sphere SphereBox SphericalBesselJ SphericalBesselY SphericalHankelH1 SphericalHankelH2 SphericalHarmonicY SphericalPlot3D SphericalRegion SpheroidalEigenvalue SpheroidalJoiningFactor SpheroidalPS SpheroidalPSPrime SpheroidalQS SpheroidalQSPrime SpheroidalRadialFactor SpheroidalS1 SpheroidalS1Prime SpheroidalS2 SpheroidalS2Prime Splice SplicedDistribution SplineClosed SplineDegree SplineKnots SplineWeights Split SplitBy SpokenString Sqrt SqrtBox SqrtBoxOptions Square SquaredEuclideanDistance SquareFreeQ SquareIntersection SquaresR SquareSubset SquareSubsetEqual SquareSuperset SquareSupersetEqual SquareUnion SquareWave StabilityMargins StabilityMarginsStyle StableDistribution Stack StackBegin StackComplete StackInhibit StandardDeviation StandardDeviationFilter StandardForm Standardize StandbyDistribution Star StarGraph StartAsynchronousTask StartingStepSize StartOfLine StartOfString StartScheduledTask StartupSound StateDimensions StateFeedbackGains StateOutputEstimator StateResponse StateSpaceModel StateSpaceRealization StateSpaceTransform StationaryDistribution StationaryWaveletPacketTransform StationaryWaveletTransform StatusArea StatusCentrality StepMonitor StieltjesGamma StirlingS1 StirlingS2 StopAsynchronousTask StopScheduledTask StrataVariables StratonovichProcess StreamColorFunction StreamColorFunctionScaling StreamDensityPlot StreamPlot StreamPoints StreamPosition Streams StreamScale StreamStyle String StringBreak StringByteCount StringCases StringCount StringDrop StringExpression StringForm StringFormat StringFreeQ StringInsert StringJoin StringLength StringMatchQ StringPosition StringQ StringReplace StringReplaceList StringReplacePart StringReverse StringRotateLeft StringRotateRight StringSkeleton StringSplit StringTake StringToStream StringTrim StripBoxes StripOnInput StripWrapperBoxes StrokeForm StructuralImportance StructuredArray StructuredSelection StruveH StruveL Stub StudentTDistribution Style StyleBox StyleBoxAutoDelete StyleBoxOptions StyleData StyleDefinitions StyleForm StyleKeyMapping StyleMenuListing StyleNameDialogSettings StyleNames StylePrint StyleSheetPath Subfactorial Subgraph SubMinus SubPlus SubresultantPolynomialRemainders SubresultantPolynomials Subresultants Subscript SubscriptBox SubscriptBoxOptions Subscripted Subset SubsetEqual Subsets SubStar Subsuperscript SubsuperscriptBox SubsuperscriptBoxOptions Subtract SubtractFrom SubValues Succeeds SucceedsEqual SucceedsSlantEqual SucceedsTilde SuchThat Sum SumConvergence Sunday SuperDagger SuperMinus SuperPlus Superscript SuperscriptBox SuperscriptBoxOptions Superset SupersetEqual SuperStar Surd SurdForm SurfaceColor SurfaceGraphics SurvivalDistribution SurvivalFunction SurvivalModel SurvivalModelFit SuspendPacket SuzukiDistribution SuzukiGroupSuz SwatchLegend Switch Symbol SymbolName SymletWavelet Symmetric SymmetricGroup SymmetricMatrixQ SymmetricPolynomial SymmetricReduction Symmetrize SymmetrizedArray SymmetrizedArrayRules SymmetrizedDependentComponents SymmetrizedIndependentComponents SymmetrizedReplacePart SynchronousInitialization SynchronousUpdating Syntax SyntaxForm SyntaxInformation SyntaxLength SyntaxPacket SyntaxQ SystemDialogInput SystemException SystemHelpPath SystemInformation SystemInformationData SystemOpen SystemOptions SystemsModelDelay SystemsModelDelayApproximate SystemsModelDelete SystemsModelDimensions SystemsModelExtract SystemsModelFeedbackConnect SystemsModelLabels SystemsModelOrder SystemsModelParallelConnect SystemsModelSeriesConnect SystemsModelStateFeedbackConnect SystemStub Tab TabFilling Table TableAlignments TableDepth TableDirections TableForm TableHeadings TableSpacing TableView TableViewBox TabSpacings TabView TabViewBox TabViewBoxOptions TagBox TagBoxNote TagBoxOptions TaggingRules TagSet TagSetDelayed TagStyle TagUnset Take TakeWhile Tally Tan Tanh TargetFunctions TargetUnits TautologyQ TelegraphProcess TemplateBox TemplateBoxOptions TemplateSlotSequence TemporalData Temporary TemporaryVariable TensorContract TensorDimensions TensorExpand TensorProduct TensorQ TensorRank TensorReduce TensorSymmetry TensorTranspose TensorWedge Tetrahedron TetrahedronBox TetrahedronBoxOptions TeXForm TeXSave Text Text3DBox Text3DBoxOptions TextAlignment TextBand TextBoundingBox TextBox TextCell TextClipboardType TextData TextForm TextJustification TextLine TextPacket TextParagraph TextRecognize TextRendering TextStyle Texture TextureCoordinateFunction TextureCoordinateScaling Therefore ThermometerGauge Thick Thickness Thin Thinning ThisLink ThompsonGroupTh Thread ThreeJSymbol Threshold Through Throw Thumbnail Thursday Ticks TicksStyle Tilde TildeEqual TildeFullEqual TildeTilde TimeConstrained TimeConstraint Times TimesBy TimeSeriesForecast TimeSeriesInvertibility TimeUsed TimeValue TimeZone Timing Tiny TitleGrouping TitsGroupT ToBoxes ToCharacterCode ToColor ToContinuousTimeModel ToDate ToDiscreteTimeModel ToeplitzMatrix ToExpression ToFileName Together Toggle ToggleFalse Toggler TogglerBar TogglerBox TogglerBoxOptions ToHeldExpression ToInvertibleTimeSeries TokenWords Tolerance ToLowerCase ToNumberField TooBig Tooltip TooltipBox TooltipBoxOptions TooltipDelay TooltipStyle Top TopHatTransform TopologicalSort ToRadicals ToRules ToString Total TotalHeight TotalVariationFilter TotalWidth TouchscreenAutoZoom TouchscreenControlPlacement ToUpperCase Tr Trace TraceAbove TraceAction TraceBackward TraceDepth TraceDialog TraceForward TraceInternal TraceLevel TraceOff TraceOn TraceOriginal TracePrint TraceScan TrackedSymbols TradingChart TraditionalForm TraditionalFunctionNotation TraditionalNotation TraditionalOrder TransferFunctionCancel TransferFunctionExpand TransferFunctionFactor TransferFunctionModel TransferFunctionPoles TransferFunctionTransform TransferFunctionZeros TransformationFunction TransformationFunctions TransformationMatrix TransformedDistribution TransformedField Translate TranslationTransform TransparentColor Transpose TreeForm TreeGraph TreeGraphQ TreePlot TrendStyle TriangleWave TriangularDistribution Trig TrigExpand TrigFactor TrigFactorList Trigger TrigReduce TrigToExp TrimmedMean True TrueQ TruncatedDistribution TsallisQExponentialDistribution TsallisQGaussianDistribution TTest Tube TubeBezierCurveBox TubeBezierCurveBoxOptions TubeBox TubeBSplineCurveBox TubeBSplineCurveBoxOptions Tuesday TukeyLambdaDistribution TukeyWindow Tuples TuranGraph TuringMachine Transparent UnateQ Uncompress Undefined UnderBar Underflow Underlined Underoverscript UnderoverscriptBox UnderoverscriptBoxOptions Underscript UnderscriptBox UnderscriptBoxOptions UndirectedEdge UndirectedGraph UndirectedGraphQ UndocumentedTestFEParserPacket UndocumentedTestGetSelectionPacket Unequal Unevaluated UniformDistribution UniformGraphDistribution UniformSumDistribution Uninstall Union UnionPlus Unique UnitBox UnitConvert UnitDimensions Unitize UnitRootTest UnitSimplify UnitStep UnitTriangle UnitVector Unprotect UnsameQ UnsavedVariables Unset UnsetShared UntrackedVariables Up UpArrow UpArrowBar UpArrowDownArrow Update UpdateDynamicObjects UpdateDynamicObjectsSynchronous UpdateInterval UpDownArrow UpEquilibrium UpperCaseQ UpperLeftArrow UpperRightArrow UpperTriangularize Upsample UpSet UpSetDelayed UpTee UpTeeArrow UpValues URL URLFetch URLFetchAsynchronous URLSave URLSaveAsynchronous UseGraphicsRange Using UsingFrontEnd V2Get ValidationLength Value ValueBox ValueBoxOptions ValueForm ValueQ ValuesData Variables Variance VarianceEquivalenceTest VarianceEstimatorFunction VarianceGammaDistribution VarianceTest VectorAngle VectorColorFunction VectorColorFunctionScaling VectorDensityPlot VectorGlyphData VectorPlot VectorPlot3D VectorPoints VectorQ Vectors VectorScale VectorStyle Vee Verbatim Verbose VerboseConvertToPostScriptPacket VerifyConvergence VerifySolutions VerifyTestAssumptions Version VersionNumber VertexAdd VertexCapacity VertexColors VertexComponent VertexConnectivity VertexCoordinateRules VertexCoordinates VertexCorrelationSimilarity VertexCosineSimilarity VertexCount VertexCoverQ VertexDataCoordinates VertexDegree VertexDelete VertexDiceSimilarity VertexEccentricity VertexInComponent VertexInDegree VertexIndex VertexJaccardSimilarity VertexLabeling VertexLabels VertexLabelStyle VertexList VertexNormals VertexOutComponent VertexOutDegree VertexQ VertexRenderingFunction VertexReplace VertexShape VertexShapeFunction VertexSize VertexStyle VertexTextureCoordinates VertexWeight Vertical VerticalBar VerticalForm VerticalGauge VerticalSeparator VerticalSlider VerticalTilde ViewAngle ViewCenter ViewMatrix ViewPoint ViewPointSelectorSettings ViewPort ViewRange ViewVector ViewVertical VirtualGroupData Visible VisibleCell VoigtDistribution VonMisesDistribution WaitAll WaitAsynchronousTask WaitNext WaitUntil WakebyDistribution WalleniusHypergeometricDistribution WaringYuleDistribution WatershedComponents WatsonUSquareTest WattsStrogatzGraphDistribution WaveletBestBasis WaveletFilterCoefficients WaveletImagePlot WaveletListPlot WaveletMapIndexed WaveletMatrixPlot WaveletPhi WaveletPsi WaveletScale WaveletScalogram WaveletThreshold WeaklyConnectedComponents WeaklyConnectedGraphQ WeakStationarity WeatherData WeberE Wedge Wednesday WeibullDistribution WeierstrassHalfPeriods WeierstrassInvariants WeierstrassP WeierstrassPPrime WeierstrassSigma WeierstrassZeta WeightedAdjacencyGraph WeightedAdjacencyMatrix WeightedData WeightedGraphQ Weights WelchWindow WheelGraph WhenEvent Which While White Whitespace WhitespaceCharacter WhittakerM WhittakerW WienerFilter WienerProcess WignerD WignerSemicircleDistribution WilksW WilksWTest WindowClickSelect WindowElements WindowFloating WindowFrame WindowFrameElements WindowMargins WindowMovable WindowOpacity WindowSelected WindowSize WindowStatusArea WindowTitle WindowToolbars WindowWidth With WolframAlpha WolframAlphaDate WolframAlphaQuantity WolframAlphaResult Word WordBoundary WordCharacter WordData WordSearch WordSeparators WorkingPrecision Write WriteString Wronskian XMLElement XMLObject Xnor Xor Yellow YuleDissimilarity ZernikeR ZeroSymmetric ZeroTest ZeroWidthTimes Zeta ZetaZero ZipfDistribution ZTest ZTransform $Aborted $ActivationGroupID $ActivationKey $ActivationUserRegistered $AddOnsDirectory $AssertFunction $Assumptions $AsynchronousTask $BaseDirectory $BatchInput $BatchOutput $BoxForms $ByteOrdering $Canceled $CharacterEncoding $CharacterEncodings $CommandLine $CompilationTarget $ConditionHold $ConfiguredKernels $Context $ContextPath $ControlActiveSetting $CreationDate $CurrentLink $DateStringFormat $DefaultFont $DefaultFrontEnd $DefaultImagingDevice $DefaultPath $Display $DisplayFunction $DistributedContexts $DynamicEvaluation $Echo $Epilog $ExportFormats $Failed $FinancialDataSource $FormatType $FrontEnd $FrontEndSession $GeoLocation $HistoryLength $HomeDirectory $HTTPCookies $IgnoreEOF $ImagingDevices $ImportFormats $InitialDirectory $Input $InputFileName $InputStreamMethods $Inspector $InstallationDate $InstallationDirectory $InterfaceEnvironment $IterationLimit $KernelCount $KernelID $Language $LaunchDirectory $LibraryPath $LicenseExpirationDate $LicenseID $LicenseProcesses $LicenseServer $LicenseSubprocesses $LicenseType $Line $Linked $LinkSupported $LoadedFiles $MachineAddresses $MachineDomain $MachineDomains $MachineEpsilon $MachineID $MachineName $MachinePrecision $MachineType $MaxExtraPrecision $MaxLicenseProcesses $MaxLicenseSubprocesses $MaxMachineNumber $MaxNumber $MaxPiecewiseCases $MaxPrecision $MaxRootDegree $MessageGroups $MessageList $MessagePrePrint $Messages $MinMachineNumber $MinNumber $MinorReleaseNumber $MinPrecision $ModuleNumber $NetworkLicense $NewMessage $NewSymbol $Notebooks $NumberMarks $Off $OperatingSystem $Output $OutputForms $OutputSizeLimit $OutputStreamMethods $Packages $ParentLink $ParentProcessID $PasswordFile $PatchLevelID $Path $PathnameSeparator $PerformanceGoal $PipeSupported $Post $Pre $PreferencesDirectory $PrePrint $PreRead $PrintForms $PrintLiteral $ProcessID $ProcessorCount $ProcessorType $ProductInformation $ProgramName $RandomState $RecursionLimit $ReleaseNumber $RootDirectory $ScheduledTask $ScriptCommandLine $SessionID $SetParentLink $SharedFunctions $SharedVariables $SoundDisplay $SoundDisplayFunction $SuppressInputFormHeads $SynchronousEvaluation $SyntaxHandler $System $SystemCharacterEncoding $SystemID $SystemWordLength $TemporaryDirectory $TemporaryPrefix $TextStyle $TimedOut $TimeUnit $TimeZone $TopDirectory $TraceOff $TraceOn $TracePattern $TracePostAction $TracePreAction $Urgent $UserAddOnsDirectory $UserBaseDirectory $UserDocumentsDirectory $UserName $Version $VersionNumber",c:[{cN:"comment",b:/\(\*/,e:/\*\)/},a.ASM,a.QSM,a.CNM,{cN:"list",b:/\{/,e:/\}/,i:/:/}]}});hljs.registerLanguage("swift",function(a){var e={keyword:"class deinit enum extension func import init let protocol static struct subscript typealias var break case continue default do else fallthrough if in for return switch where while as dynamicType is new super self Self Type __COLUMN__ __FILE__ __FUNCTION__ __LINE__ associativity didSet get infix inout left mutating none nonmutating operator override postfix precedence prefix right set unowned unowned safe unsafe weak willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue assert bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal false filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced join lexicographicalCompare map max maxElement min minElement nil numericCast partition posix print println quickSort reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith strideof strideofValue swap swift toString transcode true underestimateCount unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafePointers withVaList"};var g={cN:"type",b:"\\b[A-Z][\\w']*",r:0};var b={cN:"comment",b:"/\\*",e:"\\*/",c:[a.PWM,"self"]};var c={cN:"subst",b:/\\\(/,e:"\\)",k:e,c:[]};var f={cN:"number",b:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",r:0};var d=a.inherit(a.QSM,{c:[c,a.BE]});c.c=[f];return{k:e,c:[d,a.CLCM,b,g,f,{cN:"func",bK:"func",eE:true,c:[a.inherit(a.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/,i:/\(/}),{cN:"generics",b:/\/,i:/\>/},{cN:"params",b:/\(/,e:/\)/,c:[a.CLCM,a.CBCM],i:/["']/}],i:/\[|%/},{cN:"class",k:"struct protocol class extension enum",b:"(struct|protocol|class(?! (func|var))|extension|enum)",e:"\\{",eE:true,c:[a.inherit(a.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/})]},{cN:"preprocessor",b:"(@assignment|@class_protocol|@exported|@final|@lazy|@noreturn|@NSCopying|@NSManaged|@objc|@optional|@required|@auto_closure|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix)"},]}});hljs.registerLanguage("php",function(b){var e={cN:"variable",b:"(\\$|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*"};var a={cN:"preprocessor",b:/<\?(php)?|\?>/};var c={cN:"string",c:[b.BE,a],v:[{b:'b"',e:'"'},{b:"b'",e:"'"},b.inherit(b.ASM,{i:null}),b.inherit(b.QSM,{i:null})]};var d={v:[b.BNM,b.CNM]};return{aliases:["php3","php4","php5","php6"],cI:true,k:"and include_once list abstract global private echo interface as static endswitch array null if endwhile or const for endforeach self var while isset public protected exit foreach throw elseif include __FILE__ empty require_once do xor return parent clone use __CLASS__ __LINE__ else break print eval new catch __METHOD__ case exception default die require __FUNCTION__ enddeclare final try switch continue endfor endif declare unset true false trait goto instanceof insteadof __DIR__ __NAMESPACE__ yield finally",c:[b.CLCM,b.HCM,{cN:"comment",b:"/\\*",e:"\\*/",c:[{cN:"phpdoc",b:"\\s@[A-Za-z]+"},a]},{cN:"comment",b:"__halt_compiler.+?;",eW:true,k:"__halt_compiler",l:b.UIR},{cN:"string",b:"<<<['\"]?\\w+['\"]?$",e:"^\\w+;",c:[b.BE]},a,e,{cN:"function",bK:"function",e:/[;{]/,eE:true,i:"\\$|\\[|%",c:[b.UTM,{cN:"params",b:"\\(",e:"\\)",c:["self",e,b.CBCM,c,d]}]},{cN:"class",bK:"class interface",e:"{",eE:true,i:/[:\(\$"]/,c:[{bK:"extends implements",r:10},b.UTM]},{bK:"namespace",e:";",i:/[\.']/,c:[b.UTM]},{bK:"use",e:";",c:[b.UTM]},{b:"=>"},c,d]}});hljs.registerLanguage("haskell",function(f){var g={cN:"comment",v:[{b:"--",e:"$"},{b:"{-",e:"-}",c:["self"]}]};var e={cN:"pragma",b:"{-#",e:"#-}"};var b={cN:"preprocessor",b:"^#",e:"$"};var d={cN:"type",b:"\\b[A-Z][\\w']*",r:0};var c={cN:"container",b:"\\(",e:"\\)",i:'"',c:[e,g,b,{cN:"type",b:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},f.inherit(f.TM,{b:"[_a-z][\\w']*"})]};var a={cN:"container",b:"{",e:"}",c:c.c};return{aliases:["hs"],k:"let in if then else case of where do module import hiding qualified type data newtype deriving class instance as default infix infixl infixr foreign export ccall stdcall cplusplus jvm dotnet safe unsafe family forall mdo proc rec",c:[{cN:"module",b:"\\bmodule\\b",e:"where",k:"module where",c:[c,g],i:"\\W\\.|;"},{cN:"import",b:"\\bimport\\b",e:"$",k:"import|0 qualified as hiding",c:[c,g],i:"\\W\\.|;"},{cN:"class",b:"^(\\s*)?(class|instance)\\b",e:"where",k:"class family instance where",c:[d,c,g]},{cN:"typedef",b:"\\b(data|(new)?type)\\b",e:"$",k:"data family type newtype deriving",c:[e,g,d,c,a]},{cN:"default",bK:"default",e:"$",c:[d,c,g]},{cN:"infix",bK:"infix infixl infixr",e:"$",c:[f.CNM,g]},{cN:"foreign",b:"\\bforeign\\b",e:"$",k:"foreign import export ccall stdcall cplusplus jvm dotnet safe unsafe",c:[d,f.QSM,g]},{cN:"shebang",b:"#!\\/usr\\/bin\\/env runhaskell",e:"$"},e,g,b,f.QSM,f.CNM,d,f.inherit(f.TM,{b:"^[_a-z][\\w']*"}),{b:"->|<-"}]}});hljs.registerLanguage("1c",function(b){var f="[a-zA-Zа-яА-Я][a-zA-Z0-9_а-яА-Я]*";var c="возврат дата для если и или иначе иначеесли исключение конецесли конецпопытки конецпроцедуры конецфункции конеццикла константа не перейти перем перечисление по пока попытка прервать продолжить процедура строка тогда фс функция цикл число экспорт";var e="ansitooem oemtoansi ввестивидсубконто ввестидату ввестизначение ввестиперечисление ввестипериод ввестиплансчетов ввестистроку ввестичисло вопрос восстановитьзначение врег выбранныйплансчетов вызватьисключение датагод датамесяц датачисло добавитьмесяц завершитьработусистемы заголовоксистемы записьжурналарегистрации запуститьприложение зафиксироватьтранзакцию значениевстроку значениевстрокувнутр значениевфайл значениеизстроки значениеизстрокивнутр значениеизфайла имякомпьютера имяпользователя каталогвременныхфайлов каталогиб каталогпользователя каталогпрограммы кодсимв командасистемы конгода конецпериодаби конецрассчитанногопериодаби конецстандартногоинтервала конквартала конмесяца коннедели лев лог лог10 макс максимальноеколичествосубконто мин монопольныйрежим названиеинтерфейса названиенабораправ назначитьвид назначитьсчет найти найтипомеченныенаудаление найтиссылки началопериодаби началостандартногоинтервала начатьтранзакцию начгода начквартала начмесяца начнедели номерднягода номерднянедели номернеделигода нрег обработкаожидания окр описаниеошибки основнойжурналрасчетов основнойплансчетов основнойязык открытьформу открытьформумодально отменитьтранзакцию очиститьокносообщений периодстр полноеимяпользователя получитьвремята получитьдатута получитьдокументта получитьзначенияотбора получитьпозициюта получитьпустоезначение получитьта прав праводоступа предупреждение префиксавтонумерации пустаястрока пустоезначение рабочаядаттьпустоезначение рабочаядата разделительстраниц разделительстрок разм разобратьпозициюдокумента рассчитатьрегистрына рассчитатьрегистрыпо сигнал симв символтабуляции создатьобъект сокрл сокрлп сокрп сообщить состояние сохранитьзначение сред статусвозврата стрдлина стрзаменить стрколичествострок стрполучитьстроку стрчисловхождений сформироватьпозициюдокумента счетпокоду текущаядата текущеевремя типзначения типзначениястр удалитьобъекты установитьтана установитьтапо фиксшаблон формат цел шаблон";var a={cN:"dquote",b:'""'};var d={cN:"string",b:'"',e:'"|$',c:[a]};var g={cN:"string",b:"\\|",e:'"|$',c:[a]};return{cI:true,l:f,k:{keyword:c,built_in:e},c:[b.CLCM,b.NM,d,g,{cN:"function",b:"(процедура|функция)",e:"$",l:f,k:"процедура функция",c:[b.inherit(b.TM,{b:f}),{cN:"tail",eW:true,c:[{cN:"params",b:"\\(",e:"\\)",l:f,k:"знач",c:[d,g]},{cN:"export",b:"экспорт",eW:true,l:f,k:"экспорт",c:[b.CLCM]}]},b.CLCM]},{cN:"preprocessor",b:"#",e:"$"},{cN:"date",b:"'\\d{2}\\.\\d{2}\\.(\\d{2}|\\d{4})'"}]}});hljs.registerLanguage("x86asm",function(a){return{cI:true,l:"\\.?"+a.IR,k:{keyword:"lock rep repe repz repne repnz xaquire xrelease bnd nobnd aaa aad aam aas adc add and arpl bb0_reset bb1_reset bound bsf bsr bswap bt btc btr bts call cbw cdq cdqe clc cld cli clts cmc cmp cmpsb cmpsd cmpsq cmpsw cmpxchg cmpxchg486 cmpxchg8b cmpxchg16b cpuid cpu_read cpu_write cqo cwd cwde daa das dec div dmint emms enter equ f2xm1 fabs fadd faddp fbld fbstp fchs fclex fcmovb fcmovbe fcmove fcmovnb fcmovnbe fcmovne fcmovnu fcmovu fcom fcomi fcomip fcomp fcompp fcos fdecstp fdisi fdiv fdivp fdivr fdivrp femms feni ffree ffreep fiadd ficom ficomp fidiv fidivr fild fimul fincstp finit fist fistp fisttp fisub fisubr fld fld1 fldcw fldenv fldl2e fldl2t fldlg2 fldln2 fldpi fldz fmul fmulp fnclex fndisi fneni fninit fnop fnsave fnstcw fnstenv fnstsw fpatan fprem fprem1 fptan frndint frstor fsave fscale fsetpm fsin fsincos fsqrt fst fstcw fstenv fstp fstsw fsub fsubp fsubr fsubrp ftst fucom fucomi fucomip fucomp fucompp fxam fxch fxtract fyl2x fyl2xp1 hlt ibts icebp idiv imul in inc incbin insb insd insw int int01 int1 int03 int3 into invd invpcid invlpg invlpga iret iretd iretq iretw jcxz jecxz jrcxz jmp jmpe lahf lar lds lea leave les lfence lfs lgdt lgs lidt lldt lmsw loadall loadall286 lodsb lodsd lodsq lodsw loop loope loopne loopnz loopz lsl lss ltr mfence monitor mov movd movq movsb movsd movsq movsw movsx movsxd movzx mul mwait neg nop not or out outsb outsd outsw packssdw packsswb packuswb paddb paddd paddsb paddsiw paddsw paddusb paddusw paddw pand pandn pause paveb pavgusb pcmpeqb pcmpeqd pcmpeqw pcmpgtb pcmpgtd pcmpgtw pdistib pf2id pfacc pfadd pfcmpeq pfcmpge pfcmpgt pfmax pfmin pfmul pfrcp pfrcpit1 pfrcpit2 pfrsqit1 pfrsqrt pfsub pfsubr pi2fd pmachriw pmaddwd pmagw pmulhriw pmulhrwa pmulhrwc pmulhw pmullw pmvgezb pmvlzb pmvnzb pmvzb pop popa popad popaw popf popfd popfq popfw por prefetch prefetchw pslld psllq psllw psrad psraw psrld psrlq psrlw psubb psubd psubsb psubsiw psubsw psubusb psubusw psubw punpckhbw punpckhdq punpckhwd punpcklbw punpckldq punpcklwd push pusha pushad pushaw pushf pushfd pushfq pushfw pxor rcl rcr rdshr rdmsr rdpmc rdtsc rdtscp ret retf retn rol ror rdm rsdc rsldt rsm rsts sahf sal salc sar sbb scasb scasd scasq scasw sfence sgdt shl shld shr shrd sidt sldt skinit smi smint smintold smsw stc std sti stosb stosd stosq stosw str sub svdc svldt svts swapgs syscall sysenter sysexit sysret test ud0 ud1 ud2b ud2 ud2a umov verr verw fwait wbinvd wrshr wrmsr xadd xbts xchg xlatb xlat xor cmove cmovz cmovne cmovnz cmova cmovnbe cmovae cmovnb cmovb cmovnae cmovbe cmovna cmovg cmovnle cmovge cmovnl cmovl cmovnge cmovle cmovng cmovc cmovnc cmovo cmovno cmovs cmovns cmovp cmovpe cmovnp cmovpo je jz jne jnz ja jnbe jae jnb jb jnae jbe jna jg jnle jge jnl jl jnge jle jng jc jnc jo jno js jns jpo jnp jpe jp sete setz setne setnz seta setnbe setae setnb setnc setb setnae setcset setbe setna setg setnle setge setnl setl setnge setle setng sets setns seto setno setpe setp setpo setnp addps addss andnps andps cmpeqps cmpeqss cmpleps cmpless cmpltps cmpltss cmpneqps cmpneqss cmpnleps cmpnless cmpnltps cmpnltss cmpordps cmpordss cmpunordps cmpunordss cmpps cmpss comiss cvtpi2ps cvtps2pi cvtsi2ss cvtss2si cvttps2pi cvttss2si divps divss ldmxcsr maxps maxss minps minss movaps movhps movlhps movlps movhlps movmskps movntps movss movups mulps mulss orps rcpps rcpss rsqrtps rsqrtss shufps sqrtps sqrtss stmxcsr subps subss ucomiss unpckhps unpcklps xorps fxrstor fxrstor64 fxsave fxsave64 xgetbv xsetbv xsave xsave64 xsaveopt xsaveopt64 xrstor xrstor64 prefetchnta prefetcht0 prefetcht1 prefetcht2 maskmovq movntq pavgb pavgw pextrw pinsrw pmaxsw pmaxub pminsw pminub pmovmskb pmulhuw psadbw pshufw pf2iw pfnacc pfpnacc pi2fw pswapd maskmovdqu clflush movntdq movnti movntpd movdqa movdqu movdq2q movq2dq paddq pmuludq pshufd pshufhw pshuflw pslldq psrldq psubq punpckhqdq punpcklqdq addpd addsd andnpd andpd cmpeqpd cmpeqsd cmplepd cmplesd cmpltpd cmpltsd cmpneqpd cmpneqsd cmpnlepd cmpnlesd cmpnltpd cmpnltsd cmpordpd cmpordsd cmpunordpd cmpunordsd cmppd comisd cvtdq2pd cvtdq2ps cvtpd2dq cvtpd2pi cvtpd2ps cvtpi2pd cvtps2dq cvtps2pd cvtsd2si cvtsd2ss cvtsi2sd cvtss2sd cvttpd2pi cvttpd2dq cvttps2dq cvttsd2si divpd divsd maxpd maxsd minpd minsd movapd movhpd movlpd movmskpd movupd mulpd mulsd orpd shufpd sqrtpd sqrtsd subpd subsd ucomisd unpckhpd unpcklpd xorpd addsubpd addsubps haddpd haddps hsubpd hsubps lddqu movddup movshdup movsldup clgi stgi vmcall vmclear vmfunc vmlaunch vmload vmmcall vmptrld vmptrst vmread vmresume vmrun vmsave vmwrite vmxoff vmxon invept invvpid pabsb pabsw pabsd palignr phaddw phaddd phaddsw phsubw phsubd phsubsw pmaddubsw pmulhrsw pshufb psignb psignw psignd extrq insertq movntsd movntss lzcnt blendpd blendps blendvpd blendvps dppd dpps extractps insertps movntdqa mpsadbw packusdw pblendvb pblendw pcmpeqq pextrb pextrd pextrq phminposuw pinsrb pinsrd pinsrq pmaxsb pmaxsd pmaxud pmaxuw pminsb pminsd pminud pminuw pmovsxbw pmovsxbd pmovsxbq pmovsxwd pmovsxwq pmovsxdq pmovzxbw pmovzxbd pmovzxbq pmovzxwd pmovzxwq pmovzxdq pmuldq pmulld ptest roundpd roundps roundsd roundss crc32 pcmpestri pcmpestrm pcmpistri pcmpistrm pcmpgtq popcnt getsec pfrcpv pfrsqrtv movbe aesenc aesenclast aesdec aesdeclast aesimc aeskeygenassist vaesenc vaesenclast vaesdec vaesdeclast vaesimc vaeskeygenassist vaddpd vaddps vaddsd vaddss vaddsubpd vaddsubps vandpd vandps vandnpd vandnps vblendpd vblendps vblendvpd vblendvps vbroadcastss vbroadcastsd vbroadcastf128 vcmpeq_ospd vcmpeqpd vcmplt_ospd vcmpltpd vcmple_ospd vcmplepd vcmpunord_qpd vcmpunordpd vcmpneq_uqpd vcmpneqpd vcmpnlt_uspd vcmpnltpd vcmpnle_uspd vcmpnlepd vcmpord_qpd vcmpordpd vcmpeq_uqpd vcmpnge_uspd vcmpngepd vcmpngt_uspd vcmpngtpd vcmpfalse_oqpd vcmpfalsepd vcmpneq_oqpd vcmpge_ospd vcmpgepd vcmpgt_ospd vcmpgtpd vcmptrue_uqpd vcmptruepd vcmplt_oqpd vcmple_oqpd vcmpunord_spd vcmpneq_uspd vcmpnlt_uqpd vcmpnle_uqpd vcmpord_spd vcmpeq_uspd vcmpnge_uqpd vcmpngt_uqpd vcmpfalse_ospd vcmpneq_ospd vcmpge_oqpd vcmpgt_oqpd vcmptrue_uspd vcmppd vcmpeq_osps vcmpeqps vcmplt_osps vcmpltps vcmple_osps vcmpleps vcmpunord_qps vcmpunordps vcmpneq_uqps vcmpneqps vcmpnlt_usps vcmpnltps vcmpnle_usps vcmpnleps vcmpord_qps vcmpordps vcmpeq_uqps vcmpnge_usps vcmpngeps vcmpngt_usps vcmpngtps vcmpfalse_oqps vcmpfalseps vcmpneq_oqps vcmpge_osps vcmpgeps vcmpgt_osps vcmpgtps vcmptrue_uqps vcmptrueps vcmplt_oqps vcmple_oqps vcmpunord_sps vcmpneq_usps vcmpnlt_uqps vcmpnle_uqps vcmpord_sps vcmpeq_usps vcmpnge_uqps vcmpngt_uqps vcmpfalse_osps vcmpneq_osps vcmpge_oqps vcmpgt_oqps vcmptrue_usps vcmpps vcmpeq_ossd vcmpeqsd vcmplt_ossd vcmpltsd vcmple_ossd vcmplesd vcmpunord_qsd vcmpunordsd vcmpneq_uqsd vcmpneqsd vcmpnlt_ussd vcmpnltsd vcmpnle_ussd vcmpnlesd vcmpord_qsd vcmpordsd vcmpeq_uqsd vcmpnge_ussd vcmpngesd vcmpngt_ussd vcmpngtsd vcmpfalse_oqsd vcmpfalsesd vcmpneq_oqsd vcmpge_ossd vcmpgesd vcmpgt_ossd vcmpgtsd vcmptrue_uqsd vcmptruesd vcmplt_oqsd vcmple_oqsd vcmpunord_ssd vcmpneq_ussd vcmpnlt_uqsd vcmpnle_uqsd vcmpord_ssd vcmpeq_ussd vcmpnge_uqsd vcmpngt_uqsd vcmpfalse_ossd vcmpneq_ossd vcmpge_oqsd vcmpgt_oqsd vcmptrue_ussd vcmpsd vcmpeq_osss vcmpeqss vcmplt_osss vcmpltss vcmple_osss vcmpless vcmpunord_qss vcmpunordss vcmpneq_uqss vcmpneqss vcmpnlt_usss vcmpnltss vcmpnle_usss vcmpnless vcmpord_qss vcmpordss vcmpeq_uqss vcmpnge_usss vcmpngess vcmpngt_usss vcmpngtss vcmpfalse_oqss vcmpfalsess vcmpneq_oqss vcmpge_osss vcmpgess vcmpgt_osss vcmpgtss vcmptrue_uqss vcmptruess vcmplt_oqss vcmple_oqss vcmpunord_sss vcmpneq_usss vcmpnlt_uqss vcmpnle_uqss vcmpord_sss vcmpeq_usss vcmpnge_uqss vcmpngt_uqss vcmpfalse_osss vcmpneq_osss vcmpge_oqss vcmpgt_oqss vcmptrue_usss vcmpss vcomisd vcomiss vcvtdq2pd vcvtdq2ps vcvtpd2dq vcvtpd2ps vcvtps2dq vcvtps2pd vcvtsd2si vcvtsd2ss vcvtsi2sd vcvtsi2ss vcvtss2sd vcvtss2si vcvttpd2dq vcvttps2dq vcvttsd2si vcvttss2si vdivpd vdivps vdivsd vdivss vdppd vdpps vextractf128 vextractps vhaddpd vhaddps vhsubpd vhsubps vinsertf128 vinsertps vlddqu vldqqu vldmxcsr vmaskmovdqu vmaskmovps vmaskmovpd vmaxpd vmaxps vmaxsd vmaxss vminpd vminps vminsd vminss vmovapd vmovaps vmovd vmovq vmovddup vmovdqa vmovqqa vmovdqu vmovqqu vmovhlps vmovhpd vmovhps vmovlhps vmovlpd vmovlps vmovmskpd vmovmskps vmovntdq vmovntqq vmovntdqa vmovntpd vmovntps vmovsd vmovshdup vmovsldup vmovss vmovupd vmovups vmpsadbw vmulpd vmulps vmulsd vmulss vorpd vorps vpabsb vpabsw vpabsd vpacksswb vpackssdw vpackuswb vpackusdw vpaddb vpaddw vpaddd vpaddq vpaddsb vpaddsw vpaddusb vpaddusw vpalignr vpand vpandn vpavgb vpavgw vpblendvb vpblendw vpcmpestri vpcmpestrm vpcmpistri vpcmpistrm vpcmpeqb vpcmpeqw vpcmpeqd vpcmpeqq vpcmpgtb vpcmpgtw vpcmpgtd vpcmpgtq vpermilpd vpermilps vperm2f128 vpextrb vpextrw vpextrd vpextrq vphaddw vphaddd vphaddsw vphminposuw vphsubw vphsubd vphsubsw vpinsrb vpinsrw vpinsrd vpinsrq vpmaddwd vpmaddubsw vpmaxsb vpmaxsw vpmaxsd vpmaxub vpmaxuw vpmaxud vpminsb vpminsw vpminsd vpminub vpminuw vpminud vpmovmskb vpmovsxbw vpmovsxbd vpmovsxbq vpmovsxwd vpmovsxwq vpmovsxdq vpmovzxbw vpmovzxbd vpmovzxbq vpmovzxwd vpmovzxwq vpmovzxdq vpmulhuw vpmulhrsw vpmulhw vpmullw vpmulld vpmuludq vpmuldq vpor vpsadbw vpshufb vpshufd vpshufhw vpshuflw vpsignb vpsignw vpsignd vpslldq vpsrldq vpsllw vpslld vpsllq vpsraw vpsrad vpsrlw vpsrld vpsrlq vptest vpsubb vpsubw vpsubd vpsubq vpsubsb vpsubsw vpsubusb vpsubusw vpunpckhbw vpunpckhwd vpunpckhdq vpunpckhqdq vpunpcklbw vpunpcklwd vpunpckldq vpunpcklqdq vpxor vrcpps vrcpss vrsqrtps vrsqrtss vroundpd vroundps vroundsd vroundss vshufpd vshufps vsqrtpd vsqrtps vsqrtsd vsqrtss vstmxcsr vsubpd vsubps vsubsd vsubss vtestps vtestpd vucomisd vucomiss vunpckhpd vunpckhps vunpcklpd vunpcklps vxorpd vxorps vzeroall vzeroupper pclmullqlqdq pclmulhqlqdq pclmullqhqdq pclmulhqhqdq pclmulqdq vpclmullqlqdq vpclmulhqlqdq vpclmullqhqdq vpclmulhqhqdq vpclmulqdq vfmadd132ps vfmadd132pd vfmadd312ps vfmadd312pd vfmadd213ps vfmadd213pd vfmadd123ps vfmadd123pd vfmadd231ps vfmadd231pd vfmadd321ps vfmadd321pd vfmaddsub132ps vfmaddsub132pd vfmaddsub312ps vfmaddsub312pd vfmaddsub213ps vfmaddsub213pd vfmaddsub123ps vfmaddsub123pd vfmaddsub231ps vfmaddsub231pd vfmaddsub321ps vfmaddsub321pd vfmsub132ps vfmsub132pd vfmsub312ps vfmsub312pd vfmsub213ps vfmsub213pd vfmsub123ps vfmsub123pd vfmsub231ps vfmsub231pd vfmsub321ps vfmsub321pd vfmsubadd132ps vfmsubadd132pd vfmsubadd312ps vfmsubadd312pd vfmsubadd213ps vfmsubadd213pd vfmsubadd123ps vfmsubadd123pd vfmsubadd231ps vfmsubadd231pd vfmsubadd321ps vfmsubadd321pd vfnmadd132ps vfnmadd132pd vfnmadd312ps vfnmadd312pd vfnmadd213ps vfnmadd213pd vfnmadd123ps vfnmadd123pd vfnmadd231ps vfnmadd231pd vfnmadd321ps vfnmadd321pd vfnmsub132ps vfnmsub132pd vfnmsub312ps vfnmsub312pd vfnmsub213ps vfnmsub213pd vfnmsub123ps vfnmsub123pd vfnmsub231ps vfnmsub231pd vfnmsub321ps vfnmsub321pd vfmadd132ss vfmadd132sd vfmadd312ss vfmadd312sd vfmadd213ss vfmadd213sd vfmadd123ss vfmadd123sd vfmadd231ss vfmadd231sd vfmadd321ss vfmadd321sd vfmsub132ss vfmsub132sd vfmsub312ss vfmsub312sd vfmsub213ss vfmsub213sd vfmsub123ss vfmsub123sd vfmsub231ss vfmsub231sd vfmsub321ss vfmsub321sd vfnmadd132ss vfnmadd132sd vfnmadd312ss vfnmadd312sd vfnmadd213ss vfnmadd213sd vfnmadd123ss vfnmadd123sd vfnmadd231ss vfnmadd231sd vfnmadd321ss vfnmadd321sd vfnmsub132ss vfnmsub132sd vfnmsub312ss vfnmsub312sd vfnmsub213ss vfnmsub213sd vfnmsub123ss vfnmsub123sd vfnmsub231ss vfnmsub231sd vfnmsub321ss vfnmsub321sd rdfsbase rdgsbase rdrand wrfsbase wrgsbase vcvtph2ps vcvtps2ph adcx adox rdseed clac stac xstore xcryptecb xcryptcbc xcryptctr xcryptcfb xcryptofb montmul xsha1 xsha256 llwpcb slwpcb lwpval lwpins vfmaddpd vfmaddps vfmaddsd vfmaddss vfmaddsubpd vfmaddsubps vfmsubaddpd vfmsubaddps vfmsubpd vfmsubps vfmsubsd vfmsubss vfnmaddpd vfnmaddps vfnmaddsd vfnmaddss vfnmsubpd vfnmsubps vfnmsubsd vfnmsubss vfrczpd vfrczps vfrczsd vfrczss vpcmov vpcomb vpcomd vpcomq vpcomub vpcomud vpcomuq vpcomuw vpcomw vphaddbd vphaddbq vphaddbw vphadddq vphaddubd vphaddubq vphaddubw vphaddudq vphadduwd vphadduwq vphaddwd vphaddwq vphsubbw vphsubdq vphsubwd vpmacsdd vpmacsdqh vpmacsdql vpmacssdd vpmacssdqh vpmacssdql vpmacsswd vpmacssww vpmacswd vpmacsww vpmadcsswd vpmadcswd vpperm vprotb vprotd vprotq vprotw vpshab vpshad vpshaq vpshaw vpshlb vpshld vpshlq vpshlw vbroadcasti128 vpblendd vpbroadcastb vpbroadcastw vpbroadcastd vpbroadcastq vpermd vpermpd vpermps vpermq vperm2i128 vextracti128 vinserti128 vpmaskmovd vpmaskmovq vpsllvd vpsllvq vpsravd vpsrlvd vpsrlvq vgatherdpd vgatherqpd vgatherdps vgatherqps vpgatherdd vpgatherqd vpgatherdq vpgatherqq xabort xbegin xend xtest andn bextr blci blcic blsi blsic blcfill blsfill blcmsk blsmsk blsr blcs bzhi mulx pdep pext rorx sarx shlx shrx tzcnt tzmsk t1mskc valignd valignq vblendmpd vblendmps vbroadcastf32x4 vbroadcastf64x4 vbroadcasti32x4 vbroadcasti64x4 vcompresspd vcompressps vcvtpd2udq vcvtps2udq vcvtsd2usi vcvtss2usi vcvttpd2udq vcvttps2udq vcvttsd2usi vcvttss2usi vcvtudq2pd vcvtudq2ps vcvtusi2sd vcvtusi2ss vexpandpd vexpandps vextractf32x4 vextractf64x4 vextracti32x4 vextracti64x4 vfixupimmpd vfixupimmps vfixupimmsd vfixupimmss vgetexppd vgetexpps vgetexpsd vgetexpss vgetmantpd vgetmantps vgetmantsd vgetmantss vinsertf32x4 vinsertf64x4 vinserti32x4 vinserti64x4 vmovdqa32 vmovdqa64 vmovdqu32 vmovdqu64 vpabsq vpandd vpandnd vpandnq vpandq vpblendmd vpblendmq vpcmpltd vpcmpled vpcmpneqd vpcmpnltd vpcmpnled vpcmpd vpcmpltq vpcmpleq vpcmpneqq vpcmpnltq vpcmpnleq vpcmpq vpcmpequd vpcmpltud vpcmpleud vpcmpnequd vpcmpnltud vpcmpnleud vpcmpud vpcmpequq vpcmpltuq vpcmpleuq vpcmpnequq vpcmpnltuq vpcmpnleuq vpcmpuq vpcompressd vpcompressq vpermi2d vpermi2pd vpermi2ps vpermi2q vpermt2d vpermt2pd vpermt2ps vpermt2q vpexpandd vpexpandq vpmaxsq vpmaxuq vpminsq vpminuq vpmovdb vpmovdw vpmovqb vpmovqd vpmovqw vpmovsdb vpmovsdw vpmovsqb vpmovsqd vpmovsqw vpmovusdb vpmovusdw vpmovusqb vpmovusqd vpmovusqw vpord vporq vprold vprolq vprolvd vprolvq vprord vprorq vprorvd vprorvq vpscatterdd vpscatterdq vpscatterqd vpscatterqq vpsraq vpsravq vpternlogd vpternlogq vptestmd vptestmq vptestnmd vptestnmq vpxord vpxorq vrcp14pd vrcp14ps vrcp14sd vrcp14ss vrndscalepd vrndscaleps vrndscalesd vrndscaless vrsqrt14pd vrsqrt14ps vrsqrt14sd vrsqrt14ss vscalefpd vscalefps vscalefsd vscalefss vscatterdpd vscatterdps vscatterqpd vscatterqps vshuff32x4 vshuff64x2 vshufi32x4 vshufi64x2 kandnw kandw kmovw knotw kortestw korw kshiftlw kshiftrw kunpckbw kxnorw kxorw vpbroadcastmb2q vpbroadcastmw2d vpconflictd vpconflictq vplzcntd vplzcntq vexp2pd vexp2ps vrcp28pd vrcp28ps vrcp28sd vrcp28ss vrsqrt28pd vrsqrt28ps vrsqrt28sd vrsqrt28ss vgatherpf0dpd vgatherpf0dps vgatherpf0qpd vgatherpf0qps vgatherpf1dpd vgatherpf1dps vgatherpf1qpd vgatherpf1qps vscatterpf0dpd vscatterpf0dps vscatterpf0qpd vscatterpf0qps vscatterpf1dpd vscatterpf1dps vscatterpf1qpd vscatterpf1qps prefetchwt1 bndmk bndcl bndcu bndcn bndmov bndldx bndstx sha1rnds4 sha1nexte sha1msg1 sha1msg2 sha256rnds2 sha256msg1 sha256msg2 hint_nop0 hint_nop1 hint_nop2 hint_nop3 hint_nop4 hint_nop5 hint_nop6 hint_nop7 hint_nop8 hint_nop9 hint_nop10 hint_nop11 hint_nop12 hint_nop13 hint_nop14 hint_nop15 hint_nop16 hint_nop17 hint_nop18 hint_nop19 hint_nop20 hint_nop21 hint_nop22 hint_nop23 hint_nop24 hint_nop25 hint_nop26 hint_nop27 hint_nop28 hint_nop29 hint_nop30 hint_nop31 hint_nop32 hint_nop33 hint_nop34 hint_nop35 hint_nop36 hint_nop37 hint_nop38 hint_nop39 hint_nop40 hint_nop41 hint_nop42 hint_nop43 hint_nop44 hint_nop45 hint_nop46 hint_nop47 hint_nop48 hint_nop49 hint_nop50 hint_nop51 hint_nop52 hint_nop53 hint_nop54 hint_nop55 hint_nop56 hint_nop57 hint_nop58 hint_nop59 hint_nop60 hint_nop61 hint_nop62 hint_nop63",literal:"ip eip rip al ah bl bh cl ch dl dh sil dil bpl spl r8b r9b r10b r11b r12b r13b r14b r15b ax bx cx dx si di bp sp r8w r9w r10w r11w r12w r13w r14w r15w eax ebx ecx edx esi edi ebp esp eip r8d r9d r10d r11d r12d r13d r14d r15d rax rbx rcx rdx rsi rdi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 cs ds es fs gs ss st st0 st1 st2 st3 st4 st5 st6 st7 mm0 mm1 mm2 mm3 mm4 mm5 mm6 mm7 xmm0 xmm1 xmm2 xmm3 xmm4 xmm5 xmm6 xmm7 xmm8 xmm9 xmm10 xmm11 xmm12 xmm13 xmm14 xmm15 xmm16 xmm17 xmm18 xmm19 xmm20 xmm21 xmm22 xmm23 xmm24 xmm25 xmm26 xmm27 xmm28 xmm29 xmm30 xmm31 ymm0 ymm1 ymm2 ymm3 ymm4 ymm5 ymm6 ymm7 ymm8 ymm9 ymm10 ymm11 ymm12 ymm13 ymm14 ymm15 ymm16 ymm17 ymm18 ymm19 ymm20 ymm21 ymm22 ymm23 ymm24 ymm25 ymm26 ymm27 ymm28 ymm29 ymm30 ymm31 zmm0 zmm1 zmm2 zmm3 zmm4 zmm5 zmm6 zmm7 zmm8 zmm9 zmm10 zmm11 zmm12 zmm13 zmm14 zmm15 zmm16 zmm17 zmm18 zmm19 zmm20 zmm21 zmm22 zmm23 zmm24 zmm25 zmm26 zmm27 zmm28 zmm29 zmm30 zmm31 k0 k1 k2 k3 k4 k5 k6 k7 bnd0 bnd1 bnd2 bnd3 cr0 cr1 cr2 cr3 cr4 cr8 dr0 dr1 dr2 dr3 dr8 tr3 tr4 tr5 tr6 tr7 r0 r1 r2 r3 r4 r5 r6 r7 r0b r1b r2b r3b r4b r5b r6b r7b r0w r1w r2w r3w r4w r5w r6w r7w r0d r1d r2d r3d r4d r5d r6d r7d r0h r1h r2h r3h r0l r1l r2l r3l r4l r5l r6l r7l r8l r9l r10l r11l r12l r13l r14l r15l",pseudo:"db dw dd dq dt ddq do dy dz resb resw resd resq rest resdq reso resy resz incbin equ times",preprocessor:"%define %xdefine %+ %undef %defstr %deftok %assign %strcat %strlen %substr %rotate %elif %else %endif %ifmacro %ifctx %ifidn %ifidni %ifid %ifnum %ifstr %iftoken %ifempty %ifenv %error %warning %fatal %rep %endrep %include %push %pop %repl %pathsearch %depend %use %arg %stacksize %local %line %comment %endcomment .nolist byte word dword qword nosplit rel abs seg wrt strict near far a32 ptr __FILE__ __LINE__ __SECT__ __BITS__ __OUTPUT_FORMAT__ __DATE__ __TIME__ __DATE_NUM__ __TIME_NUM__ __UTC_DATE__ __UTC_TIME__ __UTC_DATE_NUM__ __UTC_TIME_NUM__ __PASS__ struc endstruc istruc at iend align alignb sectalign daz nodaz up down zero default option assume public ",built_in:"bits use16 use32 use64 default section segment absolute extern global common cpu float __utf16__ __utf16le__ __utf16be__ __utf32__ __utf32le__ __utf32be__ __float8__ __float16__ __float32__ __float64__ __float80m__ __float80e__ __float128l__ __float128h__ __Infinity__ __QNaN__ __SNaN__ Inf NaN QNaN SNaN float8 float16 float32 float64 float80m float80e float128l float128h __FLOAT_DAZ__ __FLOAT_ROUND__ __FLOAT__"},c:[{cN:"comment",b:";",e:"$",r:0},{cN:"number",b:"\\b(?:([0-9][0-9_]*)?\\.[0-9_]*(?:[eE][+-]?[0-9_]+)?|(0[Xx])?[0-9][0-9_]*\\.?[0-9_]*(?:[pP](?:[+-]?[0-9_]+)?)?)\\b",r:0},{cN:"number",b:"\\$[0-9][0-9A-Fa-f]*",r:0},{cN:"number",b:"\\b(?:[0-9A-Fa-f][0-9A-Fa-f_]*[HhXx]|[0-9][0-9_]*[DdTt]?|[0-7][0-7_]*[QqOo]|[0-1][0-1_]*[BbYy])\\b"},{cN:"number",b:"\\b(?:0[HhXx][0-9A-Fa-f_]+|0[DdTt][0-9_]+|0[QqOo][0-7_]+|0[BbYy][0-1_]+)\\b"},a.QSM,{cN:"string",b:"'",e:"[^\\\\]'",r:0},{cN:"string",b:"`",e:"[^\\\\]`",r:0},{cN:"string",b:"\\.[A-Za-z0-9]+",r:0},{cN:"label",b:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)",r:0},{cN:"label",b:"^\\s*%%[A-Za-z0-9_$#@~.?]*:",r:0},{cN:"argument",b:"%[0-9]+",r:0},{cN:"built_in",b:"%!S+",r:0}]}});hljs.registerLanguage("python",function(a){var f={cN:"prompt",b:/^(>>>|\.\.\.) /};var b={cN:"string",c:[a.BE],v:[{b:/(u|b)?r?'''/,e:/'''/,c:[f],r:10},{b:/(u|b)?r?"""/,e:/"""/,c:[f],r:10},{b:/(u|r|ur)'/,e:/'/,r:10},{b:/(u|r|ur)"/,e:/"/,r:10},{b:/(b|br)'/,e:/'/},{b:/(b|br)"/,e:/"/},a.ASM,a.QSM]};var d={cN:"number",r:0,v:[{b:a.BNR+"[lLjJ]?"},{b:"\\b(0o[0-7]+)[lLjJ]?"},{b:a.CNR+"[lLjJ]?"}]};var e={cN:"params",b:/\(/,e:/\)/,c:["self",f,d,b]};var c={e:/:/,i:/[${=;\n]/,c:[a.UTM,e]};return{aliases:["py","gyp"],k:{keyword:"and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda nonlocal|10 None True False",built_in:"Ellipsis NotImplemented"},i:/(<\/|->|\?)/,c:[f,d,b,a.HCM,a.inherit(c,{cN:"function",bK:"def",r:10}),a.inherit(c,{cN:"class",bK:"class"}),{cN:"decorator",b:/@/,e:/$/},{b:/\b(print|exec)\(/}]}});hljs.registerLanguage("smalltalk",function(a){var b="[a-z][a-zA-Z0-9_]*";var d={cN:"char",b:"\\$.{1}"};var c={cN:"symbol",b:"#"+a.UIR};return{aliases:["st"],k:"self super nil true false thisContext",c:[{cN:"comment",b:'"',e:'"'},a.ASM,{cN:"class",b:"\\b[A-Z][A-Za-z0-9_]*",r:0},{cN:"method",b:b+":",r:0},a.CNM,c,d,{cN:"localvars",b:"\\|[ ]*"+b+"([ ]+"+b+")*[ ]*\\|",rB:true,e:/\|/,i:/\S/,c:[{b:"(\\|[ ]*)?"+b}]},{cN:"array",b:"\\#\\(",e:"\\)",c:[a.ASM,d,a.CNM,c]}]}});hljs.registerLanguage("tex",function(a){var d={cN:"command",b:"\\\\[a-zA-Zа-яА-я]+[\\*]?"};var c={cN:"command",b:"\\\\[^a-zA-Zа-яА-я0-9]"};var b={cN:"special",b:"[{}\\[\\]\\&#~]",r:0};return{c:[{b:"\\\\[a-zA-Zа-яА-я]+[\\*]? *= *-?\\d*\\.?\\d+(pt|pc|mm|cm|in|dd|cc|ex|em)?",rB:true,c:[d,c,{cN:"number",b:" *=",e:"-?\\d*\\.?\\d+(pt|pc|mm|cm|in|dd|cc|ex|em)?",eB:true}],r:10},d,c,b,{cN:"formula",b:"\\$\\$",e:"\\$\\$",c:[d,c,b],r:0},{cN:"formula",b:"\\$",e:"\\$",c:[d,c,b],r:0},{cN:"comment",b:"%",e:"$",r:0}]}});hljs.registerLanguage("actionscript",function(a){var c="[a-zA-Z_$][a-zA-Z0-9_$]*";var b="([*]|[a-zA-Z_$][a-zA-Z0-9_$]*)";var d={cN:"rest_arg",b:"[.]{3}",e:c,r:10};return{aliases:["as"],k:{keyword:"as break case catch class const continue default delete do dynamic each else extends final finally for function get if implements import in include instanceof interface internal is namespace native new override package private protected public return set static super switch this throw try typeof use var void while with",literal:"true false null undefined"},c:[a.ASM,a.QSM,a.CLCM,a.CBCM,a.CNM,{cN:"package",bK:"package",e:"{",c:[a.TM]},{cN:"class",bK:"class interface",e:"{",eE:true,c:[{bK:"extends implements"},a.TM]},{cN:"preprocessor",bK:"import include",e:";"},{cN:"function",bK:"function",e:"[{;]",eE:true,i:"\\S",c:[a.TM,{cN:"params",b:"\\(",e:"\\)",c:[a.ASM,a.QSM,a.CLCM,a.CBCM,d]},{cN:"type",b:":",e:b,r:10}]}]}});hljs.registerLanguage("sql",function(a){var b={cN:"comment",b:"--",e:"$"};return{cI:true,i:/[<>]/,c:[{cN:"operator",bK:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate savepoint release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup",e:/;/,eW:true,k:{keyword:"abs absolute acos action add adddate addtime aes_decrypt aes_encrypt after aggregate all allocate alter analyze and any are as asc ascii asin assertion at atan atan2 atn2 authorization authors avg backup before begin benchmark between bin binlog bit_and bit_count bit_length bit_or bit_xor both by cache call cascade cascaded case cast catalog ceil ceiling chain change changed char_length character_length charindex charset check checksum checksum_agg choose close coalesce coercibility collate collation collationproperty column columns columns_updated commit compress concat concat_ws concurrent connect connection connection_id consistent constraint constraints continue contributors conv convert convert_tz corresponding cos cot count count_big crc32 create cross cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime data database databases datalength date_add date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts datetimeoffsetfromparts day dayname dayofmonth dayofweek dayofyear deallocate declare decode default deferrable deferred degrees delayed delete des_decrypt des_encrypt des_key_file desc describe descriptor diagnostics difference disconnect distinct distinctrow div do domain double drop dumpfile each else elt enclosed encode encrypt end end-exec engine engines eomonth errors escape escaped event eventdata events except exception exec execute exists exp explain export_set extended external extract fast fetch field fields find_in_set first first_value floor flush for force foreign format found found_rows from from_base64 from_days from_unixtime full function get get_format get_lock getdate getutcdate global go goto grant grants greatest group group_concat grouping grouping_id gtid_subset gtid_subtract handler having help hex high_priority hosts hour ident_current ident_incr ident_seed identified identity if ifnull ignore iif ilike immediate in index indicator inet6_aton inet6_ntoa inet_aton inet_ntoa infile initially inner innodb input insert install instr intersect into is is_free_lock is_ipv4 is_ipv4_compat is_ipv4_mapped is_not is_not_null is_used_lock isdate isnull isolation join key kill language last last_day last_insert_id last_value lcase lead leading least leaves left len lenght level like limit lines ln load load_file local localtime localtimestamp locate lock log log10 log2 logfile logs low_priority lower lpad ltrim make_set makedate maketime master master_pos_wait match matched max md5 medium merge microsecond mid min minute mod mode module month monthname mutex name_const names national natural nchar next no no_write_to_binlog not now nullif nvarchar oct octet_length of old_password on only open optimize option optionally or ord order outer outfile output pad parse partial partition password patindex percent_rank percentile_cont percentile_disc period_add period_diff pi plugin position pow power pragma precision prepare preserve primary prior privileges procedure procedure_analyze processlist profile profiles public publishingservername purge quarter query quick quote quotename radians rand read references regexp relative relaylog release release_lock rename repair repeat replace replicate reset restore restrict return returns reverse revoke right rlike rollback rollup round row row_count rows rpad rtrim savepoint schema scroll sec_to_time second section select serializable server session session_user set sha sha1 sha2 share show sign sin size slave sleep smalldatetimefromparts snapshot some soname soundex sounds_like space sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_no_cache sql_small_result sql_variant_property sqlstate sqrt square start starting status std stddev stddev_pop stddev_samp stdev stdevp stop str str_to_date straight_join strcmp string stuff subdate substr substring subtime subtring_index sum switchoffset sysdate sysdatetime sysdatetimeoffset system_user sysutcdatetime table tables tablespace tan temporary terminated tertiary_weights then time time_format time_to_sec timediff timefromparts timestamp timestampadd timestampdiff timezone_hour timezone_minute to to_base64 to_days to_seconds todatetimeoffset trailing transaction translation trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse ucase uncompress uncompressed_length unhex unicode uninstall union unique unix_timestamp unknown unlock update upgrade upped upper usage use user user_resources using utc_date utc_time utc_timestamp uuid uuid_short validate_password_strength value values var var_pop var_samp variables variance varp version view warnings week weekday weekofyear weight_string when whenever where with work write xml xor year yearweek zon",literal:"true false null",built_in:"array bigint binary bit blob boolean char character date dec decimal float int integer interval number numeric real serial smallint varchar varying int8 serial8 text"},c:[{cN:"string",b:"'",e:"'",c:[a.BE,{b:"''"}]},{cN:"string",b:'"',e:'"',c:[a.BE,{b:'""'}]},{cN:"string",b:"`",e:"`",c:[a.BE]},a.CNM,a.CBCM,b]},a.CBCM,b]}});hljs.registerLanguage("nix",function(b){var a={keyword:"rec with let in inherit assert if else then",constant:"true false or and null",built_in:"import abort baseNameOf dirOf isNull builtins map removeAttrs throw toString derivation"};var g={cN:"subst",b:/\$\{/,e:/\}/,k:a};var d={cN:"variable",b:/[a-zA-Z0-9-_]+(\s*=)/};var e={cN:"string",b:"''",e:"''",c:[g]};var f={cN:"string",b:'"',e:'"',c:[g]};var c=[b.NM,b.HCM,b.CBCM,e,f,d];g.c=c;return{aliases:["nixos"],k:a,c:c}});hljs.registerLanguage("handlebars",function(b){var a="each in with if else unless bindattr action collection debugger log outlet template unbound view yield";return{aliases:["hbs","html.hbs","html.handlebars"],cI:true,sL:"xml",subLanguageMode:"continuous",c:[{cN:"expression",b:"{{",e:"}}",c:[{cN:"begin-block",b:"#[a-zA-Z- .]+",k:a},{cN:"string",b:'"',e:'"'},{cN:"end-block",b:"\\/[a-zA-Z- .]+",k:a},{cN:"variable",b:"[a-zA-Z-.]+",k:a}]}]}});hljs.registerLanguage("thrift",function(a){var b="bool byte i16 i32 i64 double string binary";return{k:{keyword:"namespace const typedef struct enum service exception void oneway set list map required optional",built_in:b,literal:"true false"},c:[a.QSM,a.NM,a.CLCM,a.CBCM,{cN:"class",bK:"struct enum service exception",e:/\{/,i:/\n/,c:[a.inherit(a.TM,{starts:{eW:true,eE:true}})]},{cN:"stl_container",b:"\\b(set|list|map)\\s*<",e:">",k:b,c:["self"]}]}});hljs.registerLanguage("vala",function(a){return{k:{keyword:"char uchar unichar int uint long ulong short ushort int8 int16 int32 int64 uint8 uint16 uint32 uint64 float double bool struct enum string void weak unowned owned async signal static abstract interface override while do for foreach else switch case break default return try catch public private protected internal using new this get set const stdout stdin stderr var",built_in:"DBus GLib CCode Gee Object",literal:"false true null"},c:[{cN:"class",bK:"class interface delegate namespace",e:"{",eE:true,i:"[^,:\\n\\s\\.]",c:[a.UTM]},a.CLCM,a.CBCM,{cN:"string",b:'"""',e:'"""',r:5},a.ASM,a.QSM,a.CNM,{cN:"preprocessor",b:"^#",e:"$",r:2},{cN:"constant",b:" [A-Z_]+ ",r:0}]}});hljs.registerLanguage("gradle",function(a){return{cI:true,k:{keyword:"task project allprojects subprojects artifacts buildscript configurations dependencies repositories sourceSets description delete from into include exclude source classpath destinationDir includes options sourceCompatibility targetCompatibility group flatDir doLast doFirst flatten todir fromdir ant def abstract break case catch continue default do else extends final finally for if implements instanceof native new private protected public return static switch synchronized throw throws transient try volatile while strictfp package import false null super this true antlrtask checkstyle codenarc copy boolean byte char class double float int interface long short void compile runTime file fileTree abs any append asList asWritable call collect compareTo count div dump each eachByte eachFile eachLine every find findAll flatten getAt getErr getIn getOut getText grep immutable inject inspect intersect invokeMethods isCase join leftShift minus multiply newInputStream newOutputStream newPrintWriter newReader newWriter next plus pop power previous print println push putAt read readBytes readLines reverse reverseEach round size sort splitEachLine step subMap times toInteger toList tokenize upto waitForOrKill withPrintWriter withReader withStream withWriter withWriterAppend write writeLine"},c:[a.CLCM,a.CBCM,a.ASM,a.QSM,a.NM,a.RM]}});hljs.registerLanguage("ini",function(a){return{cI:true,i:/\S/,c:[{cN:"comment",b:";",e:"$"},{cN:"title",b:"^\\[",e:"\\]"},{cN:"setting",b:"^[a-z0-9\\[\\]_-]+[ \\t]*=[ \\t]*",e:"$",c:[{cN:"value",eW:true,k:"on off true false yes no",c:[a.QSM,a.NM],r:0}]}]}});hljs.registerLanguage("livecodeserver",function(a){var e={cN:"variable",b:"\\b[gtps][A-Z]+[A-Za-z0-9_\\-]*\\b|\\$_[A-Z]+",r:0};var b={cN:"comment",e:"$",v:[a.CBCM,a.HCM,{b:"--"},{b:"[^:]//"}]};var d=a.inherit(a.TM,{v:[{b:"\\b_*rig[A-Z]+[A-Za-z0-9_\\-]*"},{b:"\\b_[a-z0-9\\-]+"}]});var c=a.inherit(a.TM,{b:"\\b([A-Za-z0-9_\\-]+)\\b"});return{cI:false,k:{keyword:"after byte bytes english the until http forever descending using line real8 with seventh for stdout finally element word fourth before black ninth sixth characters chars stderr uInt1 uInt1s uInt2 uInt2s stdin string lines relative rel any fifth items from middle mid at else of catch then third it file milliseconds seconds second secs sec int1 int1s int4 int4s internet int2 int2s normal text item last long detailed effective uInt4 uInt4s repeat end repeat URL in try into switch to words https token binfile each tenth as ticks tick system real4 by dateItems without char character ascending eighth whole dateTime numeric short first ftp integer abbreviated abbr abbrev private case while if",constant:"SIX TEN FORMFEED NINE ZERO NONE SPACE FOUR FALSE COLON CRLF PI COMMA ENDOFFILE EOF EIGHT FIVE QUOTE EMPTY ONE TRUE RETURN CR LINEFEED RIGHT BACKSLASH NULL SEVEN TAB THREE TWO six ten formfeed nine zero none space four false colon crlf pi comma endoffile eof eight five quote empty one true return cr linefeed right backslash null seven tab three two RIVERSION RISTATE FILE_READ_MODE FILE_WRITE_MODE FILE_WRITE_MODE DIR_WRITE_MODE FILE_READ_UMASK FILE_WRITE_UMASK DIR_READ_UMASK DIR_WRITE_UMASK",operator:"div mod wrap and or bitAnd bitNot bitOr bitXor among not in a an within contains ends with begins the keys of keys",built_in:"put abs acos aliasReference annuity arrayDecode arrayEncode asin atan atan2 average avg base64Decode base64Encode baseConvert binaryDecode binaryEncode byteToNum cachedURL cachedURLs charToNum cipherNames commandNames compound compress constantNames cos date dateFormat decompress directories diskSpace DNSServers exp exp1 exp2 exp10 extents files flushEvents folders format functionNames global globals hasMemory hostAddress hostAddressToName hostName hostNameToAddress isNumber ISOToMac itemOffset keys len length libURLErrorData libUrlFormData libURLftpCommand libURLLastHTTPHeaders libURLLastRHHeaders libUrlMultipartFormAddPart libUrlMultipartFormData libURLVersion lineOffset ln ln1 localNames log log2 log10 longFilePath lower macToISO matchChunk matchText matrixMultiply max md5Digest median merge millisec millisecs millisecond milliseconds min monthNames num number numToByte numToChar offset open openfiles openProcesses openProcessIDs openSockets paramCount param params peerAddress pendingMessages platform processID random randomBytes replaceText result revCreateXMLTree revCreateXMLTreeFromFile revCurrentRecord revCurrentRecordIsFirst revCurrentRecordIsLast revDatabaseColumnCount revDatabaseColumnIsNull revDatabaseColumnLengths revDatabaseColumnNames revDatabaseColumnNamed revDatabaseColumnNumbered revDatabaseColumnTypes revDatabaseConnectResult revDatabaseCursors revDatabaseID revDatabaseTableNames revDatabaseType revDataFromQuery revdb_closeCursor revdb_columnbynumber revdb_columncount revdb_columnisnull revdb_columnlengths revdb_columnnames revdb_columntypes revdb_commit revdb_connect revdb_connections revdb_connectionerr revdb_currentrecord revdb_cursorconnection revdb_cursorerr revdb_cursors revdb_dbtype revdb_disconnect revdb_execute revdb_iseof revdb_isbof revdb_movefirst revdb_movelast revdb_movenext revdb_moveprev revdb_query revdb_querylist revdb_recordcount revdb_rollback revdb_tablenames revGetDatabaseDriverPath revNumberOfRecords revOpenDatabase revOpenDatabases revQueryDatabase revQueryDatabaseBlob revQueryResult revQueryIsAtStart revQueryIsAtEnd revUnixFromMacPath revXMLAttribute revXMLAttributes revXMLAttributeValues revXMLChildContents revXMLChildNames revXMLFirstChild revXMLMatchingNode revXMLNextSibling revXMLNodeContents revXMLNumberOfChildren revXMLParent revXMLPreviousSibling revXMLRootNode revXMLRPC_CreateRequest revXMLRPC_Documents revXMLRPC_Error revXMLRPC_Execute revXMLRPC_GetHost revXMLRPC_GetMethod revXMLRPC_GetParam revXMLText revXMLRPC_GetParamCount revXMLRPC_GetParamNode revXMLRPC_GetParamType revXMLRPC_GetPath revXMLRPC_GetPort revXMLRPC_GetProtocol revXMLRPC_GetRequest revXMLRPC_GetResponse revXMLRPC_GetSocket revXMLTree revXMLTrees revXMLValidateDTD revZipDescribeItem revZipEnumerateItems revZipOpenArchives round sec secs seconds sha1Digest shell shortFilePath sin specialFolderPath sqrt standardDeviation statRound stdDev sum sysError systemVersion tan tempName tick ticks time to toLower toUpper transpose trunc uniDecode uniEncode upper URLDecode URLEncode URLStatus value variableNames version waitDepth weekdayNames wordOffset add breakpoint cancel clear local variable file word line folder directory URL close socket process combine constant convert create new alias folder directory decrypt delete variable word line folder directory URL dispatch divide do encrypt filter get include intersect kill libURLDownloadToFile libURLFollowHttpRedirects libURLftpUpload libURLftpUploadFile libURLresetAll libUrlSetAuthCallback libURLSetCustomHTTPHeaders libUrlSetExpect100 libURLSetFTPListCommand libURLSetFTPMode libURLSetFTPStopTime libURLSetStatusCallback load multiply socket process post seek rel relative read from process rename replace require resetAll revAddXMLNode revAppendXML revCloseCursor revCloseDatabase revCommitDatabase revCopyFile revCopyFolder revCopyXMLNode revDeleteFolder revDeleteXMLNode revDeleteAllXMLTrees revDeleteXMLTree revExecuteSQL revGoURL revInsertXMLNode revMoveFolder revMoveToFirstRecord revMoveToLastRecord revMoveToNextRecord revMoveToPreviousRecord revMoveToRecord revMoveXMLNode revPutIntoXMLNode revRollBackDatabase revSetDatabaseDriverPath revSetXMLAttribute revXMLRPC_AddParam revXMLRPC_DeleteAllDocuments revXMLAddDTD revXMLRPC_Free revXMLRPC_FreeAll revXMLRPC_DeleteDocument revXMLRPC_DeleteParam revXMLRPC_SetHost revXMLRPC_SetMethod revXMLRPC_SetPort revXMLRPC_SetProtocol revXMLRPC_SetSocket revZipAddItemWithData revZipAddItemWithFile revZipAddUncompressedItemWithData revZipAddUncompressedItemWithFile revZipCancel revZipCloseArchive revZipDeleteItem revZipExtractItemToFile revZipExtractItemToVariable revZipSetProgressCallback revZipRenameItem revZipReplaceItemWithData revZipReplaceItemWithFile revZipOpenArchive send set sort split subtract union unload wait write"},c:[e,{cN:"keyword",b:"\\bend\\sif\\b"},{cN:"function",bK:"function",e:"$",c:[e,c,a.ASM,a.QSM,a.BNM,a.CNM,d]},{cN:"function",bK:"end",e:"$",c:[c,d]},{cN:"command",bK:"command on",e:"$",c:[e,c,a.ASM,a.QSM,a.BNM,a.CNM,d]},{cN:"command",bK:"end",e:"$",c:[c,d]},{cN:"preprocessor",b:"<\\?rev|<\\?lc|<\\?livecode",r:10},{cN:"preprocessor",b:"<\\?"},{cN:"preprocessor",b:"\\?>"},b,a.ASM,a.QSM,a.BNM,a.CNM,d],i:";$|^\\[|^="}});hljs.registerLanguage("d",function(x){var b={keyword:"abstract alias align asm assert auto body break byte case cast catch class const continue debug default delete deprecated do else enum export extern final finally for foreach foreach_reverse|10 goto if immutable import in inout int interface invariant is lazy macro mixin module new nothrow out override package pragma private protected public pure ref return scope shared static struct super switch synchronized template this throw try typedef typeid typeof union unittest version void volatile while with __FILE__ __LINE__ __gshared|10 __thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ __VERSION__",built_in:"bool cdouble cent cfloat char creal dchar delegate double dstring float function idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar wstring",literal:"false null true"};var c="(0|[1-9][\\d_]*)",q="(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)",h="0[bB][01_]+",v="([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)",y="0[xX]"+v,p="([eE][+-]?"+q+")",o="("+q+"(\\.\\d*|"+p+")|\\d+\\."+q+q+"|\\."+c+p+"?)",k="(0[xX]("+v+"\\."+v+"|\\.?"+v+")[pP][+-]?"+q+")",l="("+c+"|"+h+"|"+y+")",n="("+k+"|"+o+")";var z="\\\\(['\"\\?\\\\abfnrtv]|u[\\dA-Fa-f]{4}|[0-7]{1,3}|x[\\dA-Fa-f]{2}|U[\\dA-Fa-f]{8})|&[a-zA-Z\\d]{2,};";var m={cN:"number",b:"\\b"+l+"(L|u|U|Lu|LU|uL|UL)?",r:0};var j={cN:"number",b:"\\b("+n+"([fF]|L|i|[fF]i|Li)?|"+l+"(i|[fF]i|Li))",r:0};var s={cN:"string",b:"'("+z+"|.)",e:"'",i:"."};var r={b:z,r:0};var w={cN:"string",b:'"',c:[r],e:'"[cwd]?'};var f={cN:"string",b:'[rq]"',e:'"[cwd]?',r:5};var u={cN:"string",b:"`",e:"`[cwd]?"};var i={cN:"string",b:'x"[\\da-fA-F\\s\\n\\r]*"[cwd]?',r:10};var t={cN:"string",b:'q"\\{',e:'\\}"'};var e={cN:"shebang",b:"^#!",e:"$",r:5};var g={cN:"preprocessor",b:"#(line)",e:"$",r:5};var d={cN:"keyword",b:"@[a-zA-Z_][a-zA-Z_\\d]*"};var a={cN:"comment",b:"\\/\\+",c:["self"],e:"\\+\\/",r:10};return{l:x.UIR,k:b,c:[x.CLCM,x.CBCM,a,i,w,f,u,t,j,m,s,e,g,d]}});hljs.registerLanguage("vbnet",function(a){return{aliases:["vb"],cI:true,k:{keyword:"addhandler addressof alias and andalso aggregate ansi as assembly auto binary by byref byval call case catch class compare const continue custom declare default delegate dim distinct do each equals else elseif end enum erase error event exit explicit finally for friend from function get global goto group handles if implements imports in inherits interface into is isfalse isnot istrue join key let lib like loop me mid mod module mustinherit mustoverride mybase myclass namespace narrowing new next not notinheritable notoverridable of off on operator option optional or order orelse overloads overridable overrides paramarray partial preserve private property protected public raiseevent readonly redim rem removehandler resume return select set shadows shared skip static step stop structure strict sub synclock take text then throw to try unicode until using when where while widening with withevents writeonly xor",built_in:"boolean byte cbool cbyte cchar cdate cdec cdbl char cint clng cobj csbyte cshort csng cstr ctype date decimal directcast double gettype getxmlnamespace iif integer long object sbyte short single string trycast typeof uinteger ulong ushort",literal:"true false nothing"},i:"//|{|}|endif|gosub|variant|wend",c:[a.inherit(a.QSM,{c:[{b:'""'}]}),{cN:"comment",b:"'",e:"$",rB:true,c:[{cN:"xmlDocTag",b:"'''|"},{cN:"xmlDocTag",b:""}]},a.CNM,{cN:"preprocessor",b:"#",e:"$",k:"if else elseif end region externalsource"}]}});hljs.registerLanguage("axapta",function(a){return{k:"false int abstract private char boolean static null if for true while long throw finally protected final return void enum else break new catch byte super case short default double public try this switch continue reverse firstfast firstonly forupdate nofetch sum avg minof maxof count order group by asc desc index hint like dispaly edit client server ttsbegin ttscommit str real date container anytype common div mod",c:[a.CLCM,a.CBCM,a.ASM,a.QSM,a.CNM,{cN:"preprocessor",b:"#",e:"$"},{cN:"class",bK:"class interface",e:"{",eE:true,i:":",c:[{cN:"inheritance",bK:"extends implements",r:10},a.UTM]}]}});hljs.registerLanguage("perl",function(c){var d="getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qqfileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmgetsub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedirioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when";var f={cN:"subst",b:"[$@]\\{",e:"\\}",k:d};var g={b:"->{",e:"}"};var a={cN:"variable",v:[{b:/\$\d/},{b:/[\$\%\@](\^\w\b|#\w+(\:\:\w+)*|{\w+}|\w+(\:\:\w*)*)/},{b:/[\$\%\@][^\s\w{]/,r:0}]};var e={cN:"comment",b:"^(__END__|__DATA__)",e:"\\n$",r:5};var h=[c.BE,f,a];var b=[a,c.HCM,e,{cN:"comment",b:"^\\=\\w",e:"\\=cut",eW:true},g,{cN:"string",c:h,v:[{b:"q[qwxr]?\\s*\\(",e:"\\)",r:5},{b:"q[qwxr]?\\s*\\[",e:"\\]",r:5},{b:"q[qwxr]?\\s*\\{",e:"\\}",r:5},{b:"q[qwxr]?\\s*\\|",e:"\\|",r:5},{b:"q[qwxr]?\\s*\\<",e:"\\>",r:5},{b:"qw\\s+q",e:"q",r:5},{b:"'",e:"'",c:[c.BE]},{b:'"',e:'"'},{b:"`",e:"`",c:[c.BE]},{b:"{\\w+}",c:[],r:0},{b:"-?\\w+\\s*\\=\\>",c:[],r:0}]},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{b:"(\\/\\/|"+c.RSR+"|\\b(split|return|print|reverse|grep)\\b)\\s*",k:"split return print reverse grep",r:0,c:[c.HCM,e,{cN:"regexp",b:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",r:10},{cN:"regexp",b:"(m|qr)?/",e:"/[a-z]*",c:[c.BE],r:0}]},{cN:"sub",bK:"sub",e:"(\\s*\\(.*?\\))?[;{]",r:5},{cN:"operator",b:"-\\w\\b",r:0}];f.c=b;g.c=b;return{aliases:["pl"],k:d,c:b}});hljs.registerLanguage("scala",function(a){var c={cN:"annotation",b:"@[A-Za-z]+"};var b={cN:"string",b:'u?r?"""',e:'"""',r:10};var d={cN:"symbol",b:"'\\w[\\w\\d_]*(?!')"};return{k:"type yield lazy override def with val var false true sealed abstract private trait object null if for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws",c:[{cN:"javadoc",b:"/\\*\\*",e:"\\*/",c:[{cN:"javadoctag",b:"@[A-Za-z]+"}],r:10},a.CLCM,a.CBCM,b,a.QSM,d,{cN:"class",b:"((case )?class |object |trait )",e:"({|$)",eE:true,i:":",k:"case class trait object",c:[{bK:"extends with",r:10},a.UTM,{cN:"params",b:"\\(",e:"\\)",c:[a.QSM,b,c]}]},a.CNM,c]}});hljs.registerLanguage("cmake",function(a){return{aliases:["cmake.in"],cI:true,k:{keyword:"add_custom_command add_custom_target add_definitions add_dependencies add_executable add_library add_subdirectory add_test aux_source_directory break build_command cmake_minimum_required cmake_policy configure_file create_test_sourcelist define_property else elseif enable_language enable_testing endforeach endfunction endif endmacro endwhile execute_process export find_file find_library find_package find_path find_program fltk_wrap_ui foreach function get_cmake_property get_directory_property get_filename_component get_property get_source_file_property get_target_property get_test_property if include include_directories include_external_msproject include_regular_expression install link_directories load_cache load_command macro mark_as_advanced message option output_required_files project qt_wrap_cpp qt_wrap_ui remove_definitions return separate_arguments set set_directory_properties set_property set_source_files_properties set_target_properties set_tests_properties site_name source_group string target_link_libraries try_compile try_run unset variable_watch while build_name exec_program export_library_dependencies install_files install_programs install_targets link_libraries make_directory remove subdir_depends subdirs use_mangled_mesa utility_source variable_requires write_file qt5_use_modules qt5_use_package qt5_wrap_cpp on off true false and or",operator:"equal less greater strless strgreater strequal matches"},c:[{cN:"envvar",b:"\\${",e:"}"},a.HCM,a.QSM,a.NM]}});hljs.registerLanguage("ocaml",function(a){return{aliases:["ml"],k:{keyword:"and as assert asr begin class constraint do done downto else end exception external false for fun function functor if in include inherit initializer land lazy let lor lsl lsr lxor match method mod module mutable new object of open or private rec ref sig struct then to true try type val virtual when while with parser value",built_in:"bool char float int list unit array exn option int32 int64 nativeint format4 format6 lazy_t in_channel out_channel string"},i:/\/\//,c:[{cN:"string",b:'"""',e:'"""'},{cN:"comment",b:"\\(\\*",e:"\\*\\)",c:["self"]},{cN:"class",bK:"type",e:"\\(|=|$",eE:true,c:[a.UTM]},{cN:"annotation",b:"\\[<",e:">\\]"},a.CBCM,a.inherit(a.ASM,{i:null}),a.inherit(a.QSM,{i:null}),a.CNM]}});hljs.registerLanguage("autohotkey",function(b){var d={cN:"escape",b:"`[\\s\\S]"};var c={cN:"comment",b:";",e:"$",r:0};var a=[{cN:"built_in",b:"A_[a-zA-Z0-9]+"},{cN:"built_in",bK:"ComSpec Clipboard ClipboardAll ErrorLevel"}];return{cI:true,k:{keyword:"Break Continue Else Gosub If Loop Return While",literal:"A true false NOT AND OR"},c:a.concat([d,b.inherit(b.QSM,{c:[d]}),c,{cN:"number",b:b.NR,r:0},{cN:"var_expand",b:"%",e:"%",i:"\\n",c:[d]},{cN:"label",c:[d],v:[{b:'^[^\\n";]+::(?!=)'},{b:'^[^\\n";]+:(?!=)',r:0}]},{b:",\\s*,",r:10}])}});hljs.registerLanguage("objectivec",function(a){var d={keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"NSString NSDictionary CGRect CGPoint UIButton UILabel UITextView UIWebView MKMapView NSView NSViewController NSWindow NSWindowController NSSet NSUUID NSIndexSet UISegmentedControl NSObject UITableViewDelegate UITableViewDataSource NSThread UIActivityIndicator UITabbar UIToolBar UIBarButtonItem UIImageView NSAutoreleasePool UITableView BOOL NSInteger CGFloat NSException NSLog NSMutableString NSMutableArray NSMutableDictionary NSURL NSIndexPath CGSize UITableViewCell UIView UIViewController UINavigationBar UINavigationController UITabBarController UIPopoverController UIPopoverControllerDelegate UIImage NSNumber UISearchBar NSFetchedResultsController NSFetchedResultsChangeType UIScrollView UIScrollViewDelegate UIEdgeInsets UIColor UIFont UIApplication NSNotFound NSNotificationCenter NSNotification UILocalNotification NSBundle NSFileManager NSTimeInterval NSDate NSCalendar NSUserDefaults UIWindow NSRange NSArray NSError NSURLRequest NSURLConnection UIInterfaceOrientation MPMoviePlayerController dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"};var c=/[a-zA-Z@][a-zA-Z0-9_]*/;var b="@interface @class @protocol @implementation";return{aliases:["m","mm","objc","obj-c"],k:d,l:c,i:""}]}]},{cN:"class",b:"("+b.split(" ").join("|")+")\\b",e:"({|$)",eE:true,k:b,l:c,c:[a.UTM]},{cN:"variable",b:"\\."+a.UIR,r:0}]}});hljs.registerLanguage("avrasm",function(a){return{cI:true,l:"\\.?"+a.IR,k:{keyword:"adc add adiw and andi asr bclr bld brbc brbs brcc brcs break breq brge brhc brhs brid brie brlo brlt brmi brne brpl brsh brtc brts brvc brvs bset bst call cbi cbr clc clh cli cln clr cls clt clv clz com cp cpc cpi cpse dec eicall eijmp elpm eor fmul fmuls fmulsu icall ijmp in inc jmp ld ldd ldi lds lpm lsl lsr mov movw mul muls mulsu neg nop or ori out pop push rcall ret reti rjmp rol ror sbc sbr sbrc sbrs sec seh sbi sbci sbic sbis sbiw sei sen ser ses set sev sez sleep spm st std sts sub subi swap tst wdr",built_in:"r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 r16 r17 r18 r19 r20 r21 r22 r23 r24 r25 r26 r27 r28 r29 r30 r31 x|0 xh xl y|0 yh yl z|0 zh zl ucsr1c udr1 ucsr1a ucsr1b ubrr1l ubrr1h ucsr0c ubrr0h tccr3c tccr3a tccr3b tcnt3h tcnt3l ocr3ah ocr3al ocr3bh ocr3bl ocr3ch ocr3cl icr3h icr3l etimsk etifr tccr1c ocr1ch ocr1cl twcr twdr twar twsr twbr osccal xmcra xmcrb eicra spmcsr spmcr portg ddrg ping portf ddrf sreg sph spl xdiv rampz eicrb eimsk gimsk gicr eifr gifr timsk tifr mcucr mcucsr tccr0 tcnt0 ocr0 assr tccr1a tccr1b tcnt1h tcnt1l ocr1ah ocr1al ocr1bh ocr1bl icr1h icr1l tccr2 tcnt2 ocr2 ocdr wdtcr sfior eearh eearl eedr eecr porta ddra pina portb ddrb pinb portc ddrc pinc portd ddrd pind spdr spsr spcr udr0 ucsr0a ucsr0b ubrr0l acsr admux adcsr adch adcl porte ddre pine pinf",preprocessor:".byte .cseg .db .def .device .dseg .dw .endmacro .equ .eseg .exit .include .list .listmac .macro .nolist .org .set"},c:[a.CBCM,{cN:"comment",b:";",e:"$",r:0},a.CNM,a.BNM,{cN:"number",b:"\\b(\\$[a-zA-Z0-9]+|0o[0-7]+)"},a.QSM,{cN:"string",b:"'",e:"[^\\\\]'",i:"[^\\\\][^']"},{cN:"label",b:"^[A-Za-z0-9_.$]+:"},{cN:"preprocessor",b:"#",e:"$"},{cN:"localvars",b:"@[0-9]+"}]}});hljs.registerLanguage("vhdl",function(a){return{cI:true,k:{keyword:"abs access after alias all and architecture array assert attribute begin block body buffer bus case component configuration constant context cover disconnect downto default else elsif end entity exit fairness file for force function generate generic group guarded if impure in inertial inout is label library linkage literal loop map mod nand new next nor not null of on open or others out package port postponed procedure process property protected pure range record register reject release rem report restrict restrict_guarantee return rol ror select sequence severity shared signal sla sll sra srl strong subtype then to transport type unaffected units until use variable vmode vprop vunit wait when while with xnor xor",typename:"boolean bit character severity_level integer time delay_length natural positive string bit_vector file_open_kind file_open_status std_ulogic std_ulogic_vector std_logic std_logic_vector unsigned signed boolean_vector integer_vector real_vector time_vector"},i:"{",c:[a.CBCM,{cN:"comment",b:"--",e:"$"},a.QSM,a.CNM,{cN:"literal",b:"'(U|X|0|1|Z|W|L|H|-)'",c:[a.BE]},{cN:"attribute",b:"'[A-Za-z](_?[A-Za-z0-9])*",c:[a.BE]}]}});hljs.registerLanguage("coffeescript",function(c){var b={keyword:"in if for while finally new do return else break catch instanceof throw try this switch continue typeof delete debugger super then unless until loop of by when and or is isnt not",literal:"true false null undefined yes no on off",reserved:"case default function var void with const let enum export import native __hasProp __extends __slice __bind __indexOf",built_in:"npm require console print module global window document"};var a="[A-Za-z$_][0-9A-Za-z$_]*";var f=c.inherit(c.TM,{b:a});var e={cN:"subst",b:/#\{/,e:/}/,k:b};var d=[c.BNM,c.inherit(c.CNM,{starts:{e:"(\\s*/)?",r:0}}),{cN:"string",v:[{b:/'''/,e:/'''/,c:[c.BE]},{b:/'/,e:/'/,c:[c.BE]},{b:/"""/,e:/"""/,c:[c.BE,e]},{b:/"/,e:/"/,c:[c.BE,e]}]},{cN:"regexp",v:[{b:"///",e:"///",c:[e,c.HCM]},{b:"//[gim]*",r:0},{b:"/\\S(\\\\.|[^\\n])*?/[gim]*(?=\\s|\\W|$)"}]},{cN:"property",b:"@"+a},{b:"`",e:"`",eB:true,eE:true,sL:"javascript"}];e.c=d;return{aliases:["coffee","cson","iced"],k:b,c:d.concat([{cN:"comment",b:"###",e:"###"},c.HCM,{cN:"function",b:"("+a+"\\s*=\\s*)?(\\(.*\\))?\\s*\\B[-=]>",e:"[-=]>",rB:true,c:[f,{cN:"params",b:"\\(",rB:true,c:[{b:/\(/,e:/\)/,k:b,c:["self"].concat(d)}]}]},{cN:"class",bK:"class",e:"$",i:/[:="\[\]]/,c:[{bK:"extends",eW:true,i:/[:="\[\]]/,c:[f]},f]},{cN:"attribute",b:a+":",e:":",rB:true,eE:true,r:0}])}});hljs.registerLanguage("mizar",function(a){return{k:["environ vocabularies notations constructors definitions registrations theorems schemes requirements","begin end definition registration cluster existence pred func defpred deffunc theorem proof","let take assume then thus hence ex for st holds consider reconsider such that and in provided of as from","be being by means equals implies iff redefine define now not or attr is mode suppose per cases set","thesis contradiction scheme reserve struct","correctness compatibility coherence symmetry assymetry reflexivity irreflexivity","connectedness uniqueness commutativity idempotence involutiveness projectivity"].join(" "),c:[{cN:"comment",b:"::",e:"$"}]}});hljs.registerLanguage("nginx",function(c){var b={cN:"variable",v:[{b:/\$\d+/},{b:/\$\{/,e:/}/},{b:"[\\$\\@]"+c.UIR}]};var a={eW:true,l:"[a-z/_]+",k:{built_in:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},r:0,i:"=>",c:[c.HCM,{cN:"string",c:[c.BE,b],v:[{b:/"/,e:/"/},{b:/'/,e:/'/}]},{cN:"url",b:"([a-z]+):/",e:"\\s",eW:true,eE:true},{cN:"regexp",c:[c.BE,b],v:[{b:"\\s\\^",e:"\\s|{|;",rE:true},{b:"~\\*?\\s+",e:"\\s|{|;",rE:true},{b:"\\*(\\.[a-z\\-]+)+"},{b:"([a-z\\-]+\\.)+\\*"}]},{cN:"number",b:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{cN:"number",b:"\\b\\d+[kKmMgGdshdwy]*\\b",r:0},b]};return{aliases:["nginxconf"],c:[c.HCM,{b:c.UIR+"\\s",e:";|{",rB:true,c:[{cN:"title",b:c.UIR,starts:a}],r:0}],i:"[^\\s\\}]"}});hljs.registerLanguage("erlang-repl",function(a){return{k:{special_functions:"spawn spawn_link self",reserved:"after and andalso|10 band begin bnot bor bsl bsr bxor case catch cond div end fun if let not of or orelse|10 query receive rem try when xor"},c:[{cN:"prompt",b:"^[0-9]+> ",r:10},{cN:"comment",b:"%",e:"$"},{cN:"number",b:"\\b(\\d+#[a-fA-F0-9]+|\\d+(\\.\\d+)?([eE][-+]?\\d+)?)",r:0},a.ASM,a.QSM,{cN:"constant",b:"\\?(::)?([A-Z]\\w*(::)?)+"},{cN:"arrow",b:"->"},{cN:"ok",b:"ok"},{cN:"exclamation_mark",b:"!"},{cN:"function_or_atom",b:"(\\b[a-z'][a-zA-Z0-9_']*:[a-z'][a-zA-Z0-9_']*)|(\\b[a-z'][a-zA-Z0-9_']*)",r:0},{cN:"variable",b:"[A-Z][a-zA-Z0-9_']*",r:0}]}});hljs.registerLanguage("r",function(a){var b="([a-zA-Z]|\\.[a-zA-Z.])[a-zA-Z0-9._]*";return{c:[a.HCM,{b:b,l:b,k:{keyword:"function if in break next repeat else for return switch while try tryCatch|10 stop warning require library attach detach source setMethod setGeneric setGroupGeneric setClass ...|10",literal:"NULL NA TRUE FALSE T F Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10"},r:0},{cN:"number",b:"0[xX][0-9a-fA-F]+[Li]?\\b",r:0},{cN:"number",b:"\\d+(?:[eE][+\\-]?\\d*)?L\\b",r:0},{cN:"number",b:"\\d+\\.(?!\\d)(?:i\\b)?",r:0},{cN:"number",b:"\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?i?\\b",r:0},{cN:"number",b:"\\.\\d+(?:[eE][+\\-]?\\d*)?i?\\b",r:0},{b:"`",e:"`",r:0},{cN:"string",c:[a.BE],v:[{b:'"',e:'"'},{b:"'",e:"'"}]}]}});hljs.registerLanguage("json",function(a){var e={literal:"true false null"};var d=[a.QSM,a.CNM];var c={cN:"value",e:",",eW:true,eE:true,c:d,k:e};var b={b:"{",e:"}",c:[{cN:"attribute",b:'\\s*"',e:'"\\s*:\\s*',eB:true,eE:true,c:[a.BE],i:"\\n",starts:c}],i:"\\S"};var f={b:"\\[",e:"\\]",c:[a.inherit(c,{cN:null})],i:"\\S"};d.splice(d.length,0,b,f);return{c:d,k:e,i:"\\S"}});hljs.registerLanguage("django",function(a){var b={cN:"filter",b:/\|[A-Za-z]+\:?/,k:"truncatewords removetags linebreaksbr yesno get_digit timesince random striptags filesizeformat escape linebreaks length_is ljust rjust cut urlize fix_ampersands title floatformat capfirst pprint divisibleby add make_list unordered_list urlencode timeuntil urlizetrunc wordcount stringformat linenumbers slice date dictsort dictsortreversed default_if_none pluralize lower join center default truncatewords_html upper length phone2numeric wordwrap time addslashes slugify first escapejs force_escape iriencode last safe safeseq truncatechars localize unlocalize localtime utc timezone",c:[{cN:"argument",b:/"/,e:/"/},{cN:"argument",b:/'/,e:/'/}]};return{aliases:["jinja"],cI:true,sL:"xml",subLanguageMode:"continuous",c:[{cN:"template_comment",b:/\{%\s*comment\s*%}/,e:/\{%\s*endcomment\s*%}/},{cN:"template_comment",b:/\{#/,e:/#}/},{cN:"template_tag",b:/\{%/,e:/%}/,k:"comment endcomment load templatetag ifchanged endifchanged if endif firstof for endfor in ifnotequal endifnotequal widthratio extends include spaceless endspaceless regroup by as ifequal endifequal ssi now with cycle url filter endfilter debug block endblock else autoescape endautoescape csrf_token empty elif endwith static trans blocktrans endblocktrans get_static_prefix get_media_prefix plural get_current_language language get_available_languages get_current_language_bidi get_language_info get_language_info_list localize endlocalize localtime endlocaltime timezone endtimezone get_current_timezone verbatim",c:[b]},{cN:"variable",b:/\{\{/,e:/}}/,c:[b]}]}});hljs.registerLanguage("delphi",function(b){var a="exports register file shl array record property for mod while set ally label uses raise not stored class safecall var interface or private static exit index inherited to else stdcall override shr asm far resourcestring finalization packed virtual out and protected library do xorwrite goto near function end div overload object unit begin string on inline repeat until destructor write message program with read initialization except default nil if case cdecl in downto threadvar of try pascal const external constructor type public then implementation finally published procedure";var e={cN:"comment",v:[{b:/\{/,e:/\}/,r:0},{b:/\(\*/,e:/\*\)/,r:10}]};var c={cN:"string",b:/'/,e:/'/,c:[{b:/''/}]};var d={cN:"string",b:/(#\d+)+/};var f={b:b.IR+"\\s*=\\s*class\\s*\\(",rB:true,c:[b.TM]};var g={cN:"function",bK:"function constructor destructor procedure",e:/[:;]/,k:"function constructor|10 destructor|10 procedure|10",c:[b.TM,{cN:"params",b:/\(/,e:/\)/,k:a,c:[c,d]},e]};return{cI:true,k:a,i:/("|\$[G-Zg-z]|\/\*|<\/)/,c:[e,b.CLCM,c,d,b.NM,f,g]}});hljs.registerLanguage("vbscript",function(a){return{aliases:["vbs"],cI:true,k:{keyword:"call class const dim do loop erase execute executeglobal exit for each next function if then else on error option explicit new private property let get public randomize redim rem select case set stop sub while wend with end to elseif is or xor and not class_initialize class_terminate default preserve in me byval byref step resume goto",built_in:"lcase month vartype instrrev ubound setlocale getobject rgb getref string weekdayname rnd dateadd monthname now day minute isarray cbool round formatcurrency conversions csng timevalue second year space abs clng timeserial fixs len asc isempty maths dateserial atn timer isobject filter weekday datevalue ccur isdate instr datediff formatdatetime replace isnull right sgn array snumeric log cdbl hex chr lbound msgbox ucase getlocale cos cdate cbyte rtrim join hour oct typename trim strcomp int createobject loadpicture tan formatnumber mid scriptenginebuildversion scriptengine split scriptengineminorversion cint sin datepart ltrim sqr scriptenginemajorversion time derived eval date formatpercent exp inputbox left ascw chrw regexp server response request cstr err",literal:"true false null nothing empty"},i:"//",c:[a.inherit(a.QSM,{c:[{b:'""'}]}),{cN:"comment",b:/'/,e:/$/,r:0},a.CNM]}});hljs.registerLanguage("oxygene",function(b){var g="abstract add and array as asc aspect assembly async begin break block by case class concat const copy constructor continue create default delegate desc distinct div do downto dynamic each else empty end ensure enum equals event except exit extension external false final finalize finalizer finally flags for forward from function future global group has if implementation implements implies in index inherited inline interface into invariants is iterator join locked locking loop matching method mod module namespace nested new nil not notify nullable of old on operator or order out override parallel params partial pinned private procedure property protected public queryable raise read readonly record reintroduce remove repeat require result reverse sealed select self sequence set shl shr skip static step soft take then to true try tuple type union unit unsafe until uses using var virtual raises volatile where while with write xor yield await mapped deprecated stdcall cdecl pascal register safecall overload library platform reference packed strict published autoreleasepool selector strong weak unretained";var a={cN:"comment",b:"{",e:"}",r:0};var e={cN:"comment",b:"\\(\\*",e:"\\*\\)",r:10};var c={cN:"string",b:"'",e:"'",c:[{b:"''"}]};var d={cN:"string",b:"(#\\d+)+"};var f={cN:"function",bK:"function constructor destructor procedure method",e:"[:;]",k:"function constructor|10 destructor|10 procedure|10 method|10",c:[b.TM,{cN:"params",b:"\\(",e:"\\)",k:g,c:[c,d]},a,e]};return{cI:true,k:g,i:'("|\\$[G-Zg-z]|\\/\\*|"},{cN:"keyword",b:/\w+/,r:0,k:{common:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{e:/$/,r:0,k:{literal:"on off all"},c:[{cN:"sqbracket",b:"\\s\\[",e:"\\]$"},{cN:"cbracket",b:"[\\$%]\\{",e:"\\}",c:["self",b]},b,a.QSM]}}],i:/\S/}});hljs.registerLanguage("scss",function(a){var c="[a-zA-Z-][a-zA-Z0-9_-]*";var f={cN:"variable",b:"(\\$"+c+")\\b"};var d={cN:"function",b:c+"\\(",rB:true,eE:true,e:"\\("};var b={cN:"hexcolor",b:"#[0-9A-Fa-f]+"};var e={cN:"attribute",b:"[A-Z\\_\\.\\-]+",e:":",eE:true,i:"[^\\s]",starts:{cN:"value",eW:true,eE:true,c:[d,b,a.CSSNM,a.QSM,a.ASM,a.CBCM,{cN:"important",b:"!important"}]}};return{cI:true,i:"[=/|']",c:[a.CLCM,a.CBCM,d,{cN:"id",b:"\\#[A-Za-z0-9_-]+",r:0},{cN:"class",b:"\\.[A-Za-z0-9_-]+",r:0},{cN:"attr_selector",b:"\\[",e:"\\]",i:"$"},{cN:"tag",b:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",r:0},{cN:"pseudo",b:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{cN:"pseudo",b:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},f,{cN:"attribute",b:"\\b(z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",i:"[^\\s]"},{cN:"value",b:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{cN:"value",b:":",e:";",c:[d,f,b,a.CSSNM,a.QSM,a.ASM,{cN:"important",b:"!important"}]},{cN:"at_rule",b:"@",e:"[{;]",k:"mixin include extend for if else each while charset import debug media page content font-face namespace warn",c:[d,f,a.QSM,a.ASM,b,a.CSSNM,{cN:"preprocessor",b:"\\s[A-Za-z0-9_.-]+",r:0}]}]}});hljs.registerLanguage("monkey",function(a){var b={v:[{cN:"number",b:"[$][a-fA-F0-9]+"},a.NM]};return{cI:true,k:{keyword:"public private property continue exit extern new try catch eachin not abstract final select case default const local global field end if then else elseif endif while wend repeat until forever for to step next return module inline throw",built_in:"DebugLog DebugStop Error Print ACos ACosr ASin ASinr ATan ATan2 ATan2r ATanr Abs Abs Ceil Clamp Clamp Cos Cosr Exp Floor Log Max Max Min Min Pow Sgn Sgn Sin Sinr Sqrt Tan Tanr Seed PI HALFPI TWOPI",literal:"true false null and or shl shr mod"},c:[{cN:"comment",b:"#rem",e:"#end"},{cN:"comment",b:"'",e:"$",r:0},{cN:"function",bK:"function method",e:"[(=:]|$",i:/\n/,c:[a.UTM,]},{cN:"class",bK:"class interface",e:"$",c:[{bK:"extends implements"},a.UTM]},{cN:"variable",b:"\\b(self|super)\\b"},{cN:"preprocessor",bK:"import",e:"$"},{cN:"preprocessor",b:"\\s*#",e:"$",k:"if else elseif endif end then"},{cN:"pi",b:"^\\s*strict\\b"},{bK:"alias",e:"=",c:[a.UTM]},a.QSM,b]}});hljs.registerLanguage("applescript",function(a){var b=a.inherit(a.QSM,{i:""});var d={cN:"params",b:"\\(",e:"\\)",c:["self",a.CNM,b]};var c=[{cN:"comment",b:"--",e:"$"},{cN:"comment",b:"\\(\\*",e:"\\*\\)",c:["self",{b:"--",e:"$"}]},a.HCM];return{aliases:["osascript"],k:{keyword:"about above after against and around as at back before beginning behind below beneath beside between but by considering contain contains continue copy div does eighth else end equal equals error every exit fifth first for fourth from front get given global if ignoring in into is it its last local me middle mod my ninth not of on onto or over prop property put ref reference repeat returning script second set seventh since sixth some tell tenth that the|0 then third through thru timeout times to transaction try until where while whose with without",constant:"AppleScript false linefeed return pi quote result space tab true",type:"alias application boolean class constant date file integer list number real record string text",command:"activate beep count delay launch log offset read round run say summarize write",property:"character characters contents day frontmost id item length month name paragraph paragraphs rest reverse running time version weekday word words year"},c:[b,a.CNM,{cN:"type",b:"\\bPOSIX file\\b"},{cN:"command",b:"\\b(clipboard info|the clipboard|info for|list (disks|folder)|mount volume|path to|(close|open for) access|(get|set) eof|current date|do shell script|get volume settings|random number|set volume|system attribute|system info|time to GMT|(load|run|store) script|scripting components|ASCII (character|number)|localized string|choose (application|color|file|file name|folder|from list|remote application|URL)|display (alert|dialog))\\b|^\\s*return\\b"},{cN:"constant",b:"\\b(text item delimiters|current application|missing value)\\b"},{cN:"keyword",b:"\\b(apart from|aside from|instead of|out of|greater than|isn't|(doesn't|does not) (equal|come before|come after|contain)|(greater|less) than( or equal)?|(starts?|ends|begins?) with|contained by|comes (before|after)|a (ref|reference))\\b"},{cN:"property",b:"\\b(POSIX path|(date|time) string|quoted form)\\b"},{cN:"function_start",bK:"on",i:"[${=;\\n]",c:[a.UTM,d]}].concat(c),i:"//"}});hljs.registerLanguage("lasso",function(d){var b="[a-zA-Z_][a-zA-Z0-9_.]*";var i="<\\?(lasso(script)?|=)";var c="\\]|\\?>";var g={literal:"true false none minimal full all void and or not bw nbw ew new cn ncn lt lte gt gte eq neq rx nrx ft",built_in:"array date decimal duration integer map pair string tag xml null bytes list queue set stack staticarray tie local var variable global data self inherited",keyword:"error_code error_msg error_pop error_push error_reset cache database_names database_schemanames database_tablenames define_tag define_type email_batch encode_set html_comment handle handle_error header if inline iterate ljax_target link link_currentaction link_currentgroup link_currentrecord link_detail link_firstgroup link_firstrecord link_lastgroup link_lastrecord link_nextgroup link_nextrecord link_prevgroup link_prevrecord log loop namespace_using output_none portal private protect records referer referrer repeating resultset rows search_args search_arguments select sort_args sort_arguments thread_atomic value_list while abort case else if_empty if_false if_null if_true loop_abort loop_continue loop_count params params_up return return_value run_children soap_definetag soap_lastrequest soap_lastresponse tag_name ascending average by define descending do equals frozen group handle_failure import in into join let match max min on order parent protected provide public require returnhome skip split_thread sum take thread to trait type where with yield yieldhome"};var a={cN:"comment",b:"",r:0};var j={cN:"preprocessor",b:"\\[noprocess\\]",starts:{cN:"markup",e:"\\[/noprocess\\]",rE:true,c:[a]}};var e={cN:"preprocessor",b:"\\[/noprocess|"+i};var h={cN:"variable",b:"'"+b+"'"};var f=[d.CLCM,{cN:"javadoc",b:"/\\*\\*!",e:"\\*/",c:[d.PWM]},d.CBCM,d.inherit(d.CNM,{b:d.CNR+"|-?(infinity|nan)\\b"}),d.inherit(d.ASM,{i:null}),d.inherit(d.QSM,{i:null}),{cN:"string",b:"`",e:"`"},{cN:"variable",v:[{b:"[#$]"+b},{b:"#",e:"\\d+",i:"\\W"}]},{cN:"tag",b:"::\\s*",e:b,i:"\\W"},{cN:"attribute",v:[{b:"-"+d.UIR,r:0},{b:"(\\.\\.\\.)"}]},{cN:"subst",v:[{b:"->\\s*",c:[h]},{b:":=|/(?!\\w)=?|[-+*%=<>&|!?\\\\]+",r:0}]},{cN:"built_in",b:"\\.\\.?",r:0,c:[h]},{cN:"class",bK:"define",rE:true,e:"\\(|=>",c:[d.inherit(d.TM,{b:d.UIR+"(=(?!>))?"})]}];return{aliases:["ls","lassoscript"],cI:true,l:b+"|&[lg]t;",k:g,c:[{cN:"preprocessor",b:c,r:0,starts:{cN:"markup",e:"\\[|"+i,rE:true,r:0,c:[a]}},j,e,{cN:"preprocessor",b:"\\[no_square_brackets",starts:{e:"\\[/no_square_brackets\\]",l:b+"|&[lg]t;",k:g,c:[{cN:"preprocessor",b:c,r:0,starts:{cN:"markup",e:i,rE:true,c:[a]}},j,e].concat(f)}},{cN:"preprocessor",b:"\\[",r:0},{cN:"shebang",b:"^#!.+lasso9\\b",r:10}].concat(f)}});hljs.registerLanguage("cpp",function(a){var b={keyword:"false int float while private char catch export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const struct for static_cast|10 union namespace unsigned long throw volatile static protected bool template mutable if public friend do return goto auto void enum else break new extern using true class asm case typeid short reinterpret_cast|10 default double register explicit signed typename try this switch continue wchar_t inline delete alignof char16_t char32_t constexpr decltype noexcept nullptr static_assert thread_local restrict _Bool complex _Complex _Imaginary",built_in:"std string cin cout cerr clog stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap array shared_ptr abort abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf"};return{aliases:["c","h","c++","h++"],k:b,i:""]',k:"include",i:"\\n"},a.CLCM]},{cN:"stl_container",b:"\\b(deque|list|queue|stack|vector|map|set|bitset|multiset|multimap|unordered_map|unordered_set|unordered_multiset|unordered_multimap|array)\\s*<",e:">",k:b,c:["self"]},{b:a.IR+"::"}]}});hljs.registerLanguage("matlab",function(a){var b=[a.CNM,{cN:"string",b:"'",e:"'",c:[a.BE,{b:"''"}]}];return{k:{keyword:"break case catch classdef continue else elseif end enumerated events for function global if methods otherwise parfor persistent properties return spmd switch try while",built_in:"sin sind sinh asin asind asinh cos cosd cosh acos acosd acosh tan tand tanh atan atand atan2 atanh sec secd sech asec asecd asech csc cscd csch acsc acscd acsch cot cotd coth acot acotd acoth hypot exp expm1 log log1p log10 log2 pow2 realpow reallog realsqrt sqrt nthroot nextpow2 abs angle complex conj imag real unwrap isreal cplxpair fix floor ceil round mod rem sign airy besselj bessely besselh besseli besselk beta betainc betaln ellipj ellipke erf erfc erfcx erfinv expint gamma gammainc gammaln psi legendre cross dot factor isprime primes gcd lcm rat rats perms nchoosek factorial cart2sph cart2pol pol2cart sph2cart hsv2rgb rgb2hsv zeros ones eye repmat rand randn linspace logspace freqspace meshgrid accumarray size length ndims numel disp isempty isequal isequalwithequalnans cat reshape diag blkdiag tril triu fliplr flipud flipdim rot90 find sub2ind ind2sub bsxfun ndgrid permute ipermute shiftdim circshift squeeze isscalar isvector ans eps realmax realmin pi i inf nan isnan isinf isfinite j why compan gallery hadamard hankel hilb invhilb magic pascal rosser toeplitz vander wilkinson"},i:'(//|"|#|/\\*|\\s+/\\w+)',c:[{cN:"function",bK:"function",e:"$",c:[a.UTM,{cN:"params",b:"\\(",e:"\\)"},{cN:"params",b:"\\[",e:"\\]"}]},{cN:"transposed_variable",b:"[a-zA-Z_][a-zA-Z_0-9]*('+[\\.']*|[\\.']+)",e:"",r:0},{cN:"matrix",b:"\\[",e:"\\]'*[\\.']*",c:b,r:0},{cN:"cell",b:"\\{",c:b,i:/:/,v:[{e:/\}'[\.']*/},{e:/\}/,r:0}]},{cN:"comment",b:"\\%",e:"$"}].concat(b)}});hljs.registerLanguage("scilab",function(a){var b=[a.CNM,{cN:"string",b:"'|\"",e:"'|\"",c:[a.BE,{b:"''"}]}];return{aliases:["sci"],k:{keyword:"abort break case clear catch continue do elseif else endfunction end for functionglobal if pause return resume select try then while%f %F %t %T %pi %eps %inf %nan %e %i %z %s",built_in:"abs and acos asin atan ceil cd chdir clearglobal cosh cos cumprod deff disp errorexec execstr exists exp eye gettext floor fprintf fread fsolve imag isdef isemptyisinfisnan isvector lasterror length load linspace list listfiles log10 log2 logmax min msprintf mclose mopen ones or pathconvert poly printf prod pwd rand realround sinh sin size gsort sprintf sqrt strcat strcmps tring sum system tanh tantype typename warning zeros matrix"},i:'("|#|/\\*|\\s+/\\w+)',c:[{cN:"function",bK:"function endfunction",e:"$",k:"function endfunction|10",c:[a.UTM,{cN:"params",b:"\\(",e:"\\)"}]},{cN:"transposed_variable",b:"[a-zA-Z_][a-zA-Z_0-9]*('+[\\.']*|[\\.']+)",e:"",r:0},{cN:"matrix",b:"\\[",e:"\\]'*[\\.']*",r:0,c:b},{cN:"comment",b:"//",e:"$"}].concat(b)}});hljs.registerLanguage("makefile",function(a){var b={cN:"variable",b:/\$\(/,e:/\)/,c:[a.BE]};return{aliases:["mk","mak"],c:[a.HCM,{b:/^\w+\s*\W*=/,rB:true,r:0,starts:{cN:"constant",e:/\s*\W*=/,eE:true,starts:{e:/$/,r:0,c:[b]}}},{cN:"title",b:/^[\w]+:\s*$/},{cN:"phony",b:/^\.PHONY:/,e:/$/,k:".PHONY",l:/[\.\w]+/},{b:/^\t+/,e:/$/,c:[a.QSM,b]}]}});hljs.registerLanguage("asciidoc",function(a){return{c:[{cN:"comment",b:"^/{4,}\\n",e:"\\n/{4,}$",r:10},{cN:"comment",b:"^//",e:"$",r:0},{cN:"title",b:"^\\.\\w.*$"},{b:"^[=\\*]{4,}\\n",e:"\\n^[=\\*]{4,}$",r:10},{cN:"header",b:"^(={1,5}) .+?( \\1)?$",r:10},{cN:"header",b:"^[^\\[\\]\\n]+?\\n[=\\-~\\^\\+]{2,}$",r:10},{cN:"attribute",b:"^:.+?:",e:"\\s",eE:true,r:10},{cN:"attribute",b:"^\\[.+?\\]$",r:0},{cN:"blockquote",b:"^_{4,}\\n",e:"\\n_{4,}$",r:10},{cN:"code",b:"^[\\-\\.]{4,}\\n",e:"\\n[\\-\\.]{4,}$",r:10},{b:"^\\+{4,}\\n",e:"\\n\\+{4,}$",c:[{b:"<",e:">",sL:"xml",r:0}],r:10},{cN:"bullet",b:"^(\\*+|\\-+|\\.+|[^\\n]+?::)\\s+"},{cN:"label",b:"^(NOTE|TIP|IMPORTANT|WARNING|CAUTION):\\s+",r:10},{cN:"strong",b:"\\B\\*(?![\\*\\s])",e:"(\\n{2}|\\*)",c:[{b:"\\\\*\\w",r:0}]},{cN:"emphasis",b:"\\B'(?!['\\s])",e:"(\\n{2}|')",c:[{b:"\\\\'\\w",r:0}],r:0},{cN:"emphasis",b:"_(?![_\\s])",e:"(\\n{2}|_)",r:0},{cN:"smartquote",b:"``.+?''",r:10},{cN:"smartquote",b:"`.+?'",r:10},{cN:"code",b:"(`.+?`|\\+.+?\\+)",r:0},{cN:"code",b:"^[ \\t]",e:"$",r:0},{cN:"horizontal_rule",b:"^'{3,}[ \\t]*$",r:10},{b:"(link:)?(http|https|ftp|file|irc|image:?):\\S+\\[.*?\\]",rB:true,c:[{b:"(link|image:?):",r:0},{cN:"link_url",b:"\\w",e:"[^\\[]+",r:0},{cN:"link_label",b:"\\[",e:"\\]",eB:true,eE:true,r:0}],r:10}]}});hljs.registerLanguage("parser3",function(a){return{sL:"xml",r:0,c:[{cN:"comment",b:"^#",e:"$"},{cN:"comment",b:"\\^rem{",e:"}",r:10,c:[{b:"{",e:"}",c:["self"]}]},{cN:"preprocessor",b:"^@(?:BASE|USE|CLASS|OPTIONS)$",r:10},{cN:"title",b:"@[\\w\\-]+\\[[\\w^;\\-]*\\](?:\\[[\\w^;\\-]*\\])?(?:.*)$"},{cN:"variable",b:"\\$\\{?[\\w\\-\\.\\:]+\\}?"},{cN:"keyword",b:"\\^[\\w\\-\\.\\:]+"},{cN:"number",b:"\\^#[0-9a-fA-F]+"},a.CNM]}});hljs.registerLanguage("clojure",function(l){var e={built_in:"def cond apply if-not if-let if not not= = < < > <= <= >= == + / * - rem quot neg? pos? delay? symbol? keyword? true? false? integer? empty? coll? list? set? ifn? fn? associative? sequential? sorted? counted? reversible? number? decimal? class? distinct? isa? float? rational? reduced? ratio? odd? even? char? seq? vector? string? map? nil? contains? zero? instance? not-every? not-any? libspec? -> ->> .. . inc compare do dotimes mapcat take remove take-while drop letfn drop-last take-last drop-while while intern condp case reduced cycle split-at split-with repeat replicate iterate range merge zipmap declare line-seq sort comparator sort-by dorun doall nthnext nthrest partition eval doseq await await-for let agent atom send send-off release-pending-sends add-watch mapv filterv remove-watch agent-error restart-agent set-error-handler error-handler set-error-mode! error-mode shutdown-agents quote var fn loop recur throw try monitor-enter monitor-exit defmacro defn defn- macroexpand macroexpand-1 for dosync and or when when-not when-let comp juxt partial sequence memoize constantly complement identity assert peek pop doto proxy defstruct first rest cons defprotocol cast coll deftype defrecord last butlast sigs reify second ffirst fnext nfirst nnext defmulti defmethod meta with-meta ns in-ns create-ns import refer keys select-keys vals key val rseq name namespace promise into transient persistent! conj! assoc! dissoc! pop! disj! use class type num float double short byte boolean bigint biginteger bigdec print-method print-dup throw-if printf format load compile get-in update-in pr pr-on newline flush read slurp read-line subvec with-open memfn time re-find re-groups rand-int rand mod locking assert-valid-fdecl alias resolve ref deref refset swap! reset! set-validator! compare-and-set! alter-meta! reset-meta! commute get-validator alter ref-set ref-history-count ref-min-history ref-max-history ensure sync io! new next conj set! to-array future future-call into-array aset gen-class reduce map filter find empty hash-map hash-set sorted-map sorted-map-by sorted-set sorted-set-by vec vector seq flatten reverse assoc dissoc list disj get union difference intersection extend extend-type extend-protocol int nth delay count concat chunk chunk-buffer chunk-append chunk-first chunk-rest max min dec unchecked-inc-int unchecked-inc unchecked-dec-inc unchecked-dec unchecked-negate unchecked-add-int unchecked-add unchecked-subtract-int unchecked-subtract chunk-next chunk-cons chunked-seq? prn vary-meta lazy-seq spread list* str find-keyword keyword symbol gensym force rationalize"};var f="[a-zA-Z_0-9\\!\\.\\?\\-\\+\\*\\/\\<\\=\\>\\&\\#\\$';]+";var a="[\\s:\\(\\{]+\\d+(\\.\\d+)?";var d={cN:"number",b:a,r:0};var j=l.inherit(l.QSM,{i:null});var o={cN:"comment",b:";",e:"$",r:0};var n={cN:"collection",b:"[\\[\\{]",e:"[\\]\\}]"};var c={cN:"comment",b:"\\^"+f};var b={cN:"comment",b:"\\^\\{",e:"\\}"};var h={cN:"attribute",b:"[:]"+f};var m={cN:"list",b:"\\(",e:"\\)"};var g={eW:true,k:{literal:"true false nil"},r:0};var i={k:e,l:f,cN:"title",b:f,starts:g};m.c=[{cN:"comment",b:"comment"},i,g];g.c=[m,j,c,b,o,h,n,d];n.c=[m,j,c,o,h,n,d];return{aliases:["clj"],i:/\S/,c:[o,m,{cN:"prompt",b:/^=> /,starts:{e:/\n\n|\Z/}}]}});hljs.registerLanguage("elixir",function(e){var f="[a-zA-Z_][a-zA-Z0-9_]*(\\!|\\?)?";var g="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?";var i="and false then defined module in return redo retry end for true self when next until do begin unless nil break not case cond alias while ensure or include use alias fn quote";var c={cN:"subst",b:"#\\{",e:"}",l:f,k:i};var d={cN:"string",c:[e.BE,c],v:[{b:/'/,e:/'/},{b:/"/,e:/"/}]};var b={eW:true,rE:true,l:f,k:i,r:0};var h={cN:"function",bK:"def defmacro",e:/\bdo\b/,c:[e.inherit(e.TM,{b:g,starts:b})]};var j=e.inherit(h,{cN:"class",bK:"defmodule defrecord",e:/\bdo\b|$|;/});var a=[d,e.HCM,j,h,{cN:"constant",b:"(\\b[A-Z_]\\w*(.)?)+",r:0},{cN:"symbol",b:":",c:[d,{b:g}],r:0},{cN:"symbol",b:f+":",r:0},{cN:"number",b:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",r:0},{cN:"variable",b:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{b:"->"},{b:"("+e.RSR+")\\s*",c:[e.HCM,{cN:"regexp",i:"\\n",c:[e.BE,c],v:[{b:"/",e:"/[a-z]*"},{b:"%r\\[",e:"\\][a-z]*"}]}],r:0}];c.c=a;b.c=a;return{l:f,k:i,c:a}});hljs.registerLanguage("typescript",function(a){return{aliases:["ts"],k:{keyword:"in if for while finally var new function|0 do return void else break catch instanceof with throw case default try this switch continue typeof delete let yield const class public private get set super interface extendsstatic constructor implements enum export import declare",literal:"true false null undefined NaN Infinity",built_in:"eval isFinite isNaN parseFloat parseInt decodeURI decodeURIComponent encodeURI encodeURIComponent escape unescape Object Function Boolean Error EvalError InternalError RangeError ReferenceError StopIteration SyntaxError TypeError URIError Number Math Date String RegExp Array Float32Array Float64Array Int16Array Int32Array Int8Array Uint16Array Uint32Array Uint8Array Uint8ClampedArray ArrayBuffer DataView JSON Intl arguments require module console window document any number boolean string void",},c:[{cN:"pi",b:/^\s*('|")use strict('|")/,r:0},a.ASM,a.QSM,a.CLCM,a.CBCM,a.CNM,{b:"("+a.RSR+"|\\b(case|return|throw)\\b)\\s*",k:"return throw case",c:[a.CLCM,a.CBCM,a.RM,{b:/;/,r:0,sL:"xml"}],r:0},{cN:"function",bK:"function",e:/\{/,eE:true,c:[a.inherit(a.TM,{b:/[A-Za-z$_][0-9A-Za-z$_]*/}),{cN:"params",b:/\(/,e:/\)/,c:[a.CLCM,a.CBCM],i:/["'\(]/}],i:/\[|%/,r:0},{cN:"constructor",bK:"constructor",e:/\{/,eE:true,r:10},{cN:"module",bK:"module",e:/\{/,eE:true,},{cN:"interface",bK:"interface",e:/\{/,eE:true,},{b:/\$[(.]/},{b:"\\."+a.IR,r:0}]}});hljs.registerLanguage("go",function(a){var b={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer",constant:"true false iota nil",typename:"bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{aliases:["golang"],k:b,i:""))&&g.css("position",a.css("position"));B=function(){var c,e,l;if(!E&&(c=parseInt(f.css("border-top-width"),10),e=parseInt(f.css("padding-top"),10),d=parseInt(f.css("padding-bottom"),10),q=f.offset().top+c+e,z=f.height(),m&&(u=m=!1,null==n&&(a.insertAfter(g),g.detach()),a.css({position:"",top:"",width:"",bottom:""}).removeClass(s),l=!0),D=a.offset().top-parseInt(a.css("margin-top"),10)-p,t=a.outerHeight(!0),r=a.css("float"),g&&g.css({width:a.outerWidth(!0), +height:t,display:a.css("display"),"vertical-align":a.css("vertical-align"),"float":r}),l))return b()};B();if(t!==z)return A=void 0,c=p,x=C,b=function(){var b,k,l,h;if(!E&&(null!=x&&(--x,0>=x&&(x=C,B())),l=e.scrollTop(),null!=A&&(k=l-A),A=l,m?(v&&(h=l+t+c>z+q,u&&!h&&(u=!1,a.css({position:"fixed",bottom:"",top:c}).trigger("sticky_kit:unbottom"))),lb&&!u&&(c-=k,c=Math.max(b-t,c),c=Math.min(p,c),m&&a.css({top:c+"px"})))):l>D&&(m=!0,b={position:"fixed",top:c},b.width="border-box"===a.css("box-sizing")?a.outerWidth()+"px":a.width()+"px",a.css(b).addClass(s),null==n&&(a.after(g),"left"!==r&&"right"!==r||g.append(a)),a.trigger("sticky_kit:stick")),m&&v&&(null==h&&(h=l+t+c>z+q),!u&&h)))return u=!0,"static"===f.css("position")&&f.css({position:"relative"}),a.css({position:"absolute",bottom:d,top:"auto"}).trigger("sticky_kit:bottom")}, +w=function(){B();return b()},F=function(){E=!0;e.off("touchmove",b);e.off("scroll",b);e.off("resize",w);k(document.body).off("sticky_kit:recalc",w);a.off("sticky_kit:detach",F);a.removeData("sticky_kit");a.css({position:"",bottom:"",top:"",width:""});f.position("position","");if(m)return null==n&&("left"!==r&&"right"!==r||a.insertAfter(g),g.remove()),a.removeClass(s)},e.on("touchmove",b),e.on("scroll",b),e.on("resize",w),k(document.body).on("sticky_kit:recalc",w),a.on("sticky_kit:detach",F),setTimeout(b, +0)}};q=0;for(H=this.length;q

    e>Geq=-j?VpZi)Gs^^PW13iZ27rWiA?` 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 literal 0 HcmV?d00001 diff --git a/doc/workflow/environment_branches.png b/doc/workflow/environment_branches.png new file mode 100644 index 0000000000000000000000000000000000000000..ee893ced13bd764322cc4195945403731508c9cb GIT binary patch literal 40210 zcmd43WmsF!6E<8Z6o(cm4#nMFgS!+cT3m}4_h1Ez26y*jp}0$NFYXj~cT0KFU+MpO zKEI#d>k5*bJ-a(QGxyvxyCBcb}@1uQG{#|rTk^b@X4&)?8L zFixt{VlOI2iS}N+5P2ahA^O1`=CBnhT~|Hj=1Jau+RM{mVtJ2|l8urK<#j;l>zGQ~ zSMc!85=1{?aNe)6wB07X>A~Ob!Ic_!FeFOW9LG1M{G==LL&neW-D}KuWUs1R{_CLC zbLc^^-ny|?(?!!|w){S0tf<*;*(-6!d#v$B*k#lylj9&Bjy3uP((}tN2r*wvMk7oP zmQv*T^$Lcb^$_uYZxbm*;CmevrTk_6;AH-L6?ar({I3VCs24bRLtOaq)G(Ou{(EJ^ zb_xIMi_(w}_JbkgtSNT#^UX4T$fythcla&D_x|5?<5UcvZ+`b@@R$F89elzZy%TTW zl~iH_G1?B(*t9#?j8yCEe4$eZEBcA>7Y(PS7`A*IO{1$lNfI`Hk-MlItDe1LCYh_S zlUx3&(Ai9{mjbT!CUewUhk5;_C7q3=IziJHPBi3^*G6Nbd3cH#(IIZ5^d>#tW%}`3 z^%X1Q>ep{JO}3hJHsOjxVx-QAMd$}h89pyOk`EhF+-s0kjHQb$`wMe|esmwkd&^4d zFYq?qTjW&nqtsGuUtcBlstkNr!|P^@PnfU#tQ1VAO!MWPWrj+`m>OQf6oZDTd~*oa z;}&5FNh-MAIqF<&riX5}f%ULc-X1O2>~+6P7OmSrte}n77L#xExkTbP+dHQ^wD9f4 z$Eih*qvC$eCRPlT3v)$8cJ8oNdkjNPm-UC;IYvgEm~dmK$pInf&nL1iEx)3aJX}p> zQu^h(_BqZSjwgmgug?ejw1@n%$fb2uxHU!;uXg2iem_n+sbsEYIEJ;~I94VH|&sEsHucIo4R`QOK~ zG$@uy+Y<7Vwawh=X*mkKEV1@N$Fv1b9fdQ{1mXf5%e8 zcYoS&x%PIii!Mr7klCj}?7niWGT?H9dav(j0#v}5M_MEcU`_(+ZtwQ#d+-&Y+|pI$ zo5{A*#{+Fj7K{5?+ZFD=kI9?;nK}`jeI11fi$|^QlM9B{%Z&g~a%Z0O#XhV4ZOE^+ zo7OfD;P$V!;E@YO<$xT|rKfga2zpjA{Ov=LUAJQh&`6hrKbTZ*=~Y&P$Q4?U`)aCH z7DG8$FvNbT_G3Kk$!DTP-z)YKfHzDVq_&#Ps~8CYIVOeluVsuvgdR3X`=0bZU{zKe zCm-hx9k=6T3B6n^7+~nRa`Qz^N?$AQZ@k!uiR<9i_pEw3hAPnh@Wfa$(5YEL7nf9^ zkHLb*KJoczdaJ=b=G(8}v>CEqsuZWQ6v-6~YhU(Ynm7xgLLFSgnmk;9d+|#z$mwFO z?+cLaKu08x`sz!kbQeTrrLXM3Et;XTGaPO=vk-(+mZxcd>KlCB({DD6y8G^?^C73Z ziCDYNddi(o^;#sq=doxG%Y|+MPmZt%y0aMwu7?Z1Kp{@mpJ#CyDk-N0NG)1AvgS)w zPwkzmz3)|3AdG^yD)q<&T>6b&?we7Vc+O%|0+mx|?vnR?qK%|m1w@S4Tm6s*v14h? z8$=dj)y+&@`Ww^GyUcjt5)0%OZLbeFxI9eJK!`$n`K^K)S zz#u){@3Nc-86xs=SGZsC()VeYQuF_QBL@Idhyae`f!;FrXDJgPwU!HHDd%+^*U-xi zvAsTh7Y2Shi2Jv1q|)J!KbYKr1Uo(LOMO21 zK}^fibMb8V2+*Uf?+6%#>T@@StG3* zijJZ)O^v#G?=0aaleXo;GU^E0yuMyP04CtNIbNczXP)rn*d@9N7xKcOqfF2M*}P^) z#8*C@V`^jYk%o5e6N{|43ul;+05RTuvS?odf%_q!+@uj{h&MHVn>=h~+Bp7*yQG)^ z8Qg$d8k~}VEt>ad+22S*$cmz}0Uqz~&)&EJu6MCiMS|}l4<>IEe6)hT%LA@UTN?H~at=&xpvD-7a-1+Ws_QZBTg06p3+7!!crj8YKZl9qf@H|cuPG~Y>u1h=UPHdm3cyo+tMfUETw&5^&r;d9OZ zep6$-gKn?B|L&TIxR+}C8xMH%aX&=F?iKe`p>cx+Uh}??eT9GE@*H55M9!tawGcm^ zQ(ACRq|Er5J91;44+cocJJEFRNQ62BZe1YiTWYxJ`P?R8hO7nhX1tii$=C~+F7U=1 zYu(mUHtd4$;bm|_e7f!ff=JO`A}Ax*&x;PJb@{uY52m+CFZZ5?=ml!kvJGw6NEr0K zIzs?Bz4SK~s5$2 z6l8?V;c3SJm-ePX#1S7#jAEK;e|q)OT{m2Ad^&U7yz^|n+=$)1GTA9zqI1;sMknI8 zjELt&^Y9W_7I(~nJG|sj+*G2NlxL?@JxIpf1voCL-9>-Zd^}Z~^KKFp0aU{69Ra$# z1QbdxKl6`1J@$QWV-Uw@e>`~V1S`nBzbd>qD;S$;GFBkE9XtVK@@b!PW&!tj8n&7d zMv#8al!_N*UbK?Dx$Oz*NXH3k*qaFD@mh|eeta5B?fvFstennTWVyT;uvqV2r|#7* z?Ec8Wx?KZPl{IDQ)V4Mwj>2AqqJ{f_x6qQW<0(pIw@@57a#t*i_3GmYs>B<_Q&YAi zgp~cpjfYHbSQ;-M(gl4_%J;a>1$;q8z|W}4shkWLDSBt#245=V zRW`SVtM^0zN?|^c7b&}fS)a0;!nT0A>kXu!0f-jvJ@CV;ZRc{u3DRF2H`3WY68F=| zP$f`6-2I^;TBfOM+j#=?^0AOLsYp%8tz~^0R6wdk7jd`OM`o&5bS+#VRzTWL4w?Hh zCT_0T@(#iq){@@CoA6wRj><98%FNcM>oLZYFy!S}V zNQ?*3TYr@sLAP?PKwJ+>g0A0E0G8}dugNib+*^dMLN#z|O@1zK z%(_kVbz_-ITz(nAn;mZ;<@Ehr-I>jE82LbQ?)bTa<}`Fu?T++YnU6T{KAY{z2ltL*~A*=A*Fb;TFxyaR%ykL}H6(s}UhRn?LAP9FdR*y*nXvh=D@K`La z$#=Y{3kE$rtFq3%b@cmPBF*Z~ziV3PDqH!9)Z*2Ruc@O(5=~VV+x_TmW#<5ZmqO6f ziKS2xKu<-joG>d0h>`A*5QcAr0c!Avr;jQFFb4J*g~^5nuel~jW(w*%#RTiRO!sqK zBC?sg3X_`3$x%=Q@iKmN)5iD70u00|ui}i^ivouB_j--k??U3J8&KCy3A#S^ZT7`5 z65arxJok@*iF*Ue}`Am(+!la0lSjWX+YGZ+Nm|IMCiAv}M){9lwXT+D5 zt&966-`U%7&6pB*R)d?>C+{g@PXpK~6jMLmxo&-bDCgGo@GL*`S;NWtfu-5Nd>pSt! zY+Zka-Qe!EbuTA@WEp(o9jzJxpbeeTU#Hx!=GLDukW*;+KvHSLS9PXxx2%4*8%0Qwe@>x4>NGF8dCF-498x6%|*UIFI1MPi8 zcsv}0SN+~0^3sHBE_YROWSdjG-XC+B;Ir8fBHG#VRiDX#}2+f@=t zhk#SN^eBoXgYpitd*N6_|)UmfKx2~n%-_7AV042Xp zzuK0Vbg-!!Beu2cB?Ge$oh!U)sW`L7gWC*xJ5yP>SAls#+48it8omoEDKvY)W`~SS>rxj?Nx8*RJ~j#)Ax?-A3EI zd>#JtwA5h3$yb3#F$gITerFXxvcj9Adq?NID}kY7X8G&qncG=)v%zWD_ZJKeUlzh; zcflzs#|Vo9U69NniyraA2l|QkCkZ3($7prT2Aztbn0A|RlbWQ!h*9%J=iJh>9L@J; zn-R6)uzf4ZRfUA)x7C0|GYD^}A zLLMDsLVO?3aVAlMQ{KIUG<>*s0DU8MlPn_b3x&;lKdGV$<6OfVpOQ#j(?8!ST<@p` z$)W0Ba(Nx{s6=*Gp+Vm-C#kbX6mM8R@FufN!iuEZvB)^K`Sq`cCk5{{SNjmx^};)O zjNG$KmeuNm+}01qxmakthd}DKfhYe}H?2O>tR5y`=bmfchTY8Y_gfCZ20zF?Zx%4O zs0xL}UP@#b=eyKwVBJQ3tQERbn5!F9Rg zp-)#3;+*|9)5C-hG8WDLq>r8$rnm|ty>0)s2yl|X`JL9S@LX_$^1u8OVG~$(;RI;9 zBE)zjAh4C`P1PaPNO>u=7lYp}ks+dzqY1DJR|25_B+g~_-9^1Xt~8IF-}Zjpx-QoI zTd{sXPqb~R2}-I5fHk;%LgvxPmDwLwgf01m6A$%h1btOUg*U{O9(o#qW{(g(Ku_FB zLz4bOBz8|+r+V@Wc&28=ECbx~ge#aqnYl^VN3H86kK1`5OOci8+Bp$pShsL)3*sy| zc}b$Cba{(LvJ&^hs&c{E~I{+v4o@<0P5kEjg9gu(hxb-e>+Xdx(y}S+^Indbd4x zI}L}qM@{obvzy3ep>;0L4Z@xsh3>1LMARQ~vnD)@&*4mqHl{w5X&n;Lx)BzcKa7$q zwc&;5x0tJZxyhTcet#_M(mu)>2nS6flzuTFLoPSytlo_hSyVb6=T^%(uIaKDTO3?N zsY;Tns-N8a)`gy%Gl=5BLB)00Lc%MB)#;4^tYoe~se`g47m*$ALu!yP_bU{Z2g={I zjsk7B?fP6hlzm6H$AFfAzGJcq`p2ZwCkZm?qQ zFqJFG-u0<%-grF}EiC{LZN`Yww`di0q+;l2_r2b(yb!gU04F(?iN-d?(Z(8WK9-o)eqlIWa5E-8U zhi#H#qt@SHoE&3iwtrOq*xrAp+TW3zz|M8mWJz0;vsh_&b3;!n>_qYVDwE9ntb#{q zt@Sr-radoV(gy^04v${+J0>;#57iuI3QVWze##`%qz|+tKW&kvl#LGng5-qbjS1}E zNq&4!WMhno^%f4I*$q7;C!F~HRx%n%`2PFtAv;uewX& z=c0^?vEG?gGXTm6y~d9{5^eK#q9R&3*Z}LZyGLszFuAe{F$*N$VzGN-nk{Yu)0uAa z4yR3)KtYZGjeZE8gxmPUPW*t-w@6;*o6R&w<-rw=<99cv?Mrcmbf`C6;UtmJ;==M2 zsIP!DiwVN3Z!wh5H(@T_PTCg(%Mn(yggJ~QzT(3)@vU1sBcxTnM|TQ4A6{2u)}>}T zkA}qakmYvBc>H>k_}7vRa9qA7k$-9^V(4&*ZBCKm-TF3W-S7u&a5J2*uPtHQL$c*v zEv1)(^h@SODeSK)$fM&8cBe*zd&u1_g^P?2y;AK$}J!{1`3hCT+1=IVFn8vy!z0LOSQ5`S( z&H|gG1TejPFNYjAjfMf|pXS^Lwu|7W;6AO{>$Ko4!Hwo2aBF1p+ZVq^-~$p(<_3k3@fCZ$%h!S54q=G z6T5;2na07&Ris1cKUaM0O7dGShTe}gjw1q1Gp>P$(!uZ58CVi6+h~J%RSl^hQ4@xF z%JDhXYg(jwpI}?7L-;E-rESR(y>9Nu-CN4rTh1A@b+@_eE}MG6l+n6yOBlw@?iAb@ zW(Ob2_+OyXTzYwOvQnjNU7%dJm+rjmc-S(OJ9NR*)#&0$)ePJ9MPnAH)wkTz)*|%D zNzUtfDmr#ouHffnU|-3 zSKAU~vBGa@y7;sr+_KIUnUJzJb~sBXX__V8<$4K1&ol5|c7Gbc^azHdVx{c;0XZKk z@HHIXQ7+osV3+Z|)Of4Ev=SLAV1~-_)T4QU!MS))(yq{V{A(rHqDAAWc`(Mywl?Xh zzm>%`Z@lx<6Uwqa3t^uI7-;Ha?uAPYthkM-rG1G5gH}Q_PXIJcW@M>{<1W)%2YNJ& zUC`RVva@dN8$?O4kZ@dWcmg229fXcD5fKkp$}40KAQ`|l!Ee?<**M|DtHC)26d3|y z2K#GLw4WjB+<+=!r{O73t68lq0R4nVe6HbffK^-n7@EuCN`OhFn+NUqXq+f{fI5n3 zC1!y=uRxdUJ@yrXW}8hnEm=~8`2w~zRuiJiB~!4V?%kuT0X z__^T%6kVK;k2)gHcUW{k+oDw%U>nrut0dX5wPBeVmkwM^iTGf&J>K}x4&sH3_Id_8 z$9oDjWXuLm8*G&MWO1x9ex(bv*d^@XX>&rAY%E&!8KU69x`Sulu{!>nZ#X$iHEVd{z~qVHQkqq&WK^1o$v(B-{dZ?@IW<;U!-F zbA`%h$KJP>1T3q%Kj=F@1bG6Y$XT4EwLZ{88U*+ubA;f=^c23)Ve!b>PoY}4(P}S?K>R?SLbqBy~Bep*QnCDi5taZLN90SPRk1cTkyr*G|b;FBZ&ez;vk}0G{24T z%riUAq%PEGygjHFkJpl3dklv*<*$hoLYUT0i&|dDv(SsF`yu$gN9wNaC}4(Xh9`Zj zY{zi{#^Vk+PkU4oD=|-2?!o$Epa2HL^PA6q%&S}+ZB~4=amN`ON=(A-GDRdh8TV3M zFthUm9_F>k04frIf+;+vVV4`2>NNQnS#Ps39y}z z;H7B z!z-;qG|6v-ljaXzp-cm(u5S;sbVd&SaF>uxG0@s!u6`_2)m1YYr0vMz$9dd+9QdSz zh3^SyAMTL|Pm;!m*VfwF@!5UT#|Q_~gIjn-b3CxlHvzin+U?Vx0PQqBe7KD$AO$#3jd(bC5Nb+Pk+L{$1Dk5&1kUXe7P3Ix}0 zrzW04S^Mvmd9SL`F&Zl(!rq(GrfX|et*8YKy!}PpTTsIZ_KBpXy~Oa)Xq^GUnha&x zD&RJTVf7T{$@jLQyxlnQy@B`M?#eXd-!Nm*@&iBhl~?7MBzvJru30#DFb?Cg@2n1+ zDbdjghXiBo^z3O@;{nr#8%p^UrKmN$<})x9e=N7p{9I~zGdstqMj1M?7cfJGgX>%I z_SbwmmLc{#cwF5N!P0HI>))3{%7)Pis(QZtg2#w^H=@`C6C@x>J1;H*oLcT#QjZ(u!61!^)mQ zDPd5)<@k$fP?u+%Uw8+vQVn|E*f*AuAtE{rYcz zyMr9=#{`5?6*o-w-=2CjC0ua_rL1^$>hs2Sw9KCmRIRI+|9$ZPsfFMYC4A}Jr&*(x zdsh_I^fZBf&&$geOh(-%#GpM2-uvEz*F0$Jg7d^uy|!9)uJE2KuKMz9*F5<6)j6JL zs#9MP3PZgKV`B9})h(C;<_`u9A5>bd?1*0x1NWLc@MxsK9f z%QHzywCUONguU<4s-whuxJfZpkLi^*EY%9;2x|(Q>SAhk;1vFv_i&vz+IS`>_tq+r zvG2z@QOJ_2)IQ#XeBx>u)z&V53$8uS^}Bj2{24~h2W)ty8Pblnu5xtyKswrDoq(5@C3-YSb1Yp6 z5xD0kJlh`f8fRenX~YLuZ`r;jBE{!S|Ir9W%T;zYsKapGI8769sBy!_x{K;snSEu_ zj2wc!0daA6Y5(XkJ{h=t<$e^I*FRhvD$GvqyS&TGm$U3#UmNPZfrtNwMYO2JRNccA zceRbo!o&YyEtoaxz7^b8!_4b~sA3?fVYSD^2s zp5N>I>H-`po*I5isFK=P&yr(CoGx} zx@5P;mxWaFByUAU^G2#3cb=MC?6b{X7Uz1tn~vI?s=BxE=(E3s(3tv5l4+{cb6WcO zt~tq^%si+*EVTj0Y%yOUP;-Adu{s~KXlW*fkbhCbBu#njC!lBXby??oK0}f$3u~lI z))-WiV?Iuf4Z=0tiGn1*CtT8;G)uJ@C%?EHK-3{hx>|^(s*b6;kmU5Fakg0Fb!-Wk z9QtlEE|#B(C;705m4`z2E$BVQ(-r%n(aB-&*9M5&E(j|WK02&>WM4v z+Trw=+7RspscWgy@;SGLXyA_rD7ou-R4*U2`gvx#i#;c z2M7eg>8~H=laW`+o)7ORp!mzuvToqb3cXjLU@(%WpL~B`b(3z06tloqvX(-PW`=WD z44}_rxn}0%!VtjzrW=nlxH!WpLe9Zo;nr#)`(~=ky**J^TcC3O4+VfAiJ z?3+X4+!st@TLB!ERbpECrh|1FSVqJ6(s#55OincjQTP3tg*V9ygv(h|LdLrHRv8Wj-t(S}Txf42 zZrrlJSBxYFk0W+s!}@A_r4P}^O!J21aIlTO|DbP-s@s5rmtyYuz%^z4b|(%mrs8w|-JtSvKsWgK8>tiN6tshN77j=p>owG%Bn(Y+~|;{k^G)oA5#yd#yC;cLO3WqB`8zqTqc&5u0yh5Cxp%)Y zsH`bH?zTrcp5r~MEnGUd^DDA~iW)|3{b|zR_t>6a1@CuMP0iwzqs1<3tm&$x;o{&5 z#hu~}aZq(IoT!80s&)AT`=~tjio&vz$)9w-N)U@n>j@DM5{_pJTdRhtsO{sS#MZ!c zb8Ec2N=-}+e*$Q}7q_g=0P)oMc?8G11k{$k<3mhN;==?zuajXal7g{~F%fa($nG)! z_Qp#oGtxaAaXsxB#+m%{b9r&u0>&&PhEHvgcyB{Fw>q*naD*npx+39atZkjPFn`NW zbBxs-Vm1^Y>0_ijwzn)EMCHnu+b$3x2Nf;kOyuXu92)8@DiVaqFF0@R6y>SUh=Xdv z$<^2+G#nmz-ULjU4mWb@5-+NgOE+A)A-i3nn2`3$L{XGuWHFkC1<;kmW~~}%B$ccB zwLF@3tNmNNJUHr{j)?5LQ_;Ae`(8b-ioo+{!faDQbB7I?Q4v+Og$xxStUARrNjTN4g(gH=lEVJ3TiYFrXiGle>(%#K$B67I7HL4c}-?HTA_^ipgp4X=JHvU|W(` z;2XZso0ale}(bC z)igAIZ)yHN)@*1Ct3vRqk{eD=`5%!sWP+Y1Et}SlPyENWMWE+Ljd=bZJca(QB!ZqF zO|)i5R`_>|2z;>5zk{nX9ga}mtyM8V%lhy9EA;T{^d|C|+uyo(1;9vgOg8wHqyMO+QT=uaHp}w~r;QEk1Ah;jr06C%T~?D12Z(_;dv; z7+C(I6ML;C*jzOU=)$4Ak(n#tcnNWRoU8{57n)(=8w7sR(aCy=*Y*D>xmF^>Njv@VtmE&TRrr}>iGY~1cibsv{Aj@=H5Zx86? z;iXarKh8~+s>nYb^S~nIbvS42cHOkM!gqD$o%Ql&3xZqRUn~D!XMq73t-pab+49e2LFcDQUdl;n4K)4)=flrw>ODnE|8VgL zjuG4;c(KS+xiNO4AkgsXZEhxc;?BEoVd^8$5P+P$9^2|4-`#0JJ9Ajw*5n#dY z?igKbKi6B8?5IK>PJCM0Q%~r=z2A=tBnpy^C^TjI>rlor9kIv? zAD!xX0|Z`+Gjb%=-q%i!kmCQ(-C(gQ^I;;950=pV)NI9mtG{D*fAuTCPCl*xqFyeVd?Ei`Wm^V#<13~@{?8s>!X~O3rtS-D zyqLcXjkdYU5Uyej{m;ES_Td-{I-F~+v@+Zz&S^gSPy}@K|6Oib#ST?ug(^Nb!hDYA z{5C#qkH4zw_;Uv;Iwc4a&OQGg_Na!q0p%R|SwaaV=uN|OpB(Qb@rjAUTO?Bgo?#@` z@6RNM%OWU0VY=bOmA4(IUi_Wk4qMWN1pRxqLLEs<)n8Uov2#lk zTupzG?TDMOx%LQ0YJ`mJhx?DJqQWMAFs#uhZE=SW3v=o!&aKV$KHPW8{%wc%|9V0h zY+Pncu(kc8;cBY(soY2lrp^7X6^fiv%s(#YiKM0BucB}OaOY11D-rd%X0>0D?_T2l z&um3L!zTKET)C{3k49|fHgMgV*J{e8cDFvG|DQ*QcAO(CXq&;FT1-P z#^tV(|Eo%xS09ro%rMoYX_Pa&PftI4tACw% zUWN|N*D{FkQhc^eHeAdrHcT()(sc>j9L5s*N54xhP5Jove6q9@)zc%se|Vr!8iRY* zyP?@1-26o0eJqb_JUx%XI8K6J6}E5f=7XBR4vS@pM?fH^rWO}tq9| z*h}*q%!e?c2rZ>~y8BhC1=L;qlzJj~YMK7sT)&&x!r2)+GBT3aWj|heVP-~SHw#*a zlzIGXjUuo}V()UW@ocfwjqMt?$i3GX&KfE2YN!$#ZVW2A#O@+EcRz*Gvy^n-qVOZ< z&Gz;7KI(1%t31fy!6FrP@P3rCHEz3V2S=4s%@8v461xrc%db%iA%Y?zM4kfRYeV>; z(Kpg7P;{N^uel9j$!Nh#eU#Fqfy70)4%bvSmmQ*NQ`I8XuiU8=9}SrXj+l$v+Lmv| zyx~OAOUaiT?&uKt>E0sq47D0mtnxP0f{}D(I&rTZ+aDhV+z;1w#yxUbB!+kOXH=5L z1``qBCC3-*Ws72`zVf6`OXJ|+Bnx@DIy3Yv$-F~^QO`txvH@dLGc%*(rAFZ$j{vyX zgcm0X-)bK>LtYkY9M=@h*dokS7)0zmU2+32ZW(qz9fYQ!{-Fk$?bw>$_qxLw1_W1* zs;a8*)zyRQ>gu9PLC)W5qx$3H;|&v$d;dKWge3CLQp#}HmW!J^Y8g!QBYg3CEm9LB zT;;Ig?mQnf)WdM-=mzF3l1g9+hzDrNf2b+F#dpozP3bW*qHClub=~UB-8#lQ0u)}W~hwc&ES*{*PmX==PfCKf2gYVp&8lvZ#~j3-o8RYj}s?e4N-rx@08kZ~Bn{ew6dXgl*4_s-bbT3Vmx zn!f0`Of4>2E6~9&WE%3Dn&Ce?;%ho6r;fp_e($ZvUmn#ZR?a`MTz;^>pPhyJ&sE`} zJ(c~GQ;SxqCw)&vJyk+M?5`5;P8?(O%*_4WX++?uS3c4rpBEGq}{2;Qq-U&YWYhpUWY(=e78|v zw`%_Sy76!d2hC=ijF6BJ=W(Q&III1C0{YWI$vg*@_l!b%Qj*17&+e3JWwrovsb0jg z9md(>eiKEhb5YbKR`srcDAGWG5c&GCDF zNUlf8a5+N<(z9C_-5GG~TAt|@5)u+9nKn_I8Tj$6W5&%tQ_p>M^x=DRkj`+tA3NJ0 zSf|8;Y7d6s!}9(?_`(Cr`0`O{-^#**PBFqSL(xbt_0YYO6O6aEjU(%o|F!L1tWsNc-u${Bx)P_M_<*^H1yX?j%*(~lZBU$~TH%b%y+&nx8 zdOrERtxzPUWnqyP6%}=ko&&Arc=?8s4A@R4+4Tlb+Tdlh<3vgy{G_hdND$JKiHoEU z1ig`z#@_ymy|3Hhcww>HZ8`E{`69hsG!NeA)I#gxr;5u?_PBK`Pw!0E5r@D{FO$N~ z9c>mtl~=`?yo48?E8VR(OMzY_UO{+-g4O`kgKt{##>5b2T9E&(vvihXCFuEUEFvZ)Q%HIB z;|(A*ac=RAas|DDZoml zutl)$tD3_OAApy3)wdsL^*(rBz{?lcp~NTW40b)XzSSh3yzy^*4rG6FDCXzmXZ>+< z%)l46hM0N8Ci>xrhc;czQqtN1<-!xzCEdmL_=;Fcc)T_0QaaA-pM={k3luBoG1jDS zZ2gJ3ZQ72^BYvL&ZLG1?rKHWdhvVEYK5N?Xp)Vw^t(suuSRYL{ILq#StN3hcMD*AD ztzWMqMsO|AG3O8W_p`<)r>Cc9pBIDhxQ^Ac*w(%rPBQnR5$?!O<+*SAJ|W)xdcfj? zo%uat%X>7(a#pR!4yMexF~w>knv@EX=gwYlH5e!!X`Z`26>@ieAfrHk-fN&lfEwSx z_^3*0!Tr~7B_+s>`!Z;r)7JK0US4>;t=x=@-YCZnb`&c|vjKV5 zXG^Cn9zS<-J3T7r&Vt~Y?M8UL64VHxW;~dW61uVLF~F!bQHw#-`&K}UdfZE$jMpOd zpBWg!z;^zkeOvrOGMx{0|Dm9%i9g>sS3vt9YG#e_X5nmNjK;e&zO23by?#w1&d_Lg z%ar(SXqWJIn1Ed$`$rTKFT+w8qn?L}vs<5=^-KZME#!ifgSU(+e=MQ!58_yMrZ)r? z3q^PytM9mV1}xk0M0i2?5H1xhx_ZGYp^6u_yY6>y9`kN}RQg<_xulg>07j%Q+0K^h ztqDompF-v+O5@(z|0bURUc5Y<2vSI!$qUFl_Wpj3`X4a7hUx}sDw#ta2dZ=`sa9+R z6sx6=1_q$aOe7adE!}?3p9S?xkq0xHIpU9PEz`n&#GsXsq@GN---Yd+>c*E3$Ja&Q zOUIc@<0`rkyy}dHnyc(A@6>N63giRKN5cOGhZ!0XLcD`|Bi$(xs83VK)P^EKLmc$q zaM%nbdS+&HvSu^}a0{_p29IA|##2~G&zlI_Go0II`LTHT8H@D@jGCc-1$0%AO;2lk zyvxkpfkWTb!O|jXgv;7UhJ7wiDW>(PZouhVmyp+?Nq@QUdH{6w8a@SgzZQc=5o>T% z|L%UCVNPD&OaAiwGjaTzw~1l<-#48%PfC9z*)O~s9Fpi~W|Z9je$;T0)V%rSHoEab zuA!x~KT{600?A%`R5G!@yRuq3YqjR9<@Aqw`ZG+&IUH}0=^b@MJ2Ey74!(T3dJfWC zPcN@Xx`OfXal`Ql>mYgNp5a{8s3s5MfFIW;MsuqYl&BPcb7Tb`Xor)Ef}w+I0Jb$~ zFNhJ$)iYZ&Yr(-und};*ds3hs<{>w#ixss2pbg=%k11|9 z2u^of0fEQ4HD|qxpUr=h3FavoC;Gx3$Q{i=HbI}qUf!%7_pP9+qk^9+2xR`NuaF&R zStYBOVf47m8REt0X!mKPlC%#p<(mWrwE7LIkbbq_z~)CmaNOj(VAQaCiTnXAEce>@gI3vp9F0ROlmjl+6oNYBsEo7?(doS&nDTlw{{ zZN37n-~9V7fU6kK!}8Kn4k%wNKzgfcTe3N27&)wJ`|CH>QmdMIqNyFN?~zM`rAe)> zb>D^W1f<;Gw*9f-F$+{`*NT3y4WBi`j(JM@pN2$qF;}rwb-Cq@Q4&&8Eb(rFRTZ4E zr6Ro^9v-@0#DYfLmi*;4HAD5}h2Pq@pu6dFur*DzJ)|H78Vs1VMKmozI+#A_|9Ce@ z`^SM_kNt_sW94xjRo@&=mKvSLCnjL85U^+?K%l&w92he*Gg?OH#%~v-Ld~#g6|KZ+ z3Yte~0;e4;E;y<<6-`IsTAGT!E!2X7|82q#VM4=>S=#7*fB@7p&-99a&diwGsJ)JI z0CRx9SMWf+qz;b(RMD_uPS2vm`rppIKioSgcZ4ArA5?#D-wLIVz4Qg|GrgLdnS*=Jp6J^1z$LKc2h<4IgO27#5zmXaMbx~}9ZOl|`aaQu>rE?dq0xP_kp zvHCCerd&F$O7*fX9#Sn_Mr82c@{Lyf3u!~fh0me1t{NH|bG@xiVzLSNe~Uu?o0QO* z@GT12S(oRrS}mo;#bJGr;yNw|gF}=U*#2@@O42_uZ+6?kzyyk`G_GWt#IlMyA3x^2 zYKy_U=@oi`{5NZR9Sa4i=&yK2)QH8RVCIa9;$lQ?Z%xf4BZ0g;_GiNx5cNY=aIC2| zv*z=wDhS?k;WP-?gTE4nYGNIL{(6YgH~D@wy)3 z1qTQJUv7=^r!1e-n_JulXTzQLT}-x~4Dsz$!_Dp4#_rJ(+W*KrqnICZgd{^4?$)MT+E(FAO`}y%VrEyrGhlPiWZ?CMZL?tG^wFA74#isaU zfE~kF(6r~mRs^ZgUqn_mG^hUy`>~M6dA`w7DbVD(q_hrIVe+jlq1;gTVDDW^RzL=W zDWeZvv)KA0Z6^enWjIwx4^%>8JUrGz6iTU9-M=_ zzTx-3_rAN{r}qI^tTofq(_LM)d)KZWdiwV?zdWI;C+#hmu>TXVx-QWWI&@Di|FRh6 zUtL~yEv*IPxg695xQ$Kj1I9E8}jdAa4)#C!Epz004}@*K)z*DBU#Ecx3a@Y*~H2|*7KuicusJK|I zR6+Bgk5K_p`RK~ZDlT(S}#0f8)%LYrG$PKu@^ zS_uB@^{HPI;uSipt~{wJ`suWNWtl-O&U$WjA5E5d{}+zRdArIRy863}2pBZrWqcQf zeOkaQ^V1iXcbjju&@z?CY*h`v%2NCfRzgh?L2h$HYyY7sh!#QwJONbqLl924@Pmjy zIbTK~cye+wB9d5`rrvUSX?hx5A1Qg+oRYJbS{&Fk9awwBve`)CQ+;0q+c8s#B|T^m z5izi_@mJknVdk{FjIc1=|J+hV*!8-?CSIR|AgXJ!dHEqX>z7&vcrYv}E>OtUw&ELh zuCjD@faj6wSo=XYCo0@YhKRN5Y$$j$9C!{2^jE2@v!{?Ct_1NXP)bTl(5~Lod#qPC zmE6%G&^e*fJV8TM`ev0^&4&B0ENJ%iK@qMc99QM2e0epqMqlZnh#}kczMuwcWx}n4eFvXZ{ypOMXd(N3ABi)vM*M zDUfkbsbFIL7x5j0{mGaerZ6zbV-fdM00vH00(q3@ezQHTKB|lSJS<nphxBLnn|T-mC&!a-C>T<6W3H_Uu| zpAvN}m+`HCQkKc!{Ff<03j}%vwvbTk@dP`Y9Y{ntx%@2X>|FE2%i=vm_~UbP zW=brnUu{fULReUwv&vt6Sr4AX(V^c*%hTqezrBIL5ElbHo7~!2&BGM2sJkMnEw7*; z$mGE=QBB;ynOtmYR77F>-<8|_+20P|Pi6Ql7zqZB@83rn86fN(;r5)VjTP6FOgfk8 zq(^?BGl3fpi!fL?#d}4rwGLWpD36VWKKZ8{?ot!lN@iQ1zf*2Lylc4W%^K_(-R}YA=j`hK9);h1Le9yhbO>%i=DXNFwF6KG9 zy*oTi;C_a^qu}GB?^!G!7#-MoS;kkHK~%)X51WV6VLP|nV~L09y*o*pA({T>oB`|Se_ASYiMOHM%H$}1;OWGd zV(7{|>6F+R4sH09!-LshMG}BAj-h_-TRBC%7z8&y^wFks>HFH_fvU6XrCg*IPVpaQ zzR`s$u)1y|BuoaU4aZ~$;eB+()-MKv`Fg>QjDPzG!{q=*zWZI4^8HVoByrelYadMi zf2j~YlVy`J33}%6A17c_`8#VG`pkVu0C;()%uOmpjYPn%i`jL*(lKwb9Gr(|+4i4g zdD~e2%!z5K<0I048BIuTtfEJ1{`?Lt3FH^L|TH-^0z zul~{068f`B&EbD(6bP{YhI1%-Ss@?aA26LBNWU$dHI$JBDFPU$^~~}Dtt?Ep-Ka*Y z1e53L58zQ^+Q4W^kO_+MwYIz6`G@<7CQHqSafIWSW4qJCe!VEVW@?#p)baeumc@aR z4T&x0=cmRgK?-YCy`O1dv{<3vG|YDG4)536ws(V=lH6^osC+x%ULM=m4Ce*VDoK?p zz?9i?2h(HH^bO!-IcqeEd*uj;7YL zRyq;*((=I5J+^#Y#o=$BsWJ~m+)jphm&3($livbu7=zKoAp1NM!6$xR;YTD!8Nmy5 zOoYAhoww4)41M$D%(bPf{04avdBet5yg_X8zg3ge;( zY->wye=d=N@s@%kb@LfWZAA>9uYcTL5`R*pzya7bVp33|x)V^tTb3gfl2n&7g(%;Z#4m zBL>AL&)2Z2jfW(ag^}(TU9{c`_L?@l417ARDetwG9g+vFi>VfxgzM`)zR#x@IG-fa zn@#ldq)jey+4K0}kqK@W%`3JGE+zLZHZFB8MfU=%1kero?^f$pwW_W3KvSg2*nyx(N z8cz6G0XnMQ<_SsJ<%3SK)-s$jbcPZ`ea6WYXr*Z971H z^TqHK4N*jFT_Ts@fQbV4H?Me{u%dKOgE4_5F~)yd3+tt%s#2P|?rG_haf8kI>%fD+ zHbQOC$qdz4-QQn9|B9`wy*@ZmL2juGB;j}d>|XZ^F3?C64VAb--QAG5nQ0K45ZSUM zGPnQ032$*NaPCy_MFU|4Ezy4)Bbn@^AzQT|295twRO}m$1Th{sl{ZctqNq1Q@TomQ ztn(WoIm!u`zOd_PNgMk_CYN*#(s=ke(_COJ_iIp!h}61Bqs#T%@H*S)O}F%a2oZve zeBz)`u`W-f;mm&u2GgA*-*9aVi6O;k3RJFx7(qvas%yrU+OrAZ;n!B%ys`)B{k{@) zCW*Mn?ggc9{G}K|BBwH?WtKyGB!~1;8qff`nNlLSr@`94+11Y?hBpGUwps`gp+w2X zNV%W;b>ov?#T5njqj{Dk#-(;j?8j_s9IQP`jdwo>jZKGB9SMhDQxwmL8V7^3 z6SL?qBDvJxYGydY_L37qg=*TzS#1(M%Tlz$f&mMUMX8$3$h00^d9y#v7}qh}1dH&` zH2+OgTymjPE&Y{-^stygxr6~8g2sUy(I*MQ2W*>%U*JUxFT8t}a0H?39CH4ygkC;f z#dMu3*esT->O-~3aY7vceCbnmOL`Po!L{d&Arwo zVVGBS(&$*27j7=a`s7{8#NV(xU~%_yzc$$HF?eM!)o|Z;*KVkJX1ZIDQ^LkA_DxPd zhVO5x2`@Q36@(fU(3!9nwopsWG>CB|NUG^u)rGO=>ojvMr3Q!-yOhv3Xmt=b`~DO0 z4_1^U-gwoX6elZ({QUVZWlfIb2gAm31y{*M{Vb(G99 z_yiDTbU4}u%*D*4$$`>lx8=D_~ekZ#C(!JaVwMNfg-O5`$>H|&TU~wqH)CV+P|vreK>x@L z_~A{j_|7g2UR5i&|68>p#0)wh+OGKk-<_jzuJuiic2qwe8*>53L1vRN-+TNUKxO0P zG%`1*ruAS&2hh_V+W#wWX~EcJZoFXB!3tJVBriq39bfP$l4Ax0A{}`M`-=8@oE#iM z0A7!#Jl@}b6=LAw;Q?g8o{Apm;r+i1TL(u!TtHaiRT&Q$tE_p!%wH8P##a zlNv&wBR-288mqJP%UQ-EKJD;GX}Wxx?eue=+s9{&j$+wK=RlhA=E<0zpARwWkJ|V* z7_Vad6?V%7ErjIN4;2^FG(8@;eC}m&(){1DnpEx<`US~#010hGOqCths}aIBU8S=8 z#1!lAIqm9#aYmw92HgeTtYsXe%7Z3qPbC{)(`Cyf^7b zep+wok6ZRtM3sU6V3eo{=9u&JRfMQ!)Ai(LGFC!tEF>%piC&`$=Esi~AIaOBJiMEd z&!-c);%3f>*VWirqPa>n3kG(}!TJLB$gXvPf}&W;UBl_%91-G2UREi1G2XPa4p&69 zZ*x0C6@2&r(iFi*z956dfD@U9f9rCpe0?hJbsCRNS`ySH#0+-Xm}X^Q_y8dLAW~%| zCDLuNzWz3_wvBZGLj?PJN@28=14Yxuv6sW47`t;r?v;q3mMdT2lI?@Iwa+$jfnttD zDbG78#@B%Um-e0gL!+SWer&j^*+{=xN7d(MPeLnvbugC7!s$xdj=>kzM`%*s_c*_Y z2}7ehH?}sY7?K`l5}+WU*J(llm`p&+J!~tHWH2{3C%=Stl$bHLsatIPd=PDrG7<%M z;)0_(I=cU-L2xhOqqX1WH}AuS*0cEr&f)=)MG_oATLcX~h+^E3@mThuT?eymwis#1 zG1t?Koo*C{9D!&+q=S)AHZ#+QwU!RF6G1ZHhAIHVtus3ILUh zB`dn5J!ALme*Qc|w=Fh>NmhtS4@FWr7*DU4BSV^YA;g60IfCD{nZ=K+VzEul@X}z+ zO4fYwncDaA*0t`^K8z;r+ITsIDu&~NR75;LHmSVtEEjq^HOoISXpiSOI9Ly)D0M=Q zJ}t5R>>~vt0wm%R!+RgyL_=ljI{@c;vwe0xH5AA5TwS1*=TH8!BGP-WYJ7fi5h+NZ zJ7F>%D}%aEC=Kw(>P$dl9al<`+?As{@5uAQ?>z;4I8%SiSAz$pOpct&c5h6*hZCZK z>JLPB|HP{*NHGsbpO_b+EAHpVHe6voO?Go?!)xvY&$bs*p#b!~07rPyXhJtaC@PLvVO zxroEfz?3rVN6mI$tDiH@0sl??+up{VlHOQam_Iu|X_Pl!I5XqB+|xfh7WnMm?Wm*S zfFj6zWH7tcSGu#Yt^bI~EciPERHrm}F3VM!_fflH2r2(h(uQzGXa9@Dn>5=eLdFbE#ftWA%*Wec`4GDl zZ+d|nG;hxf9&W>ATG$oh={3gPdz~y^w_}xB`P)mxazMXY&+fMM%g^`C!}authLNd> z*}C#vRw>ZJd2|_-V~G4Id>)3f{urdw3bypyUEI!OwnvYwYfK zcW$TiJ!82SBKk5z7o$4jx&n0Cgcum24gFzm0y`(Jtpora*GKd8V{Uxh;I?p_BJ({t zGddVRfQGIWE}X#;?5w+J)+;$ee%?i-xkDvAAMn&qJg&av3RpOY(VYIJz( z$gJR7`a{LhRNZ!W>sfHzHXu-~{4!3YKyb}7o#!P82&cxjPajEaFKFKa! zLGPdT(C8ZP<6oWN)=H_K3^xJL!-nvD=M&ANKJM>k6){T=ZZXS<-bZoJlnC(#CJvSO zimL(i{*U{!yxkUObZ0;m2QF`<;`q07r!oC7Isu=GZ{g@8hR(HIsv2!(&*=}9KNAy! z&fYPkqJ3rp;Fmoj@DhK6pGA!j8)!wk`M_pl+QDfZ%^!99S9`O3Z!la5pudaJnYJwI zLkIPvWrw6pXp1loOCZ&W1h=6w?E5A8;kwGQ47XcYF1}_x;!OI857p(HQ|ezux%=L% zCVuzE7@2iQB|MtHXOWuAjHbZ+CgWy72n?wxNHVvJnra2+oOFTyOqfV7^#4*>4t=cI z7+DK8)Md=&-_>n;ziX%Uk>D=Wb$kLVg9E8Fo`OHV2NwZ8X?Wme@Wz+GUV6xwArLasuZ`8E(zTE9%?Y>ejh>aAF^0M06ICG>?l= zqx=3RBjLgAQp;+Cv)(EjjZi97$=y-mk*vc?o(%Q!ii)WD?K^T2Lp0!RXwVcMuBd=3sc>DzWL0?Me5fhAuIhQarqJ0Y5i9(gm;#$ z$<-cI%dw!T#xUiAAx7j13D7GA)B%KH-^%q6exkmXtjgd+_H_bmSm~IQdW4yt^BRjFeKEZo4K5rGwtnDT!ewr>B zrzaqC1V(xv6UVNLXGBF;+wjptKRo#4K1P|Mpt*S=Ss0l-{^RGvH;zD{?_;VJI(t>e z5_cf%sl+VS6jxW=2Tn{R2u<}plg|}XKTQ!arHXS28p=Hk*fdp}8zNq}GTC**F%kWl zIXsL!;?!$K-E;K1Y8N^(IttcvrJqp?Po`bA-@Pi@F(mgJ>2~`3nqd#JH%h2qY*bNI z{Z(E*;@$;MCw`+dfr{%=hUs}{QkDLiPcq;^+@mD}6sc2RTfj>eTUZtETLj~u_Xo;p zS-vD06z&)e294bA1cf{7B?xY{mjUwCy+ScXm)5Mlt$sbWs3bqb-vR}tFL}Zd-Z~Eb zBlZiAL3E$CVudge$lHjLshPtL=`XQUD}<0RFE{f0+964LL4P5y;zH9n|HVHtricy7 zrn&*;NU^pexU)_M>nMcnyHw1d?ky=*lQz8??c%m)Y^mJw?b`6ulE(8h;vK5w2}Ca% zY$nYbJGU<_u~qf-j$<>PVLq4^K~cFSJAm}Oe~?;_?iK2|oH zq;JB5QM}mpk%INLvswPn_7mIF_jGGsKzixOHn8NAO9KJ`ruaav+|IsBF+|KuNPh0; zZ@5=K{zkmc+-89PVvp?W%(?4&mNUuOq8*Z>waeS!6Z}AAU#X-hY0ru3IK4Z{62!!L z(LviRtI!yy$yX5mYk3AFx9aTWLrl#mB)UX}PJFJ8N(IdWxI3LGPI(Zl5F6C-d_Twv zWsMxfu&Ipi)>IIioXP`z@nb}EQrp#;Y2y4zMz5-OX{u-v1PC&Ss}YJwS<9uX(BJ9H zSmp{ceuw(y=Wez90Sg1Bl?A6-Pm zR+vQaoH28GW~XMPJ%^b+K=(QVj{ic|P#t}+m^7;Vb2mv&l^}?))QXIUOw3nbvU>)9 z1>i;OKXL;3YN1WE@d?c<3Uq4>Vg=|Gd}&z3v%>*5O8)rn%(@nTiB~X?p#ENdt+xC3Yp%EvzG%A**3b{e(J#bRzyA!&>n}`#k}j0W^@ridtg{7D zKJfX9JPdO<`IX7Hcxd-!t;P-wGh^YgqmRTxEZxoR<585gElFLSfBOB%qs3ZbrnCbBGpA_7 z+e$zEJ^%^=W(M-4O5|p-<4O`G5r|Pf!mjiwJS6_GC9c;A(oh&Fw^g-UX<X) zx-p2^TjaphiS_l8grpRK3$}jD+|CXt8lOVWlnF8k`E60NV9qqKM?Y^VB=eP69oCj) zn-;t4Y1&1r@#iEwWlMi~gnn0mF@THMBd!r>)t^s)(p5~L#B-H%FW`VYh zGaAB5e}C_kB0@QzkhslF-z2cDH2jDk=oRQImIlhMNT!CBCEGePBh z?FjqzOqQahxzX3l8;Z|R04gY?0 z0Jz8Un+kre7H*5d!^;Kp3U@Z@iSxUM+uN4Pa((5AzV_mp9Ir*-Z(V^r#UCDU08{qK zN0;tdGM;y?9E~A4x>a7Do}@;Tdt52Lm3!B&pE*7XY%V@qbF&$y0p!WtdNPy{uhyq$ zi-T`NtSXbs5?wq>OVuJ&HKAEg-z0xlOWD?DW_$K??J^*xZdkyS$;5q05{5x%`u#uy zx<4Ps4)Ddz$1~K?rJG>{q{D5`poQvZJ?++xRojj$<=M;@kI|opX0RXx47qiY76Yrk z9c$n>5{{2NgR{|`@e-jKPasBm+cdAim<#fJ+S`>&Hbw=>gf}bK-_cDU#3`*Gy7uy9 zcU%2;hWqfe1sZ3urqyJ$uxB}Cc9Qe{xiE1|Fuh@SRDFnHrkttT8zRv?A@gzAyN*{E zHoWP)D_S|LzdDH7*ewHC3ck1B(TArowm$o>wX_ zHA6+W$_=`RnWC%x-N~{j_-lR!7TKLg zuqf0aLe|*U^+{(X?;3nH2zZ>SEnh6^J(MQ7E-HH=%S)f;NGm~y z=hhDD)3Q!#h@eM}p%~~FrYZ3HHlqyW`k3k)OltLXEuwjrD=O{L)7k3ZH}`$(TGUN1 zD6aGv#TypdtqALU8x8(kan7GB)7rFMC@wW$5gfYCl-WG_sO@yXG_yWt;xM6{M*7>b zmPTfD5KRZktx?^d#ak$m>GS8*8uY)VzmHO4uy1hUHt~F(hdP(;wJv>}N9RRuF(7pv zx$FQy{GZsAw-Pg-5~|ub4l{RA$L8hoEFD#(?RJxe#+zxjMpUWXy5;;gOJMV=2*j|3ZZq>4`_0V&nReq8X49)D< zy;O3mD1Ux(edyD@5w$eE=M*q*TzRU34n78Jn@vz}+ z$jW9aq%=g+C4R+w2Huo6tGdRB^K!&85zseLUwZG&v z$(9H?20edG`PlRG)v<-uZrtHj`y!`t*24nh(w3!3Kqi7gfZlJ9fvn@tbRm*AqPY)C zC841lb<)lj-_wX(*cntc(Q!Yd=heN!Lwn7( z(_&YOe}fx2Gpm3*{LC}mh4D~0EABe1*~Rs>Cp7$<#J9O($JR7C zQFG!4Lzh)Km-6eG78b=hnmL$)K4LAZ0je4zjdY*i&pz0LLG;ex{oMrA{Rx(JJM;_TKchd6QO!Z>SNvS zhuS7LxVRD7I5@;w7)x#xOZ3G`LMJL+KX_sb-f17EBrOtHF-4~P{f>lp+D&yizVdF} z8LMbuUAdGg9OhjK;vI{si`Klja$D+OKF8Ib>|Euj@R;^(ZthoOQ`6G7sC~GKRpyvS zgawDG2(h!=|EhB-ZJ{vDWV1`N<<7UNP1*~wtkNECj6~C|O}uKZnALYQnp$>MOH*x1 zJtxjL*rt@A*t%%j^Q~X})BBe4Y(XhkkNr5Z94)hLB!u7RrMlW6m9dEnt;oqndFU(#Q)qFx7$(sVq-o1AgmL+xJ z&yQ-_6Pgz7W8N#zt@t;8R#Y8}#|S$fN?ng`=0a>dzh(G59CtQG9*<6JO272}?YQYY zf^Yn`8}_a&A@&$x^1-lK@634ubmBb8O=>IO6hBNBY#!SDv|05#*fG*7^g4%?TK5l8f!^XNxq*mYU;`on$@WCjjo&Gy3QXJ&p zc`(fF`Fw8nxF^VTN9@aD#O4_i9rO$<>5}G;D}Z9k1Y3{kS-!n{%{g6z(wEb=t*Q4imkV@b9frXrOGn?$vMgoj+;tMw5pHFL z1l=zQVyU@O9@`xZxu=%JQVEHvC2PM4-d_7YOifKj=Nt#~p?BT>`U!H31bD=PC8_Ba-!nX0W^7XJ{O(h>8Qr-PE%8#zotHdYue!09L7YLu#ymcx2_Cf<85F1=8od`|HPVTuhqjb45 z{5|_hl}!kDz_T(ti#2y>PW$5(J;$5xo&FfAY~ZcXw%|BVv=MBYA&eTd=jP2AE!_@1kEjIsIDAi9-V!;V@Iu4VDylEA z2<>>$`H{zf*Asv+RH%L3$`2%IU*)t;a=zs(obI}9JOX)ary9$Ck!f)toNj6?EgfPr z9iCP(ORCVbp-UQZgK8aEpNY=RmuK;)wN74~qxa)Iwq)?=mJbnF*^#@aAPt~_ztWv% z-Ty(4t6SLh$bUmgPNX7eq6~=3TXPgW9`5gz^jZq26(S?v45qPx%w$W;o9?l+QEP7o zqCPZScSqU|dbe)w2v%P#;zn}@1t%d8uY+BY?>oI53gEf-Jhcg&YgNZ8lKBkG3m&jN zc`Xf)bV2EYzx)GY6B82$>^BfOLo|y*7ucfXEKTC_Lp#)u=&XoBFZs0U@81L@87t6> z6aNFX!Z8a-afmi~*;{)3w!UWi*8pbzF}=HN0e^6KNCxzkE}qt+iWgu!*4;uUZwD0e z(k`3l${M!Ii{Fk**9i^*6ZQ%8pmUPWoWB~2xMrH9Ez!Zya;npWcTl1mVW|m-fCzAL zJt`e4@xc8Be`ha_D(5n>TSMj$i2b`TU_TgQ7yR3OEO^$$FHj<{6lG9QR7AyX=IoeL zd=#~c0LpTqCuU*(?)a-@{z9IRs!O3TsrS(joCak6p^2npqD7P zR_|$ZXL{|GDM;1i`3af7_LJP|-!WKx*x`RQ|1zS%Z1WkWb+hYk#Y%uHIJM^rdMj_z z%>zb}U{KKB8T+iLv`c%pxajy21pC~6{%^ZG6$Md6O-;;``p*qd7?r|@h!)LKv7kU0 z5CS>6dAa1%cj^USU)@zL=-MM%*X@;Zze^_-P?wR4poIRlK+1Bt)oPAWc?sv$OTFq+OC>+zFvB>d-k7`t4AX?uZ#9q1Dl35p?P>VOJ4 z0M|Hd%2fT1#|zBd!6?8e5Gw@K{^vQ^X=)xo3W9@zNP#6A%LE`Klo)?Xj&!0pSLQ;_ zTV4$^{|!5i7!Xia65H4ADVLxEcQML)GQZIitZdT<3==|ZS4w1eRFSHhnf8U>F(fR%(BZiX19;Gyz+cZYH2ZeYM4*-Ptz+xkEeC^TUpQZn#wb zh>!hj%ed1*ZWlWvtU!rEQcG*A%~&SCj9|>iuqyA)h-l3vM3e4{{QJA$NGSVR?S~?^!0Pv_mrN%)atqW?!Z+6@o^v zD^imT6p{Gxw4(SoQBhF^i}cfn&Pz7d*4C54U$U&x`Rf>cy$HSs3RNR{_pXRpI)Cu| zp3JDpyd0zDk>@@ z6j)qFrYZ1*m^m!&gMkg%yl9(Pb`h9O;4j6y1L*TeH{NYcH@pdULxC-}h6;S_reP68 z64Fy*fV3wlKk_T@c(yq1P0N(V=eHj}Nz`jn&xxABkFj5*D)OyB+jOEwd=cC!PR~4Q z%lHj+ODg%AC;s~Ix$9alpmp_Hp7OAP0PY1iMaJTy&Fw^Q>bo{!e6gja<)qvivq!Yi z3ETGfYYGyDBT&o8`Th+%=7Wi;w~w?={nXf_KFUk;o#t6amy{1y*_CeL5o15J2pe|=J z!b8jY$Oa0MbxsJHy0%x(KEuvjO3BOmPi|LFm|b@OP%P=A&GFiRfC3|8O5IAb6)`2` zPm#F7H4;WZ(dceL{x}EQvg}ar8&O(?E!*zp8t01r1^IpS3X=2w-Mbeo4N@n+1d*Ii zRqtVu;ituno*(bjfO5g>BE0&>!opynszR!s4meywa-@ziUMn^4tpPkplz9BWlYHf$ zrQzdb_-;g>W$WTh2bdkT3tVqJs6GlRp-`~-LRpXWaC5>L>5#)X_e~zCdQg18VwmDV z1ARf!I9U9>?5mb$!IxktSK^@aA=a7QU-8b-`9R{ z4;JU=n;=0F^I6kt(BGj@8$v{DX`%0k(#^t6^KDtxL+FdTkNtes(j{(Ah~F~RQ25jI zQbjo3iwQx$O*4u?jg%-N4@zUb*gt$pjxVqZu@TOj+c$+nuEqAasTBI2Ccnp_j()L` zxQSb=t(*UH9x>b1;1T*#htky>eo6@8g1wSaLW#FMtvBCVeSUs!A`3jUsjIfVRMTm4 zl(+36SJ<#5s1z*!Q{e!WDN(&r?8^IcphAbyyDB!QVb(Munxe=6iLyl`zh^IRc4m9e zPvZ@`SDpxIOSu$CWi^v4TJkwsa8J zSo2uD-D%R+=-f9=nC*@5-N)Q^QAF<|N2TzW1odo&_~RWPueS5roRGN?s5Ksmv^!;s zz(CfKoRK2|Kp9LH4S+6rmXcPLSATZiTpzoe%{eI>40v>rkpFtJh7Oz)G!p%@;wCl# z8~$^|_j5u5CKQB(u32HS>qQ8OSG(Oj$4xX)7H`_aSfnajivRrLJlC=9Jm=$8?3Jmh zx4hDLp;-lHmaIcGi0i6ykL$X8kefpG9wemf8 zmY3yQ4`VVep`hW`v9EGMsbMiKL-pc$oWy$0dVX16Rpj^Z<`6*py4l154$~E?rz0RB zC{XlJz7kMML;2ud2)klFuXU9F_lktM9aO8^v$TLYGvyVB@+rf1nuOZ8hN~Skv6vtu zmIhQwMb>46ayVwJ(rxVlh(X969$;>?8~)Lcfl4$}IPLTn5#OmQ=MECPJcPc6O5&I_ z7+4uw+oK;sUtwJ`X1TuXdlD3nL@a6+lQ6#qAt7QfSWxoO$UGe6C+NT?FV<`7ibv^$ z0{9k0Tn05w(*y(6YXtT0QUNY2rlkL}9tfWU#K{r3Jk7EhOzrPme<)Yixs<8m8XQKn;?`f`8Dw<1qXLWkm{Yv#YGP^ zzFrKoQpCUg&j3LNO97eUN^UYRW&+0K9IJoLB8mc@p`<;OKwWIe%E~I*Zh#KxGYo9* zn;>LZkOxXpX=!)_wz5{LCx+}yM{hJ$E(c3VC<%%GYcH^g{kd(s>A(f5LaZsJ4t9XL z@b`tehxLf`k=WT6*&`l3GT^#z7{vaFM#b^F0f;}7lEeWFy@ob<4jkQ(HZ_h67?YPny*(PE9Ec*%rc@In?OQ0buxGhGy z;0OlhQ=O#97ZnVJt<{|!Q{|EH7KOqRpU_P8dS(U&hKWXapVgM3k&4zo|Ldn`q=Gkn z{E>x+40&eh#Lg0Z6Hea#1iKQ92zRk@R z@0KS~a$8HbxxL#M$Zch6v_R3pH(8qD#qxN32oILANljv?sNOc45zCZbv2J@E4q|k` z6l8wRzkyhwj5Fj32Y)p2{39*Rl^7H*RM?qe$1O=E}gDu zYnm8+fiYV9mz&Nl{tT~jQG%$8o*xzUv9!2YvM$WT0W{s!3pvGt>|-f>*Zq$_gx)ev zeEtS(8x~u3AE3X8P8xs3KE8D9^Rj(=Ij!Vxc-|Ivg__9DPAUkji2go973vBGl0E|T zY>W$QpaYlb)m6g5z#TrI)ILguO56)C-*OzEV6WoWuWwo?V*OF`f!KIrqHuUCp6BNrlZhE*vFoCZc3}*%O@xatyKC1ecg%UvrJmrWaR|>*pO;#V?FXiP z9unzx(Rz*H`Wq0yD*nY_kXDuPJ0Qz-eS}}@^_<_j_dUs6Pw#1r#B2S`XQGi0*d>Gj zHvd)U)|h`8gvWn#Yhw)td2HjCp0h;6Z3JO$6a?2+>cI_Xdtei@ZZ%An_XSsUVZ}T7$kCFgo)kd!i75E zGkv$KpeIZnv99g*x7|VCZ4HMv2}8!hy&d0!QMed|V4Ri%)Dgg11}jd_c1>wz{+`GY zOMQ)R;>%6BK?q<4e;7*tA5w`ZA=vXBX44I=&4B^w9FOUJZC@Dm&x3^8fVXNxI6+0o z?t0_)K)ol)&I~8q-6o24SwrTB@Q+joCO#bRfq0!2qW;z1>yPZXO`1-lCE)i1g@Kko zZObqU)7zh(211Vz)#mn%|J>bUy+kpl)63I~!$4jwH&j<6OvnJdiZGG>#m*Yo+cOjC zumlL<0YkwiR3_GS0@8)WZ(>ySFn5%SbQ1=A-ugR~E^ueW;I{#I*PEcN>)5cnqHx{o zBV>7B;pYj;2NJ+}Bi_CBKYFWgARhw4N!TG&fA{~n@ndkA)!9Zj83~g^O|%dfU@X5C zNB^<(f_ic{EYVr`q+rlDqj@!vV}aKXK}stNQZ_Ir*LnFB%wX_tw=h+Q%s>pLrw|6> z|L$kPD1!S(DznN6yZS-mHeupy&|~r2I)gzhkRs&ZuRT1oYJzvU=uoB^IDo!O-!lgI zRt(8HxPUq@;mHgpdLlYoxNS>QaQqM^RNFEYcfTIsezlQ&6xT zO~?N}BYKDvo$+O2tRMoYKp^G~qJ)j0oYr=ENSoc?owK%FCkC+`rekL=nc)7aLIuIT z8oK>P96ONU?sUADDWylg_IDi`D~U8sG6S}w5TT|wArea2TU%SMmwW#ZPo$)z%Icgg z?cOjD;p3+{=i#!NYG~sAj8|}1RsrD+9}8Dt?G#4_haa@Gwcy-PnS+`2EF3?9A4$4i zS}M|k0%vPr6N;OV+^|w(GL&ZnFcPmUMttJg8;BwLZ6pvi_OpmwyFIIQ7ee*Nf)j`9 z5`*j`-JCWpKf%KKy|aHen2;3^eQ>ah_hKO!fbI8!8n}J1%c(xkT{r_~X-(4AAR6$F zr2z;BU_D(_i~!q_c)EOz_(NZskAt+}0$GEZN)8Q+uIGcXmoAyZHt^wfizBFLZzdSi; zSFg7ei*j1pC0U+uoidd6fhPsX#d{bpKC8T0R5ur;uHeOevq15PQRTiPGmLL z1+ih>DXlj$m2gJ>+DuZ>;~+z8V;evI!z9}0LdtCOYLe!Mhlhoi%u3N@3{)g27_c+c zdW|K6qgP*0l$BKt8+F*{Rs?c(^%VsC{=q@eQHlK}a|)TRZ$Il@3M`c8lLvqM3<78@ z?a!Qg6FSq4@rye@m#{0+%%tTR4EYzP2j3X z5l|2!AVrZwmtF)y5NT2rrH86W4^0gn?j*spzO}wz_wQYoA6bhv@64Xrv-f^>nRnjN zbe>|-G!<74tu;&u>Fjs%+|}g8U1a_m#<%+sK@Q2t%xnb?^SmnqC#Ed)^rUCbT*Xat zSZG81V1bK~Ugo4iWf$xHho59Bd$>9K?h z&}hXN9=cEza&eKfqE&&Xd`6LJuno8VtP(kLBl>iAepzg)<-$X80U!FG;Srsj;CX%+ z?1VRu5(6xg^;6;NBs&m>*$DiYR&G*|dm1b)+fXyoZDZrqVf(2xVmhbZs+DXKI}6~vT`@B zsi@HkcOUn)a;fMlI?MEelT#A~Bw{RtdG^87V)STd{n9MX%tTxLv?K7#S=RIAJ`?`BNKyG*^Mv097GCkBDWnGw+U?ELp;U`jJ z0}wyuqjFk9gIumwsr>pj;PDMxj>xq8k#6RY4b^dXUDFBQPIfjBF|3mf~}+5FY=`(`mYr|S_}srz@<2# z!#hTQh~N9Zw;@0H2Z$_u_jl(-K&4ji>Q@kRpr z1t-sml7?E6vIbjSPR<=NnMlIy=>_l2Db|ZC#e`9qMRDt3oP-?YPy2D7hTWalg&L;i zLQPKS_2YP#@t}$4FW*W^9-{6nD6zr4cyQ*J$}YVB%(}m3^n$5=JQ?lb3h%gD)@u5LgWZTml=k-8G6HAh8f4bQj+ zq9}_tbfvVekL^}_qN`$elJUL+PL(8*SA(7E03{d(Tp6Pa;U`>J-Ax~QxF4vp>|>W^ zSRW54h6}}O-U+gaq3k4h%#bdGm%uZK8Qk0CHaPUsLr$M@H2)8ygd3o=2Ik5F!!tW9 z?GGDxxcbPk=x!=CJH^)dtCcy<%t$C9DI-8&500;RLdx#C$1Z#GHXm;gNe!9BGp$Ai z#ZoCiKr$825irjfn|Y*a1t)mc*S!!_aA<%WcN~Yj+c)?pP0tOImkp)+AJ8J(IIVX z)m=yS$o-@9X1nhmR)xhKU85=#mQaq^ki-tH_45%Iyaee~c6Vxkr@U8soG>(W3a z?YKNz+lr@zjUrtTrL#9#VSSIFZT@H;^UbM>q{%fsR5rQ_^ldLPIkg|Mt3|rjy zW%hT8<2&uXB2;VCfZUM4&XJbZYj2wk@K^3QKWJ*94w-ojbm6FM9DR7AQ?eJ6dV`NT z_ZsXQ7%sDMr5t1Qgz2zrHff6gQa_ck0&(?}wun`q$5Aq>s!#4llpdz-mmr0@v8gRv z*h!_d=Hl<`f!#p|(r^=sp_7)MWN^q+PaN;%H|;-3<)4oY!5pefD#-h0&KG8Wy~7(3 znQ(^M6i8XAvt95jts!#j^jf~1_!Gdc?+HPXh)xp4*C0M9XgN_ zqL(OaN8G2(u5|61`-^c_#D=a+Nk*AceAggNQ$s(|13nndol#lH`gG*;oQotv&#qmJ zHvb{ShBTJG6*Er~mAAax)amizsV7Zq5Rb9@Fx+3r!h+7*NiTjKzByDARXP^VOWSEg z6n6O}G~n#sM7Tb4$Y#h-j;pYAEty9&MJR&dkq!wxu|*ci?)UDNjO#=tld?3<;;YRh z*=f6`hS-q7Ds9&p7X*3P3f8Upc1KAmD@)Bxo}%s$V#Ab>1l4tOi?Aiv&TRHw_#U9B z&{c~3!>%cT9SaI!6jt64nCO6LZf*^}xl7=h>2;3Nz<91%H*5=j2~_+oS=y2lL`Zhs z%g!SFl2g0ibdx+UGR33WTk^-`GrxA)I8)|?DrhuLa$|6=(%;hOQ04Zk6jiskDw=8s zAl3`R?~kv^rq>UKNxT0LPD*$B`)-C%fLqBi>SCbqlQ2AsgVpdnDe>cOk77^J*o?R#Hn9!nYV&oPajS`2^~v?bD0kVeKX+%UY@LNi&Px=9P2Qw(HquIf z6&iit+@`ei9_boJ9{a-5+6@yl^W99gtTgB1DH|Tzh|MCbIEZF(I)0A>-LDiC+4bk- zFOzCJll1!Cs)DXQ!ao|R9Ezfoo^O1`_1N)EM9+qNx_$8L7psjAhMpa>ra>eYby>g= z8tt1UFg?tD(G9cX(mW)*nP5!x=(=d*BwFi2O&|h>Pl*^N61|!ml7`=RF5sMQWAj$mKyK zO@26zBSIh?!k^3kn6WD!ytme5Ke&$HP5p&l*D?>VM0l|q12sC=&B@fGiB4pcQA7?9raXKS2ylAGdCxO(IKvu|hnzCNT5E5XP3!)i0>}Z4L5>sr{8g{CV)Z`+fJ;Q5B_`@yWh(-^qoCln8Og!%-_X=k8hiS4X1G!vD$V0A+780bz*F zul4m5yn|ExJzk)vt&)*-{cEGOw+*aR{ib!ni;gMf(TWJTEvm7#@n45M!IdTp1@qof z6Tt9e&J+<55%zwQo7{y_ZwgKHUhgdn>VeSU7T=akpH%zC{7Sn?#Q2Si-}NHNKusVT2Ck3q9~{J)_E*%Cg#=j-L3i8wNS|%u$*;-6C4A7%T1ilt zw#4MlU$vl&T}DV%@{?iydUVkpq<^ln=j7(L=ROZj7IC$|<_PTIZQCQ0&eny6q=;w^ zOtA$QAM}^>O*gl8v8G{tVmvD%1?A`GXO@0<_k}C}HD4%R$I+wI%_8T}qX@3e7M_?S z#C^?-%F2G{uHf| zrzwyF%d^!n6UwC|f#7}NA%YqFcwj@_1k0J~e}0zXgzBa~35)l)FSkmUY>QAnC(1Q<_7`QRMX<4$; z2i{LqFClAwCo|xA?`H=cNc>OPI5Aka+#UTphGqh+KB1Q}LJ@n3ux=EYRa-n`QjxvF zyQkD9{HJ!%J8V(bArYNMGZPbC&T+MG=B`ShSosv0b1S&KMdPC!+Q6$h*@xj22-c?Q ze}3-Rk-Y?oe|H6X)j#gMii@&?d8B$71IUxinrC_OuDZ*7YSx|BN1^K17yi~bpf&AH25b&p?p?|qZ%^~dHiVDuj literal 0 HcmV?d00001 diff --git a/doc/workflow/forking/branch_select.png b/doc/workflow/forking/branch_select.png new file mode 100644 index 0000000000000000000000000000000000000000..275f64d113baa4800fe6a49c366d5bb3c1237068 GIT binary patch literal 55352 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 literal 0 HcmV?d00001 diff --git a/doc/workflow/forking/fork_button.png b/doc/workflow/forking/fork_button.png new file mode 100644 index 0000000000000000000000000000000000000000..def4266476ad23531e7299ace79fa4e999b3d94c GIT binary patch literal 68271 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