summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitlab-ci.yml38
-rw-r--r--.rspec1
-rw-r--r--.rubocop.yml2
-rw-r--r--CHANGELOG77
-rw-r--r--CONTRIBUTING.md12
-rw-r--r--Gemfile52
-rw-r--r--Gemfile.lock141
-rw-r--r--README.md72
-rw-r--r--VERSION2
-rw-r--r--app/assets/images/favicon.icobin32988 -> 5430 bytes
-rw-r--r--app/assets/images/logo-white.pngbin7699 -> 0 bytes
-rw-r--r--app/assets/images/logo.svg26
-rw-r--r--app/assets/images/logo_wordmark.svg26
-rw-r--r--app/assets/images/msapplication-tile.pngbin0 -> 6102 bytes
-rw-r--r--app/assets/images/touch-icon-ipad-retina.pngbin0 -> 8130 bytes
-rw-r--r--app/assets/images/touch-icon-ipad.pngbin0 -> 3493 bytes
-rw-r--r--app/assets/images/touch-icon-iphone-retina.pngbin0 -> 4997 bytes
-rw-r--r--app/assets/images/touch-icon-iphone.pngbin0 -> 2766 bytes
-rw-r--r--app/assets/javascripts/application.js.coffee7
-rw-r--r--app/assets/javascripts/behaviors/requires_input.js.coffee39
-rw-r--r--app/assets/javascripts/blob/blob.js.coffee73
-rw-r--r--app/assets/javascripts/blob/edit_blob.js.coffee1
-rw-r--r--app/assets/javascripts/blob/new_blob.js.coffee1
-rw-r--r--app/assets/javascripts/dispatcher.js.coffee24
-rw-r--r--app/assets/javascripts/dropzone_input.js.coffee6
-rw-r--r--app/assets/javascripts/issuable_context.js.coffee22
-rw-r--r--app/assets/javascripts/issuable_form.js.coffee4
-rw-r--r--app/assets/javascripts/issue.js.coffee19
-rw-r--r--app/assets/javascripts/labels.js.coffee5
-rw-r--r--app/assets/javascripts/line_highlighter.js.coffee148
-rw-r--r--app/assets/javascripts/merge_request.js.coffee118
-rw-r--r--app/assets/javascripts/merge_request_tabs.js.coffee154
-rw-r--r--app/assets/javascripts/merge_request_widget.js.coffee4
-rw-r--r--app/assets/javascripts/notes.js.coffee2
-rw-r--r--app/assets/javascripts/pager.js.coffee2
-rw-r--r--app/assets/javascripts/project_new.js.coffee6
-rw-r--r--app/assets/javascripts/project_show.js.coffee14
-rw-r--r--app/assets/javascripts/shortcuts_navigation.coffee1
-rw-r--r--app/assets/stylesheets/base/mixins.scss2
-rw-r--r--app/assets/stylesheets/generic/common.scss9
-rw-r--r--app/assets/stylesheets/generic/gfm.scss4
-rw-r--r--app/assets/stylesheets/generic/header.scss37
-rw-r--r--app/assets/stylesheets/generic/mobile.scss24
-rw-r--r--app/assets/stylesheets/generic/sidebar.scss117
-rw-r--r--app/assets/stylesheets/generic/typography.scss8
-rw-r--r--app/assets/stylesheets/pages/issues.scss6
-rw-r--r--app/assets/stylesheets/pages/notes.scss16
-rw-r--r--app/assets/stylesheets/pages/projects.scss73
-rw-r--r--app/assets/stylesheets/themes/gitlab-theme.scss4
-rw-r--r--app/controllers/admin/identities_controller.rb41
-rw-r--r--app/controllers/admin/projects_controller.rb3
-rw-r--r--app/controllers/admin/users_controller.rb27
-rw-r--r--app/controllers/application_controller.rb8
-rw-r--r--app/controllers/autocomplete_controller.rb37
-rw-r--r--app/controllers/dashboard_controller.rb4
-rw-r--r--app/controllers/groups_controller.rb2
-rw-r--r--app/controllers/oauth/authorized_applications_controller.rb7
-rw-r--r--app/controllers/omniauth_callbacks_controller.rb8
-rw-r--r--app/controllers/passwords_controller.rb2
-rw-r--r--app/controllers/profiles/preferences_controller.rb1
-rw-r--r--app/controllers/profiles/two_factor_auths_controller.rb10
-rw-r--r--app/controllers/profiles_controller.rb10
-rw-r--r--app/controllers/projects/branches_controller.rb4
-rw-r--r--app/controllers/projects/graphs_controller.rb7
-rw-r--r--app/controllers/projects/issues_controller.rb9
-rw-r--r--app/controllers/projects/merge_requests_controller.rb15
-rw-r--r--app/controllers/projects/milestones_controller.rb7
-rw-r--r--app/controllers/projects/notes_controller.rb2
-rw-r--r--app/controllers/projects/refs_controller.rb26
-rw-r--r--app/controllers/projects/services_controller.rb3
-rw-r--r--app/controllers/projects/snippets_controller.rb8
-rw-r--r--app/controllers/projects/wikis_controller.rb6
-rw-r--r--app/controllers/projects_controller.rb36
-rw-r--r--app/controllers/registrations_controller.rb2
-rw-r--r--app/controllers/sessions_controller.rb9
-rw-r--r--app/controllers/snippets_controller.rb6
-rw-r--r--app/finders/issuable_finder.rb18
-rw-r--r--app/helpers/appearances_helper.rb2
-rw-r--r--app/helpers/application_helper.rb81
-rw-r--r--app/helpers/application_settings_helper.rb2
-rw-r--r--app/helpers/broadcast_messages_helper.rb15
-rw-r--r--app/helpers/events_helper.rb2
-rw-r--r--app/helpers/gitlab_markdown_helper.rb23
-rw-r--r--app/helpers/gitlab_routing_helper.rb12
-rw-r--r--app/helpers/groups_helper.rb8
-rw-r--r--app/helpers/icons_helper.rb2
-rw-r--r--app/helpers/issues_helper.rb4
-rw-r--r--app/helpers/milestones_helper.rb2
-rw-r--r--app/helpers/notes_helper.rb4
-rw-r--r--app/helpers/notifications_helper.rb2
-rw-r--r--app/helpers/oauth_helper.rb2
-rw-r--r--app/helpers/preferences_helper.rb12
-rw-r--r--app/helpers/projects_helper.rb106
-rw-r--r--app/helpers/submodule_helper.rb2
-rw-r--r--app/helpers/visibility_level_helper.rb6
-rw-r--r--app/models/ability.rb85
-rw-r--r--app/models/audit_event.rb19
-rw-r--r--app/models/commit.rb4
-rw-r--r--app/models/concerns/mentionable.rb44
-rw-r--r--app/models/concerns/participable.rb8
-rw-r--r--app/models/event.rb2
-rw-r--r--app/models/identity.rb1
-rw-r--r--app/models/key.rb1
-rw-r--r--app/models/merge_request.rb27
-rw-r--r--app/models/milestone.rb6
-rw-r--r--app/models/note.rb7
-rw-r--r--app/models/project.rb4
-rw-r--r--app/models/project_services/gitlab_ci_service.rb9
-rw-r--r--app/models/project_services/irker_service.rb109
-rw-r--r--app/models/repository.rb92
-rw-r--r--app/models/security_event.rb2
-rw-r--r--app/models/snippet.rb1
-rw-r--r--app/models/user.rb69
-rw-r--r--app/services/audit_event_service.rb25
-rw-r--r--app/services/delete_user_service.rb6
-rw-r--r--app/services/git_push_service.rb4
-rw-r--r--app/services/git_tag_push_service.rb6
-rw-r--r--app/services/issuable_base_service.rb8
-rw-r--r--app/services/issues/bulk_update_service.rb2
-rw-r--r--app/services/issues/create_service.rb1
-rw-r--r--app/services/issues/update_service.rb14
-rw-r--r--app/services/merge_requests/auto_merge_service.rb45
-rw-r--r--app/services/merge_requests/create_service.rb1
-rw-r--r--app/services/merge_requests/update_service.rb15
-rw-r--r--app/services/notes/create_service.rb2
-rw-r--r--app/services/notes/update_service.rb2
-rw-r--r--app/services/projects/transfer_service.rb13
-rw-r--r--app/services/update_snippet_service.rb4
-rw-r--r--app/views/admin/application_settings/_form.html.haml108
-rw-r--r--app/views/admin/application_settings/show.html.haml2
-rw-r--r--app/views/admin/deploy_keys/index.html.haml3
-rw-r--r--app/views/admin/identities/_form.html.haml19
-rw-r--r--app/views/admin/identities/_identity.html.haml12
-rw-r--r--app/views/admin/identities/edit.html.haml6
-rw-r--r--app/views/admin/identities/index.html.haml13
-rw-r--r--app/views/admin/users/_head.html.haml23
-rw-r--r--app/views/admin/users/groups.html.haml19
-rw-r--r--app/views/admin/users/index.html.haml10
-rw-r--r--app/views/admin/users/keys.html.haml3
-rw-r--r--app/views/admin/users/projects.html.haml43
-rw-r--r--app/views/admin/users/show.html.haml387
-rw-r--r--app/views/dashboard/issues.html.haml2
-rw-r--r--app/views/dashboard/merge_requests.html.haml2
-rw-r--r--app/views/doorkeeper/authorized_applications/_delete_form.html.haml9
-rw-r--r--app/views/events/_event.html.haml22
-rw-r--r--app/views/explore/projects/_project.html.haml2
-rw-r--r--app/views/groups/edit.html.haml1
-rw-r--r--app/views/groups/group_members/_group_member.html.haml2
-rw-r--r--app/views/groups/issues.html.haml2
-rw-r--r--app/views/groups/merge_requests.html.haml2
-rw-r--r--app/views/help/_shortcuts.html.haml6
-rw-r--r--app/views/layouts/_head.html.haml19
-rw-r--r--app/views/layouts/_page.html.haml2
-rw-r--r--app/views/layouts/group.html.haml2
-rw-r--r--app/views/layouts/group_settings.html.haml4
-rw-r--r--app/views/layouts/header/_default.html.haml3
-rw-r--r--app/views/layouts/header/_empty.html.haml2
-rw-r--r--app/views/layouts/header/_public.html.haml3
-rw-r--r--app/views/layouts/nav/_group.html.haml30
-rw-r--r--app/views/layouts/nav/_group_settings.html.haml20
-rw-r--r--app/views/layouts/nav/_profile.html.haml14
-rw-r--r--app/views/layouts/nav/_project.html.haml26
-rw-r--r--app/views/layouts/nav/_project_settings.html.haml10
-rw-r--r--app/views/profiles/_event_table.html.haml16
-rw-r--r--app/views/profiles/accounts/show.html.haml2
-rw-r--r--app/views/profiles/applications.html.haml11
-rw-r--r--app/views/profiles/audit_log.html.haml5
-rw-r--r--app/views/profiles/history.html.haml11
-rw-r--r--app/views/profiles/preferences/show.html.haml8
-rw-r--r--app/views/projects/_activity.html.haml15
-rw-r--r--app/views/projects/_aside.html.haml108
-rw-r--r--app/views/projects/_home_panel.html.haml59
-rw-r--r--app/views/projects/_last_push.html.haml14
-rw-r--r--app/views/projects/_readme.html.haml24
-rw-r--r--app/views/projects/_section.html.haml36
-rw-r--r--app/views/projects/_zen.html.haml2
-rw-r--r--app/views/projects/activity.html.haml1
-rw-r--r--app/views/projects/blob/_remove.html.haml5
-rw-r--r--app/views/projects/blob/edit.html.haml8
-rw-r--r--app/views/projects/blob/new.html.haml2
-rw-r--r--app/views/projects/blob/show.html.haml3
-rw-r--r--app/views/projects/branches/new.html.haml3
-rw-r--r--app/views/projects/buttons/_dropdown.html.haml31
-rw-r--r--app/views/projects/buttons/_fork.html.haml13
-rw-r--r--app/views/projects/buttons/_star.html.haml22
-rw-r--r--app/views/projects/commits/_commit.html.haml57
-rw-r--r--app/views/projects/commits/show.html.haml3
-rw-r--r--app/views/projects/compare/_form.html.haml8
-rw-r--r--app/views/projects/diffs/_diffs.html.haml3
-rw-r--r--app/views/projects/diffs/_parallel_view.html.haml4
-rw-r--r--app/views/projects/diffs/_text_file.html.haml2
-rw-r--r--app/views/projects/diffs/_warning.html.haml2
-rw-r--r--app/views/projects/edit.html.haml4
-rw-r--r--app/views/projects/empty.html.haml34
-rw-r--r--app/views/projects/graphs/commits.html.haml4
-rw-r--r--app/views/projects/graphs/show.html.haml6
-rw-r--r--app/views/projects/issues/_discussion.html.haml8
-rw-r--r--app/views/projects/issues/_form.html.haml2
-rw-r--r--app/views/projects/issues/_issue.html.haml77
-rw-r--r--app/views/projects/issues/_issue_context.html.haml46
-rw-r--r--app/views/projects/issues/index.html.haml6
-rw-r--r--app/views/projects/issues/show.html.haml8
-rw-r--r--app/views/projects/issues/update.js.haml18
-rw-r--r--app/views/projects/labels/_form.html.haml2
-rw-r--r--app/views/projects/labels/_label.html.haml4
-rw-r--r--app/views/projects/merge_requests/_discussion.html.haml4
-rw-r--r--app/views/projects/merge_requests/_form.html.haml5
-rw-r--r--app/views/projects/merge_requests/_merge_request.html.haml8
-rw-r--r--app/views/projects/merge_requests/_new_compare.html.haml10
-rw-r--r--app/views/projects/merge_requests/_new_submit.html.haml4
-rw-r--r--app/views/projects/merge_requests/_show.html.haml6
-rw-r--r--app/views/projects/merge_requests/branch_from.js.haml1
-rw-r--r--app/views/projects/merge_requests/branch_to.js.haml1
-rw-r--r--app/views/projects/merge_requests/index.html.haml7
-rw-r--r--app/views/projects/merge_requests/show/_context.html.haml48
-rw-r--r--app/views/projects/merge_requests/show/_mr_box.html.haml2
-rw-r--r--app/views/projects/merge_requests/show/_mr_title.html.haml9
-rw-r--r--app/views/projects/merge_requests/show/_participants.html.haml4
-rw-r--r--app/views/projects/merge_requests/update.js.haml11
-rw-r--r--app/views/projects/merge_requests/widget/_closed.html.haml2
-rw-r--r--app/views/projects/merge_requests/widget/_heading.html.haml6
-rw-r--r--app/views/projects/merge_requests/widget/_merged.html.haml2
-rw-r--r--app/views/projects/merge_requests/widget/open/_accept.html.haml4
-rw-r--r--app/views/projects/milestones/_form.html.haml5
-rw-r--r--app/views/projects/milestones/_milestone.html.haml4
-rw-r--r--app/views/projects/milestones/show.html.haml9
-rw-r--r--app/views/projects/network/show.html.haml6
-rw-r--r--app/views/projects/new.html.haml6
-rw-r--r--app/views/projects/notes/_edit_form.html.haml4
-rw-r--r--app/views/projects/notes/_form.html.haml10
-rw-r--r--app/views/projects/notes/_note.html.haml9
-rw-r--r--app/views/projects/notes/_notes_with_form.html.haml2
-rw-r--r--app/views/projects/notes/discussions/_active.html.haml4
-rw-r--r--app/views/projects/notes/discussions/_commit.html.haml2
-rw-r--r--app/views/projects/notes/discussions/_outdated.html.haml2
-rw-r--r--app/views/projects/show.html.haml55
-rw-r--r--app/views/projects/snippets/index.html.haml2
-rw-r--r--app/views/projects/snippets/show.html.haml2
-rw-r--r--app/views/projects/tree/show.html.haml4
-rw-r--r--app/views/projects/wikis/_main_links.html.haml2
-rw-r--r--app/views/projects/wikis/_nav.html.haml2
-rw-r--r--app/views/search/results/_blob.html.haml1
-rw-r--r--app/views/search/results/_merge_request.html.haml4
-rw-r--r--app/views/search/results/_wiki_blob.html.haml1
-rw-r--r--app/views/shared/_clone_panel.html.haml2
-rw-r--r--app/views/shared/_field.html.haml4
-rw-r--r--app/views/shared/_file_highlight.html.haml6
-rw-r--r--app/views/shared/_visibility_radios.html.haml1
-rw-r--r--app/views/shared/issuable/_context.html.haml50
-rw-r--r--app/views/shared/issuable/_filter.html.haml (renamed from app/views/shared/_issuable_filter.html.haml)21
-rw-r--r--app/views/shared/issuable/_form.html.haml (renamed from app/views/projects/_issuable_form.html.haml)88
-rw-r--r--app/views/shared/issuable/_search_form.html.haml (renamed from app/views/shared/_issuable_search_form.html.haml)0
-rw-r--r--app/views/shared/snippets/_form.html.haml2
-rw-r--r--app/views/snippets/show.html.haml2
-rw-r--r--app/views/users/show.html.haml3
-rw-r--r--app/workers/irker_worker.rb4
-rw-r--r--app/workers/project_cache_worker.rb15
-rw-r--r--app/workers/repository_import_worker.rb2
-rw-r--r--config/application.rb1
-rw-r--r--config/initializers/6_rack_profiler.rb2
-rw-r--r--config/initializers/doorkeeper.rb3
-rw-r--r--config/routes.rb12
-rw-r--r--db/fixtures/development/04_project.rb35
-rw-r--r--db/migrate/20141118150935_add_audit_event.rb22
-rw-r--r--db/migrate/20150620233230_add_default_otp_required_for_login_value.rb11
-rw-r--r--db/migrate/20150713160110_add_project_view_to_users.rb5
-rw-r--r--db/migrate/20150717130904_add_commits_count_to_project.rb5
-rw-r--r--db/schema.rb20
-rw-r--r--doc/README.md1
-rw-r--r--doc/api/README.md3
-rw-r--r--doc/api/merge_requests.md15
-rw-r--r--doc/api/projects.md11
-rw-r--r--doc/api/settings.md88
-rw-r--r--doc/api/users.md34
-rw-r--r--doc/gitlab-basics/README.md17
-rw-r--r--doc/gitlab-basics/basic-git-commands.md59
-rw-r--r--doc/gitlab-basics/basicsimages/add_new_merge_request.pngbin0 -> 9467 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/add_sshkey.pngbin0 -> 1463 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/branch_info.pngbin0 -> 7978 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/branch_name.pngbin0 -> 2199 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/branches.pngbin0 -> 3653 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/click-on-new-group.pngbin0 -> 2063 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/commit_changes.pngbin0 -> 5567 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/commit_message.pngbin0 -> 5707 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/commits.pngbin0 -> 4258 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/compare_braches.pngbin0 -> 1624 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/create_file.pngbin0 -> 2524 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/create_group.pngbin0 -> 3224 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/edit_file.pngbin0 -> 2259 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/file_located.pngbin0 -> 3156 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/file_name.pngbin0 -> 2544 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/find_file.pngbin0 -> 8840 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/find_group.pngbin0 -> 6159 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/fork.pngbin0 -> 1046 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/group_info.pngbin0 -> 16217 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/groups.pngbin0 -> 4857 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/https.pngbin0 -> 2887 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/image_file.pngbin0 -> 2939 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/issue_title.pngbin0 -> 9059 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/issues.pngbin0 -> 4332 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/key.pngbin0 -> 1264 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/merge_requests.pngbin0 -> 4381 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/new_issue.pngbin0 -> 2974 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/new_merge_request.pngbin0 -> 3227 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/new_project.pngbin0 -> 2319 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/newbranch.pngbin0 -> 1314 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/paste_sshkey.pngbin0 -> 8620 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/profile_settings.pngbin0 -> 1194 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/project_info.pngbin0 -> 21862 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/public_file_link.pngbin0 -> 3038 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/select-group.pngbin0 -> 6075 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/select-group2.pngbin0 -> 5049 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/select_branch.pngbin0 -> 12213 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/select_project.pngbin0 -> 16832 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/settings.pngbin0 -> 4321 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/shh_keys.pngbin0 -> 4981 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/submit_new_issue.pngbin0 -> 9083 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/title_description_mr.pngbin0 -> 12749 bytes
-rw-r--r--doc/gitlab-basics/basicsimages/white_space.pngbin0 -> 3707 bytes
-rw-r--r--doc/gitlab-basics/command-line-commands.md74
-rw-r--r--doc/gitlab-basics/create-branch.md38
-rw-r--r--doc/gitlab-basics/create-group.md43
-rw-r--r--doc/gitlab-basics/create-project.md21
-rw-r--r--doc/gitlab-basics/create-your-ssh-keys.md37
-rw-r--r--doc/gitlab-basics/start-using-git.md61
-rw-r--r--doc/install/installation.md3
-rw-r--r--doc/install/requirements.md27
-rw-r--r--doc/integration/README.md1
-rw-r--r--doc/integration/saml.md5
-rw-r--r--doc/markdown/markdown.md30
-rw-r--r--doc/permissions/permissions.md4
-rw-r--r--doc/profile/preferences.md6
-rw-r--r--doc/project_services/irker.md49
-rw-r--r--doc/raketasks/backup_restore.md27
-rw-r--r--doc/raketasks/maintenance.md13
-rw-r--r--doc/release/monthly.md18
-rw-r--r--doc/ssh/README.md30
-rw-r--r--doc/update/6.x-or-7.x-to-7.13.md (renamed from doc/update/6.x-or-7.x-to-7.12.md)22
-rw-r--r--doc/update/7.12-to-7.13.md129
-rw-r--r--doc/update/mysql_to_postgresql.md7
-rw-r--r--doc/workflow/README.md2
-rw-r--r--doc/workflow/importing/import_projects_from_github.md4
-rw-r--r--doc/workflow/notifications.md6
-rw-r--r--doc_styleguide.md35
-rw-r--r--docker/Dockerfile (renamed from docker/single/Dockerfile)19
-rw-r--r--docker/README.md181
-rw-r--r--docker/app/Dockerfile32
-rwxr-xr-xdocker/app/assets/wrapper17
-rwxr-xr-xdocker/assets/wrapper (renamed from docker/single/assets/wrapper)5
-rw-r--r--docker/data/Dockerfile8
-rw-r--r--docker/data/assets/gitlab.rb37
-rw-r--r--docker/fig.yml2
-rw-r--r--docker/marathon.json31
-rw-r--r--docker/single/assets/gitlab.rb37
-rw-r--r--docker/single/marathon.json14
-rw-r--r--docker/troubleshooting.md43
-rw-r--r--features/admin/users.feature20
-rw-r--r--features/groups.feature4
-rw-r--r--features/profile/active_tab.feature6
-rw-r--r--features/profile/profile.feature2
-rw-r--r--features/project/commits/comments.feature1
-rw-r--r--features/project/forked_merge_requests.feature1
-rw-r--r--features/project/issues/issues.feature13
-rw-r--r--features/project/issues/milestones.feature4
-rw-r--r--features/project/merge_requests.feature30
-rw-r--r--features/project/project.feature27
-rw-r--r--features/project/shortcuts.feature11
-rw-r--r--features/project/source/browse_files.feature2
-rw-r--r--features/project/source/multiselect_blob.feature85
-rw-r--r--features/project/wiki.feature2
-rw-r--r--features/snippets/snippets.feature13
-rw-r--r--features/steps/admin/broadcast_messages.rb2
-rw-r--r--features/steps/admin/settings.rb6
-rw-r--r--features/steps/admin/users.rb41
-rw-r--r--features/steps/dashboard/dashboard.rb2
-rw-r--r--features/steps/dashboard/event_filters.rb2
-rw-r--r--features/steps/dashboard/new_project.rb2
-rw-r--r--features/steps/explore/projects.rb1
-rw-r--r--features/steps/groups.rb12
-rw-r--r--features/steps/profile/active_tab.rb4
-rw-r--r--features/steps/profile/profile.rb20
-rw-r--r--features/steps/project/active_tab.rb4
-rw-r--r--features/steps/project/forked_merge_requests.rb1
-rw-r--r--features/steps/project/issues/issues.rb28
-rw-r--r--features/steps/project/issues/milestones.rb8
-rw-r--r--features/steps/project/merge_requests.rb79
-rw-r--r--features/steps/project/project.rb32
-rw-r--r--features/steps/project/project_shortcuts.rb5
-rw-r--r--features/steps/project/snippets.rb6
-rw-r--r--features/steps/project/source/browse_files.rb2
-rw-r--r--features/steps/project/source/multiselect_blob.rb58
-rw-r--r--features/steps/project/star.rb2
-rw-r--r--features/steps/search.rb12
-rw-r--r--features/steps/shared/admin.rb1
-rw-r--r--features/steps/shared/authentication.rb4
-rw-r--r--features/steps/shared/diff_note.rb44
-rw-r--r--features/steps/shared/issuable.rb2
-rw-r--r--features/steps/shared/note.rb14
-rw-r--r--features/steps/shared/paths.rb53
-rw-r--r--features/steps/shared/project_tab.rb4
-rw-r--r--features/steps/shared/user.rb6
-rw-r--r--features/steps/snippets/snippets.rb26
-rw-r--r--features/support/env.rb1
-rw-r--r--lib/api/api.rb1
-rw-r--r--lib/api/entities.rb24
-rw-r--r--lib/api/groups.rb4
-rw-r--r--lib/api/issues.rb4
-rw-r--r--lib/api/merge_requests.rb4
-rw-r--r--lib/api/project_snippets.rb6
-rw-r--r--lib/api/settings.rb35
-rw-r--r--lib/api/users.rb32
-rw-r--r--lib/backup/database.rb21
-rw-r--r--lib/backup/uploads.rb2
-rw-r--r--lib/extracts_path.rb8
-rw-r--r--lib/gitlab/backend/shell_env.rb4
-rw-r--r--lib/gitlab/git_access_wiki.rb2
-rw-r--r--lib/gitlab/github_import/importer.rb4
-rw-r--r--lib/gitlab/markdown.rb4
-rw-r--r--lib/gitlab/markup_helper.rb10
-rw-r--r--lib/gitlab/o_auth/provider.rb19
-rw-r--r--lib/gitlab/satellite/action.rb6
-rw-r--r--lib/gitlab/visibility_level.rb4
-rw-r--r--lib/repository_cache.rb8
-rw-r--r--lib/support/nginx/gitlab4
-rw-r--r--lib/support/nginx/gitlab-ssl4
-rw-r--r--lib/tasks/cache.rake2
-rw-r--r--lib/tasks/dev.rake4
-rw-r--r--lib/tasks/gitlab/backup.rake4
-rw-r--r--lib/tasks/gitlab/bulk_add_permission.rake8
-rw-r--r--lib/tasks/gitlab/check.rake10
-rw-r--r--lib/tasks/gitlab/cleanup.rake6
-rw-r--r--lib/tasks/gitlab/db/drop_all_postgres_sequences.rake10
-rw-r--r--lib/tasks/gitlab/db/drop_all_tables.rake10
-rw-r--r--lib/tasks/gitlab/enable_automerge.rake2
-rw-r--r--lib/tasks/gitlab/generate_docs.rake2
-rw-r--r--lib/tasks/gitlab/import.rake2
-rw-r--r--lib/tasks/gitlab/info.rake2
-rw-r--r--lib/tasks/gitlab/setup.rake2
-rw-r--r--lib/tasks/gitlab/shell.rake6
-rw-r--r--lib/tasks/gitlab/test.rake4
-rw-r--r--lib/tasks/gitlab/web_hook.rake6
-rw-r--r--lib/tasks/jasmine.rake12
-rw-r--r--lib/tasks/migrate/add_limits_mysql.rake2
-rw-r--r--lib/tasks/migrate/migrate_iids.rake2
-rw-r--r--lib/tasks/setup.rake2
-rw-r--r--lib/tasks/sidekiq.rake6
-rw-r--r--lib/tasks/spec.rake8
-rw-r--r--lib/tasks/spinach.rake44
-rw-r--r--lib/tasks/test.rake6
-rw-r--r--public/apple-touch-icon-precomposed.pngbin11979 -> 11097 bytes
-rw-r--r--public/apple-touch-icon.pngbin11979 -> 11097 bytes
-rw-r--r--public/deploy.html2
-rw-r--r--public/favicon.icobin32988 -> 5430 bytes
-rw-r--r--public/gitlab_logo.pngbin13819 -> 0 bytes
-rw-r--r--public/logo.svg26
-rwxr-xr-x[-rw-r--r--]scripts/prepare_build.sh2
-rw-r--r--spec/controllers/admin/users_controller_spec.rb67
-rw-r--r--spec/controllers/autocomplete_controller_spec.rb94
-rw-r--r--spec/controllers/blob_controller_spec.rb12
-rw-r--r--spec/controllers/branches_controller_spec.rb30
-rw-r--r--spec/controllers/commit_controller_spec.rb60
-rw-r--r--spec/controllers/commits_controller_spec.rb7
-rw-r--r--spec/controllers/help_controller_spec.rb16
-rw-r--r--spec/controllers/import/bitbucket_controller_spec.rb40
-rw-r--r--spec/controllers/import/github_controller_spec.rb35
-rw-r--r--spec/controllers/import/gitlab_controller_spec.rb31
-rw-r--r--spec/controllers/import/gitorious_controller_spec.rb9
-rw-r--r--spec/controllers/import/google_code_controller_spec.rb14
-rw-r--r--spec/controllers/import/import_spec_helper.rb33
-rw-r--r--spec/controllers/merge_requests_controller_spec.rb101
-rw-r--r--spec/controllers/profiles/preferences_controller_spec.rb2
-rw-r--r--spec/controllers/profiles/two_factor_auths_controller_spec.rb17
-rw-r--r--spec/controllers/projects/compare_controller_spec.rb7
-rw-r--r--spec/controllers/projects/merge_requests_controller_spec.rb176
-rw-r--r--spec/controllers/projects/milestones_controller_spec.rb28
-rw-r--r--spec/controllers/projects/refs_controller_spec.rb17
-rw-r--r--spec/controllers/projects_controller_spec.rb16
-rw-r--r--spec/controllers/tree_controller_spec.rb12
-rw-r--r--spec/factories.rb3
-rw-r--r--spec/factories/merge_requests.rb2
-rw-r--r--spec/features/admin/admin_disables_two_factor_spec.rb33
-rw-r--r--spec/features/admin/admin_users_spec.rb68
-rw-r--r--spec/features/atom/users_spec.rb19
-rw-r--r--spec/features/gitlab_flavored_markdown_spec.rb3
-rw-r--r--spec/features/groups_spec.rb2
-rw-r--r--spec/features/issues/filter_by_milestone_spec.rb36
-rw-r--r--spec/features/issues_spec.rb26
-rw-r--r--spec/features/login_spec.rb2
-rw-r--r--spec/features/markdown_spec.rb2
-rw-r--r--spec/features/merge_requests/filter_by_milestone_spec.rb36
-rw-r--r--spec/features/notes_on_merge_requests_spec.rb21
-rw-r--r--spec/features/password_reset_spec.rb2
-rw-r--r--spec/features/profile_spec.rb4
-rw-r--r--spec/features/profiles/preferences_spec.rb4
-rw-r--r--spec/features/projects_spec.rb2
-rw-r--r--spec/features/search_spec.rb1
-rw-r--r--spec/features/security/profile_access_spec.rb6
-rw-r--r--spec/features/security/project/internal_access_spec.rb12
-rw-r--r--spec/features/security/project/private_access_spec.rb12
-rw-r--r--spec/features/security/project/public_access_spec.rb12
-rw-r--r--spec/features/task_lists_spec.rb6
-rw-r--r--spec/features/users_spec.rb2
-rw-r--r--spec/helpers/application_helper_spec.rb279
-rw-r--r--spec/helpers/broadcast_messages_helper_spec.rb10
-rw-r--r--spec/helpers/diff_helper_spec.rb6
-rw-r--r--spec/helpers/gitlab_markdown_helper_spec.rb7
-rw-r--r--spec/helpers/groups_helper.rb4
-rw-r--r--spec/helpers/notifications_helper_spec.rb9
-rw-r--r--spec/helpers/oauth_helper_spec.rb2
-rw-r--r--spec/helpers/preferences_helper_spec.rb2
-rw-r--r--spec/helpers/projects_helper_spec.rb44
-rw-r--r--spec/helpers/submodule_helper_spec.rb28
-rw-r--r--spec/helpers/tab_helper_spec.rb4
-rw-r--r--spec/helpers/tree_helper_spec.rb4
-rw-r--r--spec/helpers/visibility_level_helper_spec.rb39
-rw-r--r--spec/javascripts/behaviors/requires_input_spec.js.coffee49
-rw-r--r--spec/javascripts/fixtures/behaviors/requires_input.html.haml18
-rw-r--r--spec/javascripts/fixtures/issues_show.html.haml2
-rw-r--r--spec/javascripts/fixtures/line_highlighter.html.haml11
-rw-r--r--spec/javascripts/fixtures/merge_request_tabs.html.haml22
-rw-r--r--spec/javascripts/fixtures/merge_requests_show.html.haml2
-rw-r--r--spec/javascripts/line_highlighter_spec.js.coffee158
-rw-r--r--spec/javascripts/merge_request_spec.js.coffee2
-rw-r--r--spec/javascripts/merge_request_tabs_spec.js.coffee82
-rw-r--r--spec/lib/disable_email_interceptor_spec.rb4
-rw-r--r--spec/lib/extracts_path_spec.rb18
-rw-r--r--spec/lib/file_size_validator_spec.rb15
-rw-r--r--spec/lib/gitlab/asciidoc_spec.rb18
-rw-r--r--spec/lib/gitlab/auth_spec.rb8
-rw-r--r--spec/lib/gitlab/backend/grack_auth_spec.rb12
-rw-r--r--spec/lib/gitlab/backend/shell_spec.rb2
-rw-r--r--spec/lib/gitlab/bitbucket_import/project_creator_spec.rb20
-rw-r--r--spec/lib/gitlab/github_import/project_creator_spec.rb22
-rw-r--r--spec/lib/gitlab/gitlab_import/project_creator_spec.rb22
-rw-r--r--spec/lib/gitlab/google_code_import/client_spec.rb2
-rw-r--r--spec/lib/gitlab/google_code_import/importer_spec.rb23
-rw-r--r--spec/lib/gitlab/google_code_import/project_creator_spec.rb12
-rw-r--r--spec/lib/gitlab/ldap/access_spec.rb30
-rw-r--r--spec/lib/gitlab/ldap/adapter_spec.rb15
-rw-r--r--spec/lib/gitlab/ldap/authentication_spec.rb53
-rw-r--r--spec/lib/gitlab/ldap/config_spec.rb2
-rw-r--r--spec/lib/gitlab/ldap/user_spec.rb26
-rw-r--r--spec/lib/gitlab/markdown/autolink_filter_spec.rb4
-rw-r--r--spec/lib/gitlab/markdown/label_reference_filter_spec.rb2
-rw-r--r--spec/lib/gitlab/o_auth/auth_hash_spec.rb20
-rw-r--r--spec/lib/gitlab/o_auth/user_spec.rb85
-rw-r--r--spec/lib/gitlab/popen_spec.rb6
-rw-r--r--spec/lib/gitlab/project_search_results_spec.rb23
-rw-r--r--spec/lib/gitlab/satellite/action_spec.rb34
-rw-r--r--spec/lib/gitlab/satellite/merge_action_spec.rb28
-rw-r--r--spec/lib/gitlab/upgrader_spec.rb4
-rw-r--r--spec/lib/gitlab/version_info_spec.rb1
-rw-r--r--spec/lib/votes_spec.rb7
-rw-r--r--spec/mailers/notify_spec.rb5
-rw-r--r--spec/models/commit_range_spec.rb6
-rw-r--r--spec/models/commit_spec.rb8
-rw-r--r--spec/models/concerns/issuable_spec.rb5
-rw-r--r--spec/models/concerns/mentionable_spec.rb51
-rw-r--r--spec/models/deploy_keys_project_spec.rb4
-rw-r--r--spec/models/forked_project_link_spec.rb12
-rw-r--r--spec/models/hooks/service_hook_spec.rb10
-rw-r--r--spec/models/hooks/system_hook_spec.rb20
-rw-r--r--spec/models/hooks/web_hook_spec.rb10
-rw-r--r--spec/models/issue_spec.rb2
-rw-r--r--spec/models/key_spec.rb8
-rw-r--r--spec/models/members/group_member_spec.rb8
-rw-r--r--spec/models/members/project_member_spec.rb2
-rw-r--r--spec/models/merge_request_spec.rb16
-rw-r--r--spec/models/milestone_spec.rb14
-rw-r--r--spec/models/namespace_spec.rb6
-rw-r--r--spec/models/note_spec.rb6
-rw-r--r--spec/models/project_security_spec.rb14
-rw-r--r--spec/models/project_services/asana_service_spec.rb2
-rw-r--r--spec/models/project_services/assembla_service_spec.rb2
-rw-r--r--spec/models/project_services/buildkite_service_spec.rb6
-rw-r--r--spec/models/project_services/flowdock_service_spec.rb2
-rw-r--r--spec/models/project_services/gemnasium_service_spec.rb2
-rw-r--r--spec/models/project_services/gitlab_ci_service_spec.rb42
-rw-r--r--spec/models/project_services/hipchat_service_spec.rb16
-rw-r--r--spec/models/project_services/irker_service_spec.rb46
-rw-r--r--spec/models/project_services/pushover_service_spec.rb2
-rw-r--r--spec/models/project_services/slack_service/issue_message_spec.rb4
-rw-r--r--spec/models/project_services/slack_service/merge_message_spec.rb4
-rw-r--r--spec/models/project_services/slack_service/push_message_spec.rb20
-rw-r--r--spec/models/project_services/slack_service_spec.rb16
-rw-r--r--spec/models/project_spec.rb2
-rw-r--r--spec/models/project_team_spec.rb1
-rw-r--r--spec/models/project_wiki_spec.rb2
-rw-r--r--spec/models/repository_spec.rb46
-rw-r--r--spec/models/service_spec.rb25
-rw-r--r--spec/models/snippet_spec.rb1
-rw-r--r--spec/models/user_spec.rb99
-rw-r--r--spec/models/wiki_page_spec.rb8
-rw-r--r--spec/rails_helper.rb1
-rw-r--r--spec/requests/api/api_helpers_spec.rb18
-rw-r--r--spec/requests/api/branches_spec.rb4
-rw-r--r--spec/requests/api/doorkeeper_access_spec.rb10
-rw-r--r--spec/requests/api/files_spec.rb21
-rw-r--r--spec/requests/api/fork_spec.rb17
-rw-r--r--spec/requests/api/group_members_spec.rb18
-rw-r--r--spec/requests/api/groups_spec.rb9
-rw-r--r--spec/requests/api/merge_requests_spec.rb20
-rw-r--r--spec/requests/api/project_hooks_spec.rb11
-rw-r--r--spec/requests/api/project_members_spec.rb31
-rw-r--r--spec/requests/api/projects_spec.rb45
-rw-r--r--spec/requests/api/services_spec.rb14
-rw-r--r--spec/requests/api/settings_spec.rb29
-rw-r--r--spec/requests/api/system_hooks_spec.rb12
-rw-r--r--spec/requests/api/users_spec.rb166
-rw-r--r--spec/routing/admin_routing_spec.rb1
-rw-r--r--spec/routing/routing_spec.rb5
-rw-r--r--spec/services/archive_repository_service_spec.rb5
-rw-r--r--spec/services/create_snippet_service_spec.rb6
-rw-r--r--spec/services/git_push_service_spec.rb52
-rw-r--r--spec/services/git_tag_push_service_spec.rb6
-rw-r--r--spec/services/issues/bulk_update_service_spec.rb20
-rw-r--r--spec/services/merge_requests/refresh_service_spec.rb6
-rw-r--r--spec/services/notes/create_service_spec.rb1
-rw-r--r--spec/services/notification_service_spec.rb4
-rw-r--r--spec/services/projects/create_service_spec.rb4
-rw-r--r--spec/services/projects/fork_service_spec.rb6
-rw-r--r--spec/services/projects/transfer_service_spec.rb10
-rw-r--r--spec/services/projects/update_service_spec.rb6
-rw-r--r--spec/services/projects/upload_service_spec.rb4
-rw-r--r--spec/services/system_hooks_service_spec.rb12
-rw-r--r--spec/services/test_hook_service_spec.rb6
-rw-r--r--spec/services/update_snippet_service_spec.rb6
-rw-r--r--spec/spec_helper.rb20
-rw-r--r--spec/support/capybara.rb33
-rw-r--r--spec/support/capybara_helpers.rb34
-rw-r--r--spec/support/coverage.rb8
-rw-r--r--spec/support/db_cleaner.rb31
-rw-r--r--spec/support/factory_girl.rb3
-rw-r--r--spec/support/login_helpers.rb5
-rw-r--r--spec/support/matchers.rb69
-rw-r--r--spec/support/mentionable_shared_examples.rb10
-rw-r--r--spec/support/select2_helper.rb4
-rw-r--r--spec/support/stub_configuration.rb40
-rw-r--r--spec/support/test_env.rb6
-rw-r--r--spec/tasks/gitlab/backup_rake_spec.rb40
-rw-r--r--spec/tasks/gitlab/mail_google_schema_whitelisting.rb2
-rw-r--r--spec/workers/post_receive_spec.rb2
-rw-r--r--spec/workers/repository_archive_worker_spec.rb1
-rw-r--r--vendor/assets/javascripts/jquery.nicescroll.min.js118
-rw-r--r--vendor/assets/javascripts/jquery.sticky-kit.min.js9
645 files changed, 7275 insertions, 3954 deletions
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 4a9574be053..ddf4e31204a 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -9,35 +9,57 @@ before_script:
- touch log/test.log
- bundle install --without postgres production --jobs $(nproc) "${FLAGS[@]}"
- bundle exec rake db:create RAILS_ENV=test
-Rspec:
+
+spec:feature:
+ script:
+ - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:feature
+ tags:
+ - ruby
+ - mysql
+
+spec:api:
+ script:
+ - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:api
+ tags:
+ - ruby
+ - mysql
+
+spec:other:
+ script:
+ - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec:other
+ tags:
+ - ruby
+ - mysql
+
+spinach:project:
script:
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake spec
+ - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:project
tags:
- ruby
- mysql
-Spinach:
+spinach:other:
script:
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach
+ - RAILS_ENV=test SIMPLECOV=true bundle exec rake spinach:other
tags:
- ruby
- mysql
-Jasmine:
+teaspoon:
script:
- - RAILS_ENV=test SIMPLECOV=true bundle exec rake jasmine:ci
+ - RAILS_ENV=test bundle exec teaspoon
tags:
- ruby
- mysql
-Rubocop:
+rubocop:
script:
- bundle exec rubocop
tags:
- ruby
- mysql
-Brakeman:
+brakeman:
script:
- bundle exec rake brakeman
tags:
diff --git a/.rspec b/.rspec
index 4e1e0d2f722..35f4d7441e0 100644
--- a/.rspec
+++ b/.rspec
@@ -1 +1,2 @@
--color
+--format Fuubar
diff --git a/.rubocop.yml b/.rubocop.yml
index 0cc729d3b08..ea4d365761e 100644
--- a/.rubocop.yml
+++ b/.rubocop.yml
@@ -993,8 +993,6 @@ Rails/Validation:
AllCops:
RunRailsCops: true
Exclude:
- - 'spec/**/*'
- - 'features/**/*'
- 'vendor/**/*'
- 'db/**/*'
- 'tmp/**/*'
diff --git a/CHANGELOG b/CHANGELOG
index 86de9314d80..b1d079ef207 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,22 +1,87 @@
Please view this file on the master branch, on stable branches it's out of date.
+v 7.14.0 (unreleased)
+ - Fix label read access for unauthenticated users (Daniel Gerhardt)
+ - Fix OAuth provider bug where GitLab would not go return to the redirect_uri after sign-in (Stan Hu)
+ - Fix file upload dialog for comment editing (Daniel Gerhardt)
+ - Expire Rails cache entries after two weeks to prevent endless Redis growth
+ - Add support for destroying project milestones (Stan Hu)
+
v 7.13.0 (unreleased)
+ - Only enable HSTS header for HTTPS and port 443 (Stan Hu)
+ - Fix user autocomplete for unauthenticated users accessing public projects (Stan Hu)
+ - Fix redirection to home page URL for unauthorized users (Daniel Gerhardt)
+ - Add branch switching support for graphs (Daniel Gerhardt)
+ - Fix external issue tracker hook/test for HTTPS URLs (Daniel Gerhardt)
+ - Remove link leading to a 404 error in Deploy Keys page (Stan Hu)
+ - Add support for unlocking users in admin settings (Stan Hu)
+ - Add Irker service configuration options (Stan Hu)
+ - Fix order of issues imported from GitHub (Hiroyuki Sato)
+ - Bump rugments to 1.0.0beta8 to fix C prototype function highlighting (Jonathon Reinhart)
+ - Fix Merge Request webhook to properly fire "merge" action when accepted from the web UI
+ - Add `two_factor_enabled` field to admin user API (Stan Hu)
+ - Fix invalid timestamps in RSS feeds (Rowan Wookey)
+ - Fix downloading of patches on public merge requests when user logged out (Stan Hu)
+ - Fix Error 500 when relative submodule resolves to a namespace that has a different name from its path (Stan Hu)
+ - Extract the longest-matching ref from a commit path when multiple matches occur (Stan Hu)
+ - Update maintenance documentation to explain no need to recompile asssets for omnibus installations (Stan Hu)
- Support commenting on diffs in side-by-side mode (Stan Hu)
- Fix JavaScript error when clicking on the comment button on a diff line that has a comment already (Stan Hu)
+ - Return 40x error codes if branch could not be deleted in UI (Stan Hu)
- Remove project visibility icons from dashboard projects list
- Rename "Design" profile settings page to "Preferences".
- Allow users to customize their default Dashboard page.
- Update ssl_ciphers in Nginx example to remove DHE settings. This will deny forward secrecy for Android 2.3.7, Java 6 and OpenSSL 0.9.8
+ - Admin can edit and remove user identities
+ - Convert CRLF newlines to LF when committing using the web editor.
+ - API request /projects/:project_id/merge_requests?state=closed will return only closed merge requests without merged one. If you need ones that were merged - use state=merged.
+ - Allow Administrators to filter the user list by those with or without Two-factor Authentication enabled.
+ - Show a user's Two-factor Authentication status in the administration area.
+ - Explicit error when commit not found in the CI
+ - Improve performance for issue and merge request pages
+ - Users with guest access level can not set assignee, labels or milestones for issue and merge request
+ - Reporter role can manage issue tracker now: edit any issue, set assignee or milestone and manage labels
+ - Better performance for pages with events list, issues list and commits list
+ - Faster automerge check and merge itself when source and target branches are in same repository
+ - Correctly show anonymous authorized applications under Profile > Applications.
+ - Query Optimization in MySQL.
+ - Allow users to be blocked and unblocked via the API
+ - Use native Postgres database cleaning during backup restore
+ - Redesign project page. Show README as default instead of activity. Move project activity to separate page
+ - Make left menu more hierarchical and less contextual by adding back item at top
+ - A fork can’t have a visibility level that is greater than the original project.
+ - Faster code search in repository and wiki. Fixes search page timeout for big repositories
+ - Allow administrators to disable 2FA for a specific user
+ - Add error message for SSH key linebreaks
+ - Store commits count in database (will populate with valid values only after first push)
+ - Rebuild cache after push to repository in background job
+
+v 7.12.2
+ - Correctly show anonymous authorized applications under Profile > Applications.
+ - Faster automerge check and merge itself when source and target branches are in same repository
+ - Audit log for user authentication
+ - Fix transferring of project to another group using the API.
+
+v 7.12.1
+ - Fix error when deleting a user who has projects (Stan Hu)
+ - Fix post-receive errors on a push when an external issue tracker is configured (Stan Hu)
+ - Add SAML to list of social_provider (Matt Firtion)
+ - Fix merge requests API scope to keep compatibility in 7.12.x patch release (Dmitriy Zaporozhets)
+ - Fix closed merge request scope at milestone page (Dmitriy Zaporozhets)
+ - Revert merge request states renaming
+ - Fix hooks for web based events with external issue references (Daniel Gerhardt)
+ - Improve performance for issue and merge request pages
+ - Compress database dumps to reduce backup size
-v 7.12.0 (unreleased)
+v 7.12.0
+ - Fix Error 500 when one user attempts to access a personal, internal snippet (Stan Hu)
+ - Disable changing of target branch in new merge request page when a branch has already been specified (Stan Hu)
- Fix post-receive errors on a push when an external issue tracker is configured (Stan Hu)
- Update oauth button logos for Twitter and Google to recommended assets
- - Fix hooks for web based events with external issue references (Daniel Gerhardt)
- Update browser gem to version 0.8.0 for IE11 support (Stan Hu)
- Fix timeout when rendering file with thousands of lines.
- Add "Remember me" checkbox to LDAP signin form.
- Add session expiration delay configuration through UI application settings
- - Fix external issue tracker hook/test for HTTPS URLs (Daniel Gerhardt)
- Don't notify users mentioned in code blocks or blockquotes.
- Omit link to generate labels if user does not have access to create them (Stan Hu)
- Show warning when a comment will add 10 or more people to the discussion.
@@ -47,8 +112,8 @@ v 7.12.0 (unreleased)
- Add validation to wiki page creation (only [a-zA-Z0-9/_-] are allowed) (Jeroen van Baarsen)
- Fix new/empty milestones showing 100% completion value (Jonah Bishop)
- Add a note when an Issue or Merge Request's title changes
- - Consistently refer to MRs as either Accepted or Rejected.
- - Add Accepted and Rejected tabs to MR lists.
+ - Consistently refer to MRs as either Merged or Closed.
+ - Add Merged tab to MR lists.
- Prefix EmailsOnPush email subject with `[Git]`.
- Group project contributions by both name and email.
- Clarify navigation labels for Project Settings and Group Settings.
@@ -60,7 +125,7 @@ v 7.12.0 (unreleased)
- Allow to configure a URL to show after sign out
- Add an option to automatically sign-in with an Omniauth provider
- Better performance for web editor (switched from satellites to rugged)
- - GitLab CI service sends .gitlab-ci.yaml in each push call
+ - GitLab CI service sends .gitlab-ci.yml in each push call
- When remove project - move repository and schedule it removal
- Improve group removing logic
- Trigger create-hooks on backup restore task
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 38fa66816a7..69abadb151a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -67,7 +67,7 @@ To start with GitLab download the [GitLab Development Kit](https://gitlab.com/gi
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. Fork the project into your personal space on GitLab.com
1. Create a feature branch
1. Write [tests](https://gitlab.com/gitlab-org/gitlab-development-kit#running-the-tests) and code
1. Add your changes to the [CHANGELOG](CHANGELOG)
@@ -167,15 +167,17 @@ If you add a dependency in GitLab (such as an operating system package) please c
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.
+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, ethnicity, 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 applies both within project spaces and in public spaces when an individual is representing the project or its community.
+
+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/) \ No newline at end of file
+This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.1.0, available at [http://contributor-covenant.org/version/1/1/0/](http://contributor-covenant.org/version/1/1/0/)
diff --git a/Gemfile b/Gemfile
index a4a4ba74e69..ba32feba3e7 100644
--- a/Gemfile
+++ b/Gemfile
@@ -2,6 +2,10 @@ source "https://rubygems.org"
gem 'rails', '4.1.11'
+# Specify a sprockets version due to security issue
+# See https://groups.google.com/forum/#!topic/rubyonrails-security/doAVp0YaTqY
+gem 'sprockets', '~> 2.12.3'
+
# Default values for AR models
gem "default_value_for", "~> 3.0.0"
@@ -9,7 +13,7 @@ gem "default_value_for", "~> 3.0.0"
gem "mysql2", group: :mysql
gem "pg", group: :postgres
-# Auth
+# Authentication libraries
gem "devise", '3.2.4'
gem "devise-async", '0.9.0'
gem 'omniauth', "~> 1.2.2"
@@ -34,7 +38,7 @@ gem "browser", '~> 0.8.0'
# Extracting information from a git repository
# Provide access to Gitlab::Git library
-gem "gitlab_git", '~> 7.2.3'
+gem "gitlab_git", '~> 7.2.5'
# Ruby/Rack Git Smart-HTTP Server Handler
# GitLab fork with a lot of changes (improved thread-safety, better memory usage etc)
@@ -42,7 +46,7 @@ gem "gitlab_git", '~> 7.2.3'
gem 'gitlab-grack', '~> 2.0.2', require: 'grack'
# LDAP Auth
-# GitLab fork with several improvements to original library. For full list of changes
+# GitLab fork with several improvements to original library. For full list of changes
# see https://github.com/intridea/omniauth-ldap/compare/master...gitlabhq:master
gem 'gitlab_omniauth-ldap', '1.2.1', require: "omniauth-ldap"
@@ -50,9 +54,9 @@ gem 'gitlab_omniauth-ldap', '1.2.1', require: "omniauth-ldap"
gem 'gollum-lib', '~> 4.0.2'
# Language detection
-# GitLab fork of linguist does not require pygments/python dependency.
-# New version of original gem also dropped pygments support but it has strict
-# dependency to unstable rugged version. We have internal issue for replacing
+# GitLab fork of linguist does not require pygments/python dependency.
+# New version of original gem also dropped pygments support but it has strict
+# dependency to unstable rugged version. We have internal issue for replacing
# fork with original gem when we meet on same rugged version - https://dev.gitlab.org/gitlab/gitlabhq/issues/2052.
gem "gitlab-linguist", "~> 3.0.1", require: "linguist"
@@ -94,7 +98,7 @@ gem "seed-fu"
gem 'html-pipeline', '~> 1.11.0'
gem 'task_list', '1.0.2', require: 'task_list/railtie'
gem 'github-markup'
-gem 'redcarpet', '~> 3.3.0'
+gem 'redcarpet', '~> 3.3.2'
gem 'RedCloth'
gem 'rdoc', '~>3.6'
gem 'org-ruby', '= 0.9.12'
@@ -181,7 +185,7 @@ gem 'mousetrap-rails'
# Detect and convert string character encoding
gem 'charlock_holmes'
-gem "sass-rails", '~> 4.0.2'
+gem "sass-rails", '~> 4.0.5'
gem "coffee-rails"
gem "uglifier"
gem 'turbolinks', '~> 2.5.0'
@@ -199,7 +203,7 @@ gem 'jquery-ui-rails'
gem 'nprogress-rails'
gem 'raphael-rails', '~> 2.1.2'
gem 'request_store'
-gem 'select2-rails'
+gem 'select2-rails', '~> 3.5.9'
gem 'virtus'
group :development do
@@ -222,16 +226,17 @@ group :development do
end
group :development, :test do
- gem 'coveralls', require: false
- gem 'rubocop', '0.28.0', require: false
- gem 'spinach-rails'
- gem "rspec-rails", '2.99'
- gem 'capybara', '~> 2.2.1'
- gem 'capybara-screenshot', '~> 1.0.0'
- gem "pry-rails"
- gem "awesome_print"
- gem "database_cleaner"
+ gem 'awesome_print'
+ gem 'byebug', platform: :mri
+ gem 'fuubar', '~> 2.0.0'
+ gem 'pry-rails'
+
+ gem 'coveralls', '~> 0.8.2', require: false
+ gem 'database_cleaner', '~> 1.4.0'
gem 'factory_girl_rails'
+ gem 'rspec-rails', '~> 3.3.0'
+ gem 'rubocop', '0.28.0', require: false
+ gem 'spinach-rails'
# Prevent occasions where minitest is not bundled in packaged versions of ruby (see #3826)
gem 'minitest', '~> 5.3.0'
@@ -239,8 +244,9 @@ group :development, :test do
# Generate Fake data
gem 'ffaker', '~> 2.0.0'
- # PhantomJS driver for Capybara
- gem 'poltergeist', '~> 1.5.1'
+ gem 'capybara', '~> 2.4.0'
+ gem 'capybara-screenshot', '~> 1.0.0'
+ gem 'poltergeist', '~> 1.6.0'
gem 'teaspoon', '~> 1.0.0'
gem 'teaspoon-jasmine'
@@ -249,14 +255,12 @@ group :development, :test do
gem 'spring-commands-rspec', '~> 1.0.0'
gem 'spring-commands-spinach', '~> 1.0.0'
gem 'spring-commands-teaspoon', '~> 0.0.2'
-
- gem "byebug"
end
group :test do
gem 'simplecov', require: false
gem 'shoulda-matchers', '~> 2.8.0', require: false
- gem 'email_spec'
+ gem 'email_spec', '~> 1.6.0'
gem 'webmock', '~> 1.21.0'
gem 'test_after_commit'
end
@@ -268,4 +272,4 @@ end
gem "newrelic_rpm"
gem 'octokit', '3.7.0'
-gem "rugments", "~> 1.0.0.beta7"
+gem "rugments", "~> 1.0.0.beta8"
diff --git a/Gemfile.lock b/Gemfile.lock
index 0640c14d343..6e571072a4c 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -82,7 +82,7 @@ GEM
columnize (~> 0.8)
debugger-linecache (~> 1.2)
cal-heatmap-rails (0.0.1)
- capybara (2.2.1)
+ capybara (2.4.4)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
@@ -113,19 +113,19 @@ GEM
colorize (0.5.8)
columnize (0.9.0)
connection_pool (2.1.0)
- coveralls (0.7.0)
- multi_json (~> 1.3)
- rest-client
- simplecov (>= 0.7)
- term-ansicolor
- thor
+ coveralls (0.8.2)
+ json (~> 1.8)
+ rest-client (>= 1.6.8, < 2)
+ simplecov (~> 0.10.0)
+ term-ansicolor (~> 1.3)
+ thor (~> 0.19.1)
crack (0.4.2)
safe_yaml (~> 1.0.0)
creole (0.3.8)
d3_rails (3.5.5)
railties (>= 3.1.0)
daemons (1.1.9)
- database_cleaner (1.3.0)
+ database_cleaner (1.4.1)
debug_inspector (0.0.2)
debugger-linecache (1.2.0)
default_value_for (3.0.0)
@@ -149,12 +149,14 @@ GEM
diff-lcs (1.2.5)
diffy (3.0.3)
docile (1.1.5)
+ domain_name (0.5.24)
+ unf (>= 0.0.5, < 1.0.0)
doorkeeper (2.1.3)
railties (>= 3.2)
dotenv (0.9.0)
dropzonejs-rails (0.4.14)
rails (> 3.1)
- email_spec (1.5.0)
+ email_spec (1.6.0)
launchy (~> 2.1)
mail (~> 2.2)
encryptor (1.3.0)
@@ -242,6 +244,9 @@ GEM
dotenv (>= 0.7)
thor (>= 0.13.6)
formatador (0.2.5)
+ fuubar (2.0.0)
+ rspec (~> 3.0)
+ ruby-progressbar (~> 1.4)
gemnasium-gitlab-service (0.2.6)
rugged (~> 0.21)
gemojione (2.0.0)
@@ -266,7 +271,7 @@ GEM
mime-types (~> 1.19)
gitlab_emoji (0.1.0)
gemojione (~> 2.0)
- gitlab_git (7.2.3)
+ gitlab_git (7.2.5)
activesupport (~> 4.0)
charlock_holmes (~> 0.6)
gitlab-linguist (~> 3.0)
@@ -319,6 +324,8 @@ GEM
html-pipeline (1.11.0)
activesupport (>= 2)
nokogiri (~> 1.4)
+ http-cookie (1.0.2)
+ domain_name (~> 0.5)
http_parser.rb (0.5.3)
httparty (0.13.3)
json (~> 1.8)
@@ -348,7 +355,7 @@ GEM
actionpack (>= 3.0.0)
activesupport (>= 3.0.0)
kgio (2.9.2)
- launchy (2.4.2)
+ launchy (2.4.3)
addressable (~> 2.3)
letter_opener (1.1.2)
launchy (~> 2.2)
@@ -374,6 +381,7 @@ GEM
net-scp (1.2.1)
net-ssh (>= 2.6.5)
net-ssh (2.9.2)
+ netrc (0.10.3)
newrelic_rpm (3.9.4.245)
nokogiri (1.6.6.2)
mini_portile (~> 0.6.0)
@@ -431,8 +439,8 @@ GEM
orm_adapter (0.5.0)
parser (2.2.0.2)
ast (>= 1.1, < 3.0)
- pg (0.15.1)
- poltergeist (1.5.1)
+ pg (0.18.2)
+ poltergeist (1.6.0)
capybara (~> 2.1)
cliver (~> 0.3.1)
multi_json (~> 1.0)
@@ -449,7 +457,7 @@ GEM
quiet_assets (1.0.2)
railties (>= 3.1, < 5.0)
racc (1.4.10)
- rack (1.5.4)
+ rack (1.5.5)
rack-accept (0.4.5)
rack (>= 0.4)
rack-attack (4.3.0)
@@ -499,7 +507,7 @@ GEM
trollop
rdoc (3.12.2)
json (~> 1.4)
- redcarpet (3.3.1)
+ redcarpet (3.3.2)
redis (3.1.0)
redis-actionpack (4.0.0)
actionpack (~> 4)
@@ -522,29 +530,37 @@ GEM
request_store (1.0.5)
rerun (0.10.0)
listen (~> 2.7, >= 2.7.3)
- rest-client (1.6.7)
- mime-types (>= 1.16)
+ rest-client (1.8.0)
+ http-cookie (>= 1.0.2, < 2.0)
+ mime-types (>= 1.16, < 3.0)
+ netrc (~> 0.7)
rinku (1.7.3)
rotp (1.6.1)
rouge (1.7.7)
rqrcode (0.4.2)
rqrcode-rails3 (0.1.7)
rqrcode (>= 0.4.2)
- 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.99.3)
- rspec-rails (2.99.0)
- actionpack (>= 3.0)
- activemodel (>= 3.0)
- activesupport (>= 3.0)
- railties (>= 3.0)
- rspec-collection_matchers
- rspec-core (~> 2.99.0)
- rspec-expectations (~> 2.99.0)
- rspec-mocks (~> 2.99.0)
+ rspec (3.3.0)
+ rspec-core (~> 3.3.0)
+ rspec-expectations (~> 3.3.0)
+ rspec-mocks (~> 3.3.0)
+ rspec-core (3.3.1)
+ rspec-support (~> 3.3.0)
+ rspec-expectations (3.3.0)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.3.0)
+ rspec-mocks (3.3.0)
+ diff-lcs (>= 1.2.0, < 2.0)
+ rspec-support (~> 3.3.0)
+ rspec-rails (3.3.2)
+ actionpack (>= 3.0, < 4.3)
+ activesupport (>= 3.0, < 4.3)
+ railties (>= 3.0, < 4.3)
+ rspec-core (~> 3.3.0)
+ rspec-expectations (~> 3.3.0)
+ rspec-mocks (~> 3.3.0)
+ rspec-support (~> 3.3.0)
+ rspec-support (3.3.0)
rubocop (0.28.0)
astrolabe (~> 1.3)
parser (>= 2.2.0.pre.7, < 3.0)
@@ -563,15 +579,15 @@ GEM
rubyntlm (0.5.0)
rubypants (0.2.0)
rugged (0.22.2)
- rugments (1.0.0.beta7)
+ rugments (1.0.0.beta8)
safe_yaml (1.0.4)
sanitize (2.1.0)
nokogiri (>= 1.4.4)
sass (3.2.19)
- sass-rails (4.0.3)
+ sass-rails (4.0.5)
railties (>= 4.0.0, < 5.0)
- sass (~> 3.2.0)
- sprockets (~> 2.8, <= 2.11.0)
+ sass (~> 3.2.2)
+ sprockets (~> 2.8, < 3.0)
sprockets-rails (~> 2.0)
sawyer (0.6.0)
addressable (~> 2.3.5)
@@ -582,7 +598,7 @@ GEM
seed-fu (2.3.5)
activerecord (>= 3.1, < 4.3)
activesupport (>= 3.1, < 4.3)
- select2-rails (3.5.2)
+ select2-rails (3.5.9.3)
thor (~> 0.14)
settingslogic (2.0.9)
sexp_processor (4.4.5)
@@ -599,11 +615,11 @@ GEM
ice_cube (= 0.11.1)
sidekiq (>= 3.0.0)
simple_oauth (0.1.9)
- simplecov (0.9.0)
+ simplecov (0.10.0)
docile (~> 1.1.0)
- multi_json
- simplecov-html (~> 0.8.0)
- simplecov-html (0.8.0)
+ json (~> 1.8)
+ simplecov-html (~> 0.10.0)
+ simplecov-html (0.10.0)
sinatra (1.4.4)
rack (~> 1.4)
rack-protection (~> 1.4)
@@ -628,12 +644,12 @@ GEM
spring (>= 0.9.1)
spring-commands-teaspoon (0.0.2)
spring (>= 0.9.1)
- sprockets (2.11.0)
+ sprockets (2.12.4)
hike (~> 1.2)
multi_json (~> 1.0)
rack (~> 1.0)
tilt (~> 1.1, != 1.3.0)
- sprockets-rails (2.3.1)
+ sprockets-rails (2.3.2)
actionpack (>= 3.0)
activesupport (>= 3.0)
sprockets (>= 2.8, < 4.0)
@@ -648,8 +664,8 @@ GEM
teaspoon-jasmine (2.2.0)
teaspoon (>= 1.0.0)
temple (0.6.7)
- term-ansicolor (1.2.2)
- tins (~> 0.8)
+ term-ansicolor (1.3.2)
+ tins (~> 1.0)
terminal-table (1.4.5)
test_after_commit (0.2.2)
thin (1.6.1)
@@ -671,7 +687,7 @@ GEM
mime-types (~> 1.19)
multi_json (~> 1.7)
twitter-stream (~> 0.1)
- tins (0.13.1)
+ tins (1.5.4)
trollop (2.1.2)
turbolinks (2.5.3)
coffee-rails
@@ -687,7 +703,7 @@ GEM
underscore-rails (1.4.4)
unf (0.1.4)
unf_ext
- unf_ext (0.0.6)
+ unf_ext (0.0.7.1)
unicorn (4.6.3)
kgio (~> 2.6)
rack
@@ -707,7 +723,9 @@ GEM
webmock (1.21.0)
addressable (>= 2.3.6)
crack (>= 0.3.2)
- websocket-driver (0.3.3)
+ websocket-driver (0.5.4)
+ websocket-extensions (>= 0.1.0)
+ websocket-extensions (0.1.2)
wikicloth (0.8.1)
builder
expression_parser
@@ -735,16 +753,16 @@ DEPENDENCIES
browser (~> 0.8.0)
byebug
cal-heatmap-rails (~> 0.0.1)
- capybara (~> 2.2.1)
+ capybara (~> 2.4.0)
capybara-screenshot (~> 1.0.0)
carrierwave
charlock_holmes
coffee-rails
colored
- coveralls
+ coveralls (~> 0.8.2)
creole (~> 0.3.6)
d3_rails (~> 3.5.5)
- database_cleaner
+ database_cleaner (~> 1.4.0)
default_value_for (~> 3.0.0)
devise (= 3.2.4)
devise-async (= 0.9.0)
@@ -752,20 +770,21 @@ DEPENDENCIES
diffy (~> 3.0.3)
doorkeeper (= 2.1.3)
dropzonejs-rails
- email_spec
+ email_spec (~> 1.6.0)
enumerize
factory_girl_rails
ffaker (~> 2.0.0)
fog (~> 1.25.0)
font-awesome-rails (~> 4.2)
foreman
+ fuubar (~> 2.0.0)
gemnasium-gitlab-service (~> 0.2)
github-markup
gitlab-flowdock-git-hook (~> 0.4.2)
gitlab-grack (~> 2.0.2)
gitlab-linguist (~> 3.0.1)
gitlab_emoji (~> 0.1)
- gitlab_git (~> 7.2.3)
+ gitlab_git (~> 7.2.5)
gitlab_meta (= 7.0)
gitlab_omniauth-ldap (= 1.2.1)
gollum-lib (~> 4.0.2)
@@ -800,7 +819,7 @@ DEPENDENCIES
omniauth-twitter
org-ruby (= 0.9.12)
pg
- poltergeist (~> 1.5.1)
+ poltergeist (~> 1.6.0)
pry-rails
quiet_assets (~> 1.0.1)
rack-attack (~> 4.3.0)
@@ -810,19 +829,19 @@ DEPENDENCIES
rails (= 4.1.11)
raphael-rails (~> 2.1.2)
rdoc (~> 3.6)
- redcarpet (~> 3.3.0)
+ redcarpet (~> 3.3.2)
redis-rails
request_store
rerun (~> 0.10.0)
rqrcode-rails3
- rspec-rails (= 2.99)
+ rspec-rails (~> 3.3.0)
rubocop (= 0.28.0)
- rugments (~> 1.0.0.beta7)
+ rugments (~> 1.0.0.beta8)
sanitize (~> 2.0)
- sass-rails (~> 4.0.2)
+ sass-rails (~> 4.0.5)
sdoc
seed-fu
- select2-rails
+ select2-rails (~> 3.5.9)
settingslogic
shoulda-matchers (~> 2.8.0)
sidekiq (~> 3.3)
@@ -837,6 +856,7 @@ DEPENDENCIES
spring-commands-rspec (~> 1.0.0)
spring-commands-spinach (~> 1.0.0)
spring-commands-teaspoon (~> 0.0.2)
+ sprockets (~> 2.12.3)
stamp
state_machine
task_list (= 1.0.2)
@@ -855,3 +875,6 @@ DEPENDENCIES
virtus
webmock (~> 1.21.0)
wikicloth (= 0.8.1)
+
+BUNDLED WITH
+ 1.10.5
diff --git a/README.md b/README.md
index 85ea5c876af..bd663b30f3e 100644
--- a/README.md
+++ b/README.md
@@ -1,9 +1,14 @@
+# GitLab
+
+[![build status](https://ci.gitlab.com/projects/1/status.png?ref=master)](https://ci.gitlab.com/projects/1?ref=master)
+[![Build Status](https://semaphoreapp.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/243338/badge.png)](https://semaphoreapp.com/gitlabhq/gitlabhq)
+[![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
+[![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master)
+
## 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.
-# ![logo](https://about.gitlab.com/images/gitlab_logo.png) GitLab
-
## Open source software to collaborate on code
To see how GitLab looks please see the [features page on our website](https://about.gitlab.com/features/).
@@ -17,21 +22,12 @@ To see how GitLab looks please see the [features page on our website](https://ab
## 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 use EE and get official support please [become a subscriber](https://about.gitlab.com/pricing/).
-
-## 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)
+There are two editions of GitLab:
-- [![Build Status](https://semaphoreapp.com/api/v1/projects/2f1a5809-418b-4cc2-a1f4-819607579fe7/243338/badge.png)](https://semaphoreapp.com/gitlabhq/gitlabhq)
+- GitLab Community Edition (CE) is available freely under the MIT Expat license.
+- GitLab Enterprise Edition (EE) includes [extra features](https://about.gitlab.com/features/#compare) that are more useful for organizations with more than 100 users. To use EE and get official support please [become a subscriber](https://about.gitlab.com/pricing/).
-- [![Code Climate](https://codeclimate.com/github/gitlabhq/gitlabhq.svg)](https://codeclimate.com/github/gitlabhq/gitlabhq)
-
-- [![Coverage Status](https://coveralls.io/repos/gitlabhq/gitlabhq/badge.png?branch=master)](https://coveralls.io/r/gitlabhq/gitlabhq?branch=master)
+Included with the GitLab Omnibus Packages is [GitLab CI](https://about.gitlab.com/gitlab-ci/) that can easily build, test and deploy code.
## Website
@@ -46,24 +42,40 @@ On [about.gitlab.com](https://about.gitlab.com/) you can find more information a
## Requirements
-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
-
Please see the [requirements documentation](doc/install/requirements.md) for system requirements and more information about the supported operating systems.
## 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.
+The recommended way to install GitLab is with the [Omnibus packages](https://about.gitlab.com/downloads/) on our package server.
+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.
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.
You can access a new installation with the login **`root`** and password **`5iveL!fe`**, after login you are required to set a unique password.
+## Install a development environment
+
+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
+
+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).
+
+## Software stack
+
+GitLab is a Ruby on Rails application that runs on the following software:
+
+- Ubuntu/Debian/CentOS/RHEL
+- Ruby (MRI) 2.0 or 2.1
+- Git 1.7.10+
+- Redis 2.0+
+- MySQL or PostgreSQL
+
+For more information please see the [architecture documentation](http://doc.gitlab.com/ce/development/architecture.html).
+
## Third-party applications
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.
@@ -74,17 +86,7 @@ Since 2011 a minor or major version of GitLab is released on the 22nd of every m
## Upgrading
-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
-
-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
-
-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).
+For upgrading information please see our [update page](https://about.gitlab.com/update/).
## Documentation
diff --git a/VERSION b/VERSION
index 5f0902c7c6a..5778e530e10 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-7.12.0.pre \ No newline at end of file
+7.13.0.pre
diff --git a/app/assets/images/favicon.ico b/app/assets/images/favicon.ico
index bfb74960c48..3479cbbb46f 100644
--- a/app/assets/images/favicon.ico
+++ b/app/assets/images/favicon.ico
Binary files differ
diff --git a/app/assets/images/logo-white.png b/app/assets/images/logo-white.png
deleted file mode 100644
index 917bcfcb7e7..00000000000
--- a/app/assets/images/logo-white.png
+++ /dev/null
Binary files differ
diff --git a/app/assets/images/logo.svg b/app/assets/images/logo.svg
new file mode 100644
index 00000000000..c09785cb96f
--- /dev/null
+++ b/app/assets/images/logo.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="210px" height="210px" viewBox="0 0 210 210" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
+ <!-- Generator: Sketch 3.3.2 (12043) - http://www.bohemiancoding.com/sketch -->
+ <title>Slice 1</title>
+ <desc>Created with Sketch.</desc>
+ <defs></defs>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
+ <g id="logo" sketch:type="MSLayerGroup" transform="translate(0.000000, 10.000000)">
+ <g id="Page-1" sketch:type="MSShapeGroup">
+ <g id="Fill-1-+-Group-24">
+ <g id="Group-24">
+ <g id="Group">
+ <path d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" id="Fill-4" fill="#E24329"></path>
+ <path d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-8" fill="#FC6D26"></path>
+ <path d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 L12.2685,74.7341 Z" id="Fill-12" fill="#FCA326"></path>
+ <path d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 L12.2685,74.7342 Z" id="Fill-16" fill="#E24329"></path>
+ <path d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-18" fill="#FC6D26"></path>
+ <path d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 L197.8544,74.7341 Z" id="Fill-20" fill="#FCA326"></path>
+ <path d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 L197.8544,74.7342 Z" id="Fill-22" fill="#E24329"></path>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/app/assets/images/logo_wordmark.svg b/app/assets/images/logo_wordmark.svg
new file mode 100644
index 00000000000..a37fe1235cb
--- /dev/null
+++ b/app/assets/images/logo_wordmark.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="546px" height="194px" viewBox="0 0 546 194" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
+ <!-- Generator: Sketch 3.3.2 (12043) - http://www.bohemiancoding.com/sketch -->
+ <title>Fill 1 + Group 24</title>
+ <desc>Created with Sketch.</desc>
+ <defs></defs>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
+ <g id="Fill-1-+-Group-24" sketch:type="MSLayerGroup">
+ <g id="Group-24" sketch:type="MSShapeGroup">
+ <path d="M316.7906,65.3001 C301.5016,65.3001 292.0046,77.4461 292.0046,97.0001 C292.0046,116.5541 301.5016,128.7001 316.7906,128.7001 C322.5346,128.7001 327.8716,127.0711 332.2226,123.9881 L332.4336,123.8391 L332.4336,101.8711 L310.4336,101.8711 L310.4336,94.0711 L341.4336,94.0711 L341.4336,126.8061 C334.8706,133.1501 326.3546,136.5001 316.7906,136.5001 C296.2666,136.5001 283.0046,120.9951 283.0046,97.0001 C283.0046,73.0051 296.2666,57.5001 316.7906,57.5001 C326.7826,57.5001 335.2176,61.1481 341.2206,68.0561 L335.2246,73.0381 C330.6986,67.9041 324.4986,65.3001 316.7906,65.3001 L316.7906,65.3001 Z M489.8836,135.2501 L482.9356,135.2501 L480.6016,128.8021 L480.0486,129.2991 C479.9716,129.3681 472.2196,136.2501 462.4606,136.2501 C452.6096,136.2501 445.4606,129.6961 445.4606,120.6671 C445.4606,107.5951 456.7446,104.8511 466.2096,104.8511 C473.5836,104.8511 480.1886,106.5111 480.2546,106.5281 L480.8776,106.6871 L480.8776,105.1011 C480.8776,97.9861 476.4356,94.3781 467.6726,94.3781 C462.3646,94.3781 456.7556,95.6891 451.4236,98.1701 L447.8206,91.9581 C452.5266,88.8961 459.6726,85.3781 467.6726,85.3781 C481.5806,85.3781 489.8836,92.9341 489.8836,105.5891 L489.8836,135.2501 Z M470.6886,111.7771 C460.0716,111.7771 454.4606,114.8511 454.4606,120.6671 C454.4606,124.7281 457.5256,127.2501 462.4606,127.2501 C470.5906,127.2501 477.7276,123.9181 480.6626,121.9481 L480.8836,121.8001 L480.8836,112.6201 L480.4676,112.5491 C480.4226,112.5411 475.8766,111.7771 470.6886,111.7771 L470.6886,111.7771 Z M440.4576,127.4501 L440.4576,135.2501 L410.4606,135.2501 L410.4606,61.2501 L419.4606,61.2501 L419.4606,127.4501 L440.4576,127.4501 Z M520.9416,136.5001 C515.0966,136.5001 508.6886,135.6961 501.8926,134.1091 L501.8926,61.2501 L510.8926,61.2501 L510.8926,89.3131 L511.6656,88.8111 C511.7146,88.7791 516.7346,85.5711 523.6536,85.5711 C525.0336,85.5711 526.4146,85.7001 527.7486,85.9521 C539.0936,88.2761 545.8666,97.4301 545.8666,110.4391 C545.8666,125.7831 535.6176,136.5001 520.9416,136.5001 L520.9416,136.5001 Z M521.9426,94.3781 C518.3636,94.3781 514.6196,95.6031 511.1166,97.9191 L510.8926,98.0681 L510.8926,127.9021 L511.3196,127.9651 C514.6986,128.4601 517.9356,128.7121 520.9416,128.7121 C530.3176,128.7121 536.8666,121.1971 536.8666,110.4391 C536.8666,100.2321 531.4266,94.3781 521.9426,94.3781 L521.9426,94.3781 Z M398.4516,86.2501 L398.4516,94.0501 L383.4516,94.0501 L383.4516,116.9501 C383.4516,119.7551 384.5436,122.3921 386.5276,124.3741 C388.5096,126.3581 391.1466,127.4501 393.9516,127.4501 L398.4516,127.4501 L398.4516,135.2501 L393.9516,135.2501 C383.1996,135.2501 374.4516,126.5021 374.4516,115.7501 L374.4516,61.2501 L383.4516,61.2501 L383.4516,86.2501 L398.4516,86.2501 Z M353.4426,66.2501 L362.4426,66.2501 L362.4426,75.2501 L353.4426,75.2501 L353.4426,66.2501 Z M353.4426,86.2501 L362.4426,86.2501 L362.4426,135.2501 L353.4426,135.2501 L353.4426,86.2501 Z" id="Fill-2" fill="#8C929D"></path>
+ <g id="Group">
+ <path d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" id="Fill-4" fill="#E24329"></path>
+ <path id="Fill-6" fill="#FC6D26"></path>
+ <path d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 Z" id="Fill-8" fill="#FC6D26"></path>
+ <path id="Fill-10" fill="#FC6D26"></path>
+ <path d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 Z" id="Fill-12" fill="#FCA326"></path>
+ <path id="Fill-14" fill="#FC6D26"></path>
+ <path d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 Z" id="Fill-16" fill="#E24329"></path>
+ <path d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 Z" id="Fill-18" fill="#FC6D26"></path>
+ <path d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 Z" id="Fill-20" fill="#FCA326"></path>
+ <path d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 Z" id="Fill-22" fill="#E24329"></path>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/app/assets/images/msapplication-tile.png b/app/assets/images/msapplication-tile.png
new file mode 100644
index 00000000000..f8c5c8b28b4
--- /dev/null
+++ b/app/assets/images/msapplication-tile.png
Binary files differ
diff --git a/app/assets/images/touch-icon-ipad-retina.png b/app/assets/images/touch-icon-ipad-retina.png
new file mode 100644
index 00000000000..feb32b48ec9
--- /dev/null
+++ b/app/assets/images/touch-icon-ipad-retina.png
Binary files differ
diff --git a/app/assets/images/touch-icon-ipad.png b/app/assets/images/touch-icon-ipad.png
new file mode 100644
index 00000000000..a6ddc543509
--- /dev/null
+++ b/app/assets/images/touch-icon-ipad.png
Binary files differ
diff --git a/app/assets/images/touch-icon-iphone-retina.png b/app/assets/images/touch-icon-iphone-retina.png
new file mode 100644
index 00000000000..8bf7ccb7534
--- /dev/null
+++ b/app/assets/images/touch-icon-iphone-retina.png
Binary files differ
diff --git a/app/assets/images/touch-icon-iphone.png b/app/assets/images/touch-icon-iphone.png
new file mode 100644
index 00000000000..87da550f8be
--- /dev/null
+++ b/app/assets/images/touch-icon-iphone.png
Binary files differ
diff --git a/app/assets/javascripts/application.js.coffee b/app/assets/javascripts/application.js.coffee
index 6a3f7386d5b..8b041c490d8 100644
--- a/app/assets/javascripts/application.js.coffee
+++ b/app/assets/javascripts/application.js.coffee
@@ -16,7 +16,6 @@
#= require jquery.scrollTo
#= require jquery.blockUI
#= require jquery.turbolinks
-#= require jquery.sticky-kit.min
#= require turbolinks
#= require autosave
#= require bootstrap
@@ -41,6 +40,7 @@
#= require shortcuts_issuable
#= require shortcuts_network
#= require cal-heatmap
+#= require jquery.nicescroll.min
#= require_tree .
window.slugify = (text) ->
@@ -105,6 +105,8 @@ if location.hash
window.addEventListener "hashchange", shiftWindow
$ ->
+ $(".nicescroll").niceScroll(cursoropacitymax: '0.4', cursorcolor: '#FFF', cursorborder: "1px solid #FFF")
+
# Click a .js-select-on-focus field, select the contents
$(".js-select-on-focus").on "focusin", ->
# Prevent a mouseup event from deselecting the input
@@ -141,8 +143,7 @@ $ ->
$('.trigger-submit').on 'change', ->
$(@).parents('form').submit()
- $("abbr.timeago").timeago()
- $('.js-timeago').timeago()
+ $('abbr.timeago, .js-timeago').timeago()
# Flash
if (flash = $(".flash-container")).length > 0
diff --git a/app/assets/javascripts/behaviors/requires_input.js.coffee b/app/assets/javascripts/behaviors/requires_input.js.coffee
new file mode 100644
index 00000000000..8318fe435b3
--- /dev/null
+++ b/app/assets/javascripts/behaviors/requires_input.js.coffee
@@ -0,0 +1,39 @@
+# Requires Input behavior
+#
+# When called on a form with input fields with the `required` attribute, the
+# form's submit button will be disabled until all required fields have values.
+#
+#= require extensions/jquery
+#
+# ### Example Markup
+#
+# <form class="js-requires-input">
+# <input type="text" required="required">
+# <input type="submit" value="Submit">
+# </form>
+#
+$.fn.requiresInput = ->
+ $form = $(this)
+ $button = $('button[type=submit], input[type=submit]', $form)
+
+ required = '[required=required]'
+ fieldSelector = "input#{required}, select#{required}, textarea#{required}"
+
+ requireInput = ->
+ # Collect the input values of *all* required fields
+ values = _.map $(fieldSelector, $form), (field) -> field.value
+
+ # Disable the button if any required fields are empty
+ if values.length && _.any(values, _.isEmpty)
+ $button.disable()
+ else
+ $button.enable()
+
+ # Set initial button state
+ requireInput()
+
+ $form.on 'change input', fieldSelector, requireInput
+
+# Triggered on standard document `ready` and on Turbolinks `page:load` events
+$(document).on 'ready page:load', ->
+ $('form.js-requires-input').requiresInput()
diff --git a/app/assets/javascripts/blob/blob.js.coffee b/app/assets/javascripts/blob/blob.js.coffee
deleted file mode 100644
index 37a175fdbc7..00000000000
--- a/app/assets/javascripts/blob/blob.js.coffee
+++ /dev/null
@@ -1,73 +0,0 @@
-class @BlobView
- constructor: ->
- # handle multi-line select
- handleMultiSelect = (e) ->
- [ first_line, last_line ] = parseSelectedLines()
- [ line_number ] = parseSelectedLines($(this).attr("id"))
- hash = "L#{line_number}"
-
- if e.shiftKey and not isNaN(first_line) and not isNaN(line_number)
- if line_number < first_line
- last_line = first_line
- first_line = line_number
- else
- last_line = line_number
-
- hash = if first_line == last_line then "L#{first_line}" else "L#{first_line}-#{last_line}"
-
- setHash(hash)
- e.preventDefault()
-
- # See if there are lines selected
- # "#L12" and "#L34-56" supported
- highlightBlobLines = (e) ->
- [ first_line, last_line ] = parseSelectedLines()
-
- 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}", offset: -50) unless e?
-
- # parse selected lines from hash
- # always return first and last line (initialized to NaN)
- parseSelectedLines = (str) ->
- first_line = NaN
- last_line = NaN
- hash = str || window.location.hash
-
- if hash isnt ""
- matches = hash.match(/\#?L(\d+)(\-(\d+))?/)
- first_line = parseInt(matches?[1])
- last_line = parseInt(matches?[3])
- last_line = first_line if isNaN(last_line)
-
- [ first_line, last_line ]
-
- setHash = (hash) ->
- hash = hash.replace(/^\#/, "")
- nodes = $("#" + hash)
- # if any nodes are using this id, they must be temporarily changed
- # also, add a temporary div at the top of the screen to prevent scrolling
- if nodes.length > 0
- scroll_top = $(document).scrollTop()
- nodes.attr("id", "")
- tmp = $("<div></div>")
- .css({ position: "absolute", visibility: "hidden", top: scroll_top + "px" })
- .attr("id", hash)
- .appendTo(document.body)
-
- window.location.hash = hash
-
- # restore the nodes
- if nodes.length > 0
- tmp.remove()
- nodes.attr("id", hash)
-
- # initialize multi-line select
- $("#tree-content-holder .line-numbers a[id^=L]").on("click", handleMultiSelect)
-
- # Highlight the correct lines on load
- highlightBlobLines()
-
- # Highlight the correct lines when the hash part of the URL changes
- $(window).on("hashchange", highlightBlobLines)
diff --git a/app/assets/javascripts/blob/edit_blob.js.coffee b/app/assets/javascripts/blob/edit_blob.js.coffee
index 2e91a06daa8..050888f9c15 100644
--- a/app/assets/javascripts/blob/edit_blob.js.coffee
+++ b/app/assets/javascripts/blob/edit_blob.js.coffee
@@ -11,7 +11,6 @@ class @EditBlob
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()
diff --git a/app/assets/javascripts/blob/new_blob.js.coffee b/app/assets/javascripts/blob/new_blob.js.coffee
index ab8f98715e8..1f36a53f191 100644
--- a/app/assets/javascripts/blob/new_blob.js.coffee
+++ b/app/assets/javascripts/blob/new_blob.js.coffee
@@ -11,7 +11,6 @@ class @NewBlob
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()
diff --git a/app/assets/javascripts/dispatcher.js.coffee b/app/assets/javascripts/dispatcher.js.coffee
index b7ebe6a5c89..2ab148bc296 100644
--- a/app/assets/javascripts/dispatcher.js.coffee
+++ b/app/assets/javascripts/dispatcher.js.coffee
@@ -31,20 +31,14 @@ class Dispatcher
when 'projects:compare:show'
new Diff()
when 'projects:issues:new','projects:issues:edit'
- GitLab.GfmAutoComplete.setup()
shortcut_handler = new ShortcutsNavigation()
- new ZenMode()
new DropzoneInput($('.issue-form'))
- if page == 'projects:issues:new'
- new IssuableForm($('.issue-form'))
+ 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'))
+ new IssuableForm($('.merge-request-form'))
when 'projects:merge_requests:show'
new Diff()
shortcut_handler = new ShortcutsIssuable()
@@ -68,8 +62,9 @@ class Dispatcher
shortcut_handler = new ShortcutsNavigation()
when 'projects:commits:show'
shortcut_handler = new ShortcutsNavigation()
+ when 'projects:activity'
+ shortcut_handler = new ShortcutsNavigation()
when 'projects:show'
- new Activities()
shortcut_handler = new ShortcutsNavigation()
when 'groups:show'
new Activities()
@@ -87,7 +82,7 @@ class Dispatcher
new TreeView()
shortcut_handler = new ShortcutsNavigation()
when 'projects:blob:show'
- new BlobView()
+ new LineHighlighter()
shortcut_handler = new ShortcutsNavigation()
when 'projects:labels:new', 'projects:labels:edit'
new Labels()
@@ -113,13 +108,6 @@ class Dispatcher
new NamespaceSelect()
when 'dashboard'
shortcut_handler = new ShortcutsDashboardNavigation()
- switch path[1]
- when 'issues', 'merge_requests'
- new UsersSelect()
- when 'groups'
- switch path[1]
- when 'issues', 'merge_requests'
- new UsersSelect()
when 'profiles'
new Profile()
when 'projects'
@@ -135,8 +123,6 @@ class Dispatcher
new ProjectNew()
when 'show'
new ProjectShow()
- when 'issues', 'merge_requests'
- new UsersSelect()
when 'wikis'
new Wikis()
shortcut_handler = new ShortcutsNavigation()
diff --git a/app/assets/javascripts/dropzone_input.js.coffee b/app/assets/javascripts/dropzone_input.js.coffee
index a7476146010..a4f511301c1 100644
--- a/app/assets/javascripts/dropzone_input.js.coffee
+++ b/app/assets/javascripts/dropzone_input.js.coffee
@@ -25,10 +25,10 @@ class @DropzoneInput
form_dropzone = $(form).find('.div-dropzone')
form_dropzone.parent().addClass "div-dropzone-wrapper"
form_dropzone.append divHover
- $(".div-dropzone-hover").append iconPaperclip
+ form_dropzone.find(".div-dropzone-hover").append iconPaperclip
form_dropzone.append divSpinner
- $(".div-dropzone-spinner").append iconSpinner
- $(".div-dropzone-spinner").css
+ form_dropzone.find(".div-dropzone-spinner").append iconSpinner
+ form_dropzone.find(".div-dropzone-spinner").css
"opacity": 0
"display": "none"
diff --git a/app/assets/javascripts/issuable_context.js.coffee b/app/assets/javascripts/issuable_context.js.coffee
new file mode 100644
index 00000000000..176d9cabefa
--- /dev/null
+++ b/app/assets/javascripts/issuable_context.js.coffee
@@ -0,0 +1,22 @@
+#= require jquery.waitforimages
+
+class @IssuableContext
+ constructor: ->
+ new UsersSelect()
+ $('select.select2').select2({width: 'resolve', dropdownAutoWidth: true})
+
+ $(".context .inline-update").on "change", "select", ->
+ $(this).submit()
+ $(".context .inline-update").on "change", ".js-assignee", ->
+ $(this).submit()
+
+ $('.issuable-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/issuable_form.js.coffee b/app/assets/javascripts/issuable_form.js.coffee
index abd58bcf978..48c249943f2 100644
--- a/app/assets/javascripts/issuable_form.js.coffee
+++ b/app/assets/javascripts/issuable_form.js.coffee
@@ -1,5 +1,9 @@
class @IssuableForm
constructor: (@form) ->
+ GitLab.GfmAutoComplete.setup()
+ new UsersSelect()
+ new ZenMode()
+
@titleField = @form.find("input[name*='[title]']")
@descriptionField = @form.find("textarea[name*='[description]']")
diff --git a/app/assets/javascripts/issue.js.coffee b/app/assets/javascripts/issue.js.coffee
index 74d6b80be5e..603a16da1ce 100644
--- a/app/assets/javascripts/issue.js.coffee
+++ b/app/assets/javascripts/issue.js.coffee
@@ -3,29 +3,12 @@
class @Issue
constructor: ->
- $('.edit-issue.inline-update input[type="submit"]').hide()
- $(".context .inline-update").on "change", "select", ->
- $(this).submit()
- $(".context .inline-update").on "change", "#issue_assignee_id", ->
- $(this).submit()
-
# Prevent duplicate event bindings
@disableTaskList()
if $("a.btn-close").length
@initTaskList()
- $('.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('')
-
initTaskList: ->
$('.issue-details .js-task-list-container').taskList('enable')
$(document).on 'tasklist:changed', '.issue-details .js-task-list-container', @updateTaskList
@@ -42,5 +25,5 @@ class @Issue
$.ajax
type: 'PATCH'
- url: $('form.js-issue-update').attr('action')
+ url: $('form.js-issuable-update').attr('action')
data: patchData
diff --git a/app/assets/javascripts/labels.js.coffee b/app/assets/javascripts/labels.js.coffee
index 1bc8840f9ac..d05bacd7494 100644
--- a/app/assets/javascripts/labels.js.coffee
+++ b/app/assets/javascripts/labels.js.coffee
@@ -1,7 +1,6 @@
class @Labels
constructor: ->
form = $('.label-form')
- @setupLabelForm(form)
@cleanBinding()
@addBinding()
@updateColorPreview()
@@ -14,10 +13,6 @@ class @Labels
$(document).off 'click', '.suggest-colors a'
$(document).off 'input', 'input#label_color'
- # Initializes the form to disable the save button if no color or title is entered
- setupLabelForm: (form) ->
- disableButtonIfAnyEmptyField form, '.form-control', form.find('.js-save-button')
-
# Updates the the preview color with the hex-color input
updateColorPreview: =>
previewColor = $('input#label_color').val()
diff --git a/app/assets/javascripts/line_highlighter.js.coffee b/app/assets/javascripts/line_highlighter.js.coffee
new file mode 100644
index 00000000000..e604e6025c2
--- /dev/null
+++ b/app/assets/javascripts/line_highlighter.js.coffee
@@ -0,0 +1,148 @@
+# LineHighlighter
+#
+# Handles single- and multi-line selection and highlight for blob views.
+#
+#= require jquery.scrollTo
+#
+# ### Example Markup
+#
+# <div id="tree-content-holder">
+# <div class="file-content">
+# <div class="line-numbers">
+# <a href="#L1" id="L1" data-line-number="1">1</a>
+# <a href="#L2" id="L2" data-line-number="2">2</a>
+# <a href="#L3" id="L3" data-line-number="3">3</a>
+# <a href="#L4" id="L4" data-line-number="4">4</a>
+# <a href="#L5" id="L5" data-line-number="5">5</a>
+# </div>
+# <pre class="code highlight">
+# <code>
+# <span id="LC1" class="line">...</span>
+# <span id="LC2" class="line">...</span>
+# <span id="LC3" class="line">...</span>
+# <span id="LC4" class="line">...</span>
+# <span id="LC5" class="line">...</span>
+# </code>
+# </pre>
+# </div>
+# </div>
+#
+class @LineHighlighter
+ # CSS class applied to highlighted lines
+ highlightClass: 'hll'
+
+ # Internal copy of location.hash so we're not dependent on `location` in tests
+ _hash: ''
+
+ # Initialize a LineHighlighter object
+ #
+ # hash - String URL hash for dependency injection in tests
+ constructor: (hash = location.hash) ->
+ @_hash = hash
+
+ @bindEvents()
+
+ unless hash == ''
+ range = @hashToRange(hash)
+
+ if range[0]
+ @highlightRange(range)
+
+ # Scroll to the first highlighted line on initial load
+ # Offset -50 for the sticky top bar, and another -100 for some context
+ $.scrollTo("#L#{range[0]}", offset: -150)
+
+ bindEvents: ->
+ $('#tree-content-holder').on 'mousedown', 'a[data-line-number]', @clickHandler
+
+ # While it may seem odd to bind to the mousedown event and then throw away
+ # the click event, there is a method to our madness.
+ #
+ # If not done this way, the line number anchor will sometimes keep its
+ # active state even when the event is cancelled, resulting in an ugly border
+ # around the link and/or a persisted underline text decoration.
+
+ $('#tree-content-holder').on 'click', 'a[data-line-number]', (event) ->
+ event.preventDefault()
+
+ clickHandler: (event) =>
+ event.preventDefault()
+
+ @clearHighlight()
+
+ lineNumber = $(event.target).closest('a').data('line-number')
+ current = @hashToRange(@_hash)
+
+ unless current[0] && event.shiftKey
+ # If there's no current selection, or there is but Shift wasn't held,
+ # treat this like a single-line selection.
+ @setHash(lineNumber)
+ @highlightLine(lineNumber)
+ else if event.shiftKey
+ if lineNumber < current[0]
+ range = [lineNumber, current[0]]
+ else
+ range = [current[0], lineNumber]
+
+ @setHash(range[0], range[1])
+ @highlightRange(range)
+
+ # Unhighlight previously highlighted lines
+ clearHighlight: ->
+ $(".#{@highlightClass}").removeClass(@highlightClass)
+
+ # Convert a URL hash String into line numbers
+ #
+ # hash - Hash String
+ #
+ # Examples:
+ #
+ # hashToRange('#L5') # => [5, null]
+ # hashToRange('#L5-15') # => [5, 15]
+ # hashToRange('#foo') # => [null, null]
+ #
+ # Returns an Array
+ hashToRange: (hash) ->
+ matches = hash.match(/^#?L(\d+)(?:-(\d+))?$/)
+
+ if matches && matches.length
+ first = parseInt(matches[1])
+ last = if matches[2] then parseInt(matches[2]) else null
+
+ [first, last]
+ else
+ [null, null]
+
+ # Highlight a single line
+ #
+ # lineNumber - Line number to highlight
+ highlightLine: (lineNumber) =>
+ $("#LC#{lineNumber}").addClass(@highlightClass)
+
+ # Highlight all lines within a range
+ #
+ # range - Array containing the starting and ending line numbers
+ highlightRange: (range) ->
+ if range[1]
+ for lineNumber in [range[0]..range[1]]
+ @highlightLine(lineNumber)
+ else
+ @highlightLine(range[0])
+
+ # Set the URL hash string
+ setHash: (firstLineNumber, lastLineNumber) =>
+ if lastLineNumber
+ hash = "#L#{firstLineNumber}-#{lastLineNumber}"
+ else
+ hash = "#L#{firstLineNumber}"
+
+ @_hash = hash
+ @__setLocationHash__(hash)
+
+ # Make the actual hash change in the browser
+ #
+ # This method is stubbed in tests.
+ __setLocationHash__: (value) ->
+ # We're using pushState instead of assigning location.hash directly to
+ # prevent the page from scrolling on the hashchange event
+ history.pushState({turbolinks: false, url: value}, document.title, value)
diff --git a/app/assets/javascripts/merge_request.js.coffee b/app/assets/javascripts/merge_request.js.coffee
index 25a7815dba2..b21cb7904b5 100644
--- a/app/assets/javascripts/merge_request.js.coffee
+++ b/app/assets/javascripts/merge_request.js.coffee
@@ -1,133 +1,39 @@
#= require jquery.waitforimages
#= require task_list
+#= require merge_request_tabs
+
class @MergeRequest
# Initialize MergeRequest behavior
#
# Options:
- # action - String, current controller action
- # diffs_loaded - Boolean, have diffs been pre-rendered server-side?
- # (default: true if `action` is 'diffs', otherwise false)
- # commits_loaded - Boolean, have commits been pre-rendered server-side?
- # (default: false)
+ # action - String, current controller action
#
constructor: (@opts) ->
- @initContextWidget()
this.$el = $('.merge-request')
- @diffs_loaded = @opts.diffs_loaded or @opts.action == 'diffs'
- @commits_loaded = @opts.commits_loaded or false
-
- this.bindEvents()
- this.activateTabFromPath()
-
this.$('.show-all-commits').on 'click', =>
this.showAllCommits()
+ @initTabs()
+
# Prevent duplicate event bindings
@disableTaskList()
if $("a.btn-close").length
@initTaskList()
- $('.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) ->
this.$el.find(selector)
- initContextWidget: ->
- $('.edit-merge_request.inline-update input[type="submit"]').hide()
- $(".context .inline-update").on "change", "select", ->
- $(this).submit()
- $(".context .inline-update").on "change", "#merge_request_assignee_id", ->
- $(this).submit()
-
-
- bindEvents: ->
- this.$('.merge-request-tabs a[data-toggle="tab"]').on 'shown.bs.tab', (e) =>
- $target = $(e.target)
- tab_action = $target.data('action')
-
- # Lazy-load diffs
- if tab_action == 'diffs'
- this.loadDiff() unless @diffs_loaded
- $('.diff-header').trigger('sticky_kit:recalc')
-
- # Skip tab-persisting behavior on MergeRequests#new
- unless @opts.action == 'new'
- @setCurrentAction(tab_action)
-
- # Activate a tab based on the current URL path
- #
- # If the current action is 'show' or 'new' (i.e., initial page load),
- # activates the first tab, otherwise activates the tab corresponding to the
- # current action (diffs, commits).
- activateTabFromPath: ->
- if @opts.action == 'show' || @opts.action == 'new'
- this.$('.merge-request-tabs a[data-toggle="tab"]:first').tab('show')
+ initTabs: ->
+ if @opts.action != 'new'
+ # `MergeRequests#new` has no tab-persisting or lazy-loading behavior
+ new MergeRequestTabs(@opts)
else
- this.$(".merge-request-tabs a[data-action='#{@opts.action}']").tab('show')
-
- # Replaces the current Merge Request-specific action in the URL with a new one
- #
- # If the action is "notes", the URL is reset to the standard
- # `MergeRequests#show` route.
- #
- # Examples:
- #
- # location.pathname # => "/namespace/project/merge_requests/1"
- # setCurrentAction('diffs')
- # location.pathname # => "/namespace/project/merge_requests/1/diffs"
- #
- # location.pathname # => "/namespace/project/merge_requests/1/diffs"
- # setCurrentAction('notes')
- # location.pathname # => "/namespace/project/merge_requests/1"
- #
- # location.pathname # => "/namespace/project/merge_requests/1/diffs"
- # setCurrentAction('commits')
- # location.pathname # => "/namespace/project/merge_requests/1/commits"
- setCurrentAction: (action) ->
- # Normalize action, just to be safe
- action = 'notes' if action == 'show'
-
- # Remove a trailing '/commits' or '/diffs'
- new_state = location.pathname.replace(/\/(commits|diffs)\/?$/, '')
-
- # Append the new action if we're on a tab other than 'notes'
- unless action == 'notes'
- new_state += "/#{action}"
-
- # Ensure parameters and hash come along for the ride
- new_state += location.search + location.hash
-
- # Replace the current history state with the new one without breaking
- # Turbolinks' history.
- #
- # See https://github.com/rails/turbolinks/issues/363
- history.replaceState {turbolinks: true, url: new_state}, '', new_state
-
- loadDiff: (event) ->
- $.ajax
- type: 'GET'
- url: this.$('.merge-request-tabs .diffs-tab a').attr('href') + ".json"
- beforeSend: =>
- this.$('.mr-loading-status .loading').show()
- complete: =>
- @diffs_loaded = true
- this.$('.mr-loading-status .loading').hide()
- success: (data) =>
- this.$(".diffs").html(data.html)
- dataType: 'json'
+ # Show the first tab (Commits)
+ $('.merge-request-tabs a[data-toggle="tab"]:first').tab('show')
showAllCommits: ->
this.$('.first-commits').remove()
@@ -149,5 +55,5 @@ class @MergeRequest
$.ajax
type: 'PATCH'
- url: $('form.js-merge-request-update').attr('action')
+ url: $('form.js-issuable-update').attr('action')
data: patchData
diff --git a/app/assets/javascripts/merge_request_tabs.js.coffee b/app/assets/javascripts/merge_request_tabs.js.coffee
new file mode 100644
index 00000000000..a132a0a9dcc
--- /dev/null
+++ b/app/assets/javascripts/merge_request_tabs.js.coffee
@@ -0,0 +1,154 @@
+# MergeRequestTabs
+#
+# Handles persisting and restoring the current tab selection and lazily-loading
+# content on the MergeRequests#show page.
+#
+# ### Example Markup
+#
+# <ul class="nav nav-tabs merge-request-tabs">
+# <li class="notes-tab active">
+# <a data-action="notes" data-target="#notes" data-toggle="tab" href="/foo/bar/merge_requests/1">
+# Discussion
+# </a>
+# </li>
+# <li class="commits-tab">
+# <a data-action="commits" data-target="#commits" data-toggle="tab" href="/foo/bar/merge_requests/1/commits">
+# Commits
+# </a>
+# </li>
+# <li class="diffs-tab">
+# <a data-action="diffs" data-target="#diffs" data-toggle="tab" href="/foo/bar/merge_requests/1/diffs">
+# Diffs
+# </a>
+# </li>
+# </ul>
+#
+# <div class="tab-content">
+# <div class="notes tab-pane active" id="notes">
+# Notes Content
+# </div>
+# <div class="commits tab-pane" id="commits">
+# Commits Content
+# </div>
+# <div class="diffs tab-pane" id="diffs">
+# Diffs Content
+# </div>
+# </div>
+#
+# <div class="mr-loading-status">
+# <div class="loading">
+# Loading Animation
+# </div>
+# </div>
+#
+class @MergeRequestTabs
+ diffsLoaded: false
+ commitsLoaded: false
+
+ constructor: (@opts = {}) ->
+ # Store the `location` object, allowing for easier stubbing in tests
+ @_location = location
+
+ switch @opts.action
+ when 'commits'
+ @commitsLoaded = true
+ when 'diffs'
+ @diffsLoaded = true
+
+ @bindEvents()
+ @activateTab(@opts.action)
+
+ bindEvents: ->
+ $(document).on 'shown.bs.tab', '.merge-request-tabs a[data-toggle="tab"]', @tabShown
+
+ tabShown: (event) =>
+ $target = $(event.target)
+ action = $target.data('action')
+
+ if action == 'commits'
+ @loadCommits($target.attr('href'))
+ else if action == 'diffs'
+ @loadDiff($target.attr('href'))
+
+ @setCurrentAction(action)
+
+ # Activate a tab based on the current action
+ activateTab: (action) ->
+ action = 'notes' if action == 'show'
+ $(".merge-request-tabs a[data-action='#{action}']").tab('show')
+
+ # Replaces the current Merge Request-specific action in the URL with a new one
+ #
+ # If the action is "notes", the URL is reset to the standard
+ # `MergeRequests#show` route.
+ #
+ # Examples:
+ #
+ # location.pathname # => "/namespace/project/merge_requests/1"
+ # setCurrentAction('diffs')
+ # location.pathname # => "/namespace/project/merge_requests/1/diffs"
+ #
+ # location.pathname # => "/namespace/project/merge_requests/1/diffs"
+ # setCurrentAction('notes')
+ # location.pathname # => "/namespace/project/merge_requests/1"
+ #
+ # location.pathname # => "/namespace/project/merge_requests/1/diffs"
+ # setCurrentAction('commits')
+ # location.pathname # => "/namespace/project/merge_requests/1/commits"
+ #
+ # Returns the new URL String
+ setCurrentAction: (action) =>
+ # Normalize action, just to be safe
+ action = 'notes' if action == 'show'
+
+ # Remove a trailing '/commits' or '/diffs'
+ new_state = @_location.pathname.replace(/\/(commits|diffs)\/?$/, '')
+
+ # Append the new action if we're on a tab other than 'notes'
+ unless action == 'notes'
+ new_state += "/#{action}"
+
+ # Ensure parameters and hash come along for the ride
+ new_state += @_location.search + @_location.hash
+
+ # Replace the current history state with the new one without breaking
+ # Turbolinks' history.
+ #
+ # See https://github.com/rails/turbolinks/issues/363
+ history.replaceState {turbolinks: true, url: new_state}, document.title, new_state
+
+ new_state
+
+ loadCommits: (source) ->
+ return if @commitsLoaded
+
+ @_get
+ url: "#{source}.json"
+ success: (data) =>
+ document.getElementById('commits').innerHTML = data.html
+ $('.js-timeago').timeago()
+ @commitsLoaded = true
+
+ loadDiff: (source) ->
+ return if @diffsLoaded
+
+ @_get
+ url: "#{source}.json"
+ success: (data) =>
+ document.getElementById('diffs').innerHTML = data.html
+ @diffsLoaded = true
+
+ toggleLoading: ->
+ $('.mr-loading-status .loading').toggle()
+
+ _get: (options) ->
+ defaults = {
+ beforeSend: @toggleLoading
+ complete: @toggleLoading
+ dataType: 'json'
+ type: 'GET'
+ }
+
+ options = $.extend({}, defaults, options)
+
+ $.ajax(options)
diff --git a/app/assets/javascripts/merge_request_widget.js.coffee b/app/assets/javascripts/merge_request_widget.js.coffee
index ca769e06a4e..e4d815bb4e4 100644
--- a/app/assets/javascripts/merge_request_widget.js.coffee
+++ b/app/assets/javascripts/merge_request_widget.js.coffee
@@ -36,11 +36,11 @@ class @MergeRequestWidget
showCiState: (state) ->
$('.ci_widget').hide()
- allowed_states = ["failed", "canceled", "running", "pending", "success"]
+ allowed_states = ["failed", "canceled", "running", "pending", "success", "not_found"]
if state in allowed_states
$('.ci_widget.ci-' + state).show()
switch state
- when "failed", "canceled"
+ when "failed", "canceled", "not_found"
@setMergeButtonClass('btn-danger')
when "running", "pending"
@setMergeButtonClass('btn-warning')
diff --git a/app/assets/javascripts/notes.js.coffee b/app/assets/javascripts/notes.js.coffee
index 1c05a2b9fe8..bcff7bcc49e 100644
--- a/app/assets/javascripts/notes.js.coffee
+++ b/app/assets/javascripts/notes.js.coffee
@@ -298,7 +298,7 @@ class @Notes
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.addClass('current-note-edit-form gfm-form')
form.find('.div-dropzone').remove()
# Show the attachment delete link
diff --git a/app/assets/javascripts/pager.js.coffee b/app/assets/javascripts/pager.js.coffee
index fe83dc0410e..d639303aed3 100644
--- a/app/assets/javascripts/pager.js.coffee
+++ b/app/assets/javascripts/pager.js.coffee
@@ -12,7 +12,7 @@
@loading.show()
$.ajax
type: "GET"
- url: location.href
+ url: $(".content_list").data('href') || location.href
data: "limit=" + @limit + "&offset=" + @offset
complete: =>
@loading.hide()
diff --git a/app/assets/javascripts/project_new.js.coffee b/app/assets/javascripts/project_new.js.coffee
index 836269c44f9..fecdb9fc2e7 100644
--- a/app/assets/javascripts/project_new.js.coffee
+++ b/app/assets/javascripts/project_new.js.coffee
@@ -3,9 +3,3 @@ class @ProjectNew
$('.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
index 6828ae471e5..1fdf28f2528 100644
--- a/app/assets/javascripts/project_show.js.coffee
+++ b/app/assets/javascripts/project_show.js.coffee
@@ -1,15 +1,3 @@
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"
+ # I kept class for future
diff --git a/app/assets/javascripts/shortcuts_navigation.coffee b/app/assets/javascripts/shortcuts_navigation.coffee
index 31895fbf2bc..5b6f9e7e3f2 100644
--- a/app/assets/javascripts/shortcuts_navigation.coffee
+++ b/app/assets/javascripts/shortcuts_navigation.coffee
@@ -4,6 +4,7 @@ class @ShortcutsNavigation extends Shortcuts
constructor: ->
super()
Mousetrap.bind('g p', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-project'))
+ Mousetrap.bind('g e', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-project-activity'))
Mousetrap.bind('g f', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-tree'))
Mousetrap.bind('g c', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-commits'))
Mousetrap.bind('g n', -> ShortcutsNavigation.findAndFollowLink('.shortcuts-network'))
diff --git a/app/assets/stylesheets/base/mixins.scss b/app/assets/stylesheets/base/mixins.scss
index 08cbe911672..d64e79170b9 100644
--- a/app/assets/stylesheets/base/mixins.scss
+++ b/app/assets/stylesheets/base/mixins.scss
@@ -109,7 +109,7 @@
font-size: 1.2em;
}
- blockquote p {
+ blockquote {
color: #888;
font-size: 15px;
line-height: 1.5;
diff --git a/app/assets/stylesheets/generic/common.scss b/app/assets/stylesheets/generic/common.scss
index 1419a9cded9..961ac793de2 100644
--- a/app/assets/stylesheets/generic/common.scss
+++ b/app/assets/stylesheets/generic/common.scss
@@ -364,3 +364,12 @@ table {
margin-top: 8px;
}
}
+
+.profiler-results {
+ top: 50px !important;
+
+ .profiler-button,
+ .profiler-controls {
+ border-color: #EEE !important;
+ }
+}
diff --git a/app/assets/stylesheets/generic/gfm.scss b/app/assets/stylesheets/generic/gfm.scss
index 8fac5e534fa..bd9200ace23 100644
--- a/app/assets/stylesheets/generic/gfm.scss
+++ b/app/assets/stylesheets/generic/gfm.scss
@@ -19,3 +19,7 @@
height: 14em;
}
}
+
+.gfm-commit, .gfm-commit_range {
+ font-family: $monospace_font;
+}
diff --git a/app/assets/stylesheets/generic/header.scss b/app/assets/stylesheets/generic/header.scss
index 8f17232592e..31e2ad86691 100644
--- a/app/assets/stylesheets/generic/header.scss
+++ b/app/assets/stylesheets/generic/header.scss
@@ -3,6 +3,8 @@
*
*/
header {
+ transition-duration: .3s;
+
&.navbar-empty {
background: #FFF;
border-bottom: 1px solid #EEE;
@@ -10,6 +12,10 @@ header {
.center-logo {
margin: 8px 0;
text-align: center;
+
+ img {
+ height: 32px;
+ }
}
}
@@ -63,28 +69,35 @@ header {
float: left;
height: $header-height;
width: $sidebar_width;
+ overflow: hidden;
+ transition-duration: .3s;
a {
float: left;
height: $header-height;
width: 100%;
padding: ($header-height - 36 ) / 2 8px;
-
- h3 {
- width: 158px;
- float: left;
- margin: 0;
- margin-left: 14px;
- font-size: 18px;
- line-height: $header-height - 14;
- font-weight: normal;
- }
+ overflow: hidden;
img {
width: 36px;
height: 36px;
float: left;
}
+
+ .gitlab-text-container {
+ width: 230px;
+
+ h3 {
+ width: 158px;
+ float: left;
+ margin: 0;
+ margin-left: 14px;
+ font-size: 18px;
+ line-height: $header-height - 14;
+ font-weight: normal;
+ }
+ }
}
&:hover {
@@ -157,10 +170,6 @@ header {
@mixin collapsed-header {
.header-logo {
width: $sidebar_collapsed_width;
-
- h3 {
- display: none;
- }
}
.header-content {
diff --git a/app/assets/stylesheets/generic/mobile.scss b/app/assets/stylesheets/generic/mobile.scss
index a49775daf8b..24c828e0d97 100644
--- a/app/assets/stylesheets/generic/mobile.scss
+++ b/app/assets/stylesheets/generic/mobile.scss
@@ -44,20 +44,18 @@
.project-home-panel {
padding-left: 0 !important;
- .project-home-row {
- .project-home-desc {
- margin-right: 0 !important;
- float: none !important;
- }
-
- .project-repo-buttons {
- position: static;
- margin-top: 15px;
- width: 100%;
- float: none;
- text-align: left;
- }
+ .project-avatar {
+ display: block;
}
+
+ .project-repo-buttons,
+ .git-clone-holder {
+ display: none;
+ }
+ }
+
+ .project-stats {
+ display: none;
}
.container .title {
diff --git a/app/assets/stylesheets/generic/sidebar.scss b/app/assets/stylesheets/generic/sidebar.scss
index 65e06e14c73..b96664d30db 100644
--- a/app/assets/stylesheets/generic/sidebar.scss
+++ b/app/assets/stylesheets/generic/sidebar.scss
@@ -2,68 +2,75 @@
.sidebar-wrapper {
position: fixed;
top: 0;
+ bottom: 0;
+ overflow-y: auto;
+ overflow-x: hidden;
left: 0;
height: 100%;
+ transition-duration: .3s;
}
}
.sidebar-wrapper {
z-index: 99;
background: $background-color;
+ transition-duration: .3s;
}
.content-wrapper {
width: 100%;
- padding: 15px;
+ padding: 20px;
background: #FFF;
}
.nav-sidebar {
- margin: 0;
+ margin-top: 29 + $header-height;
+ margin-bottom: 50px;
+ transition-duration: .3s;
list-style: none;
+ overflow: hidden;
&.navbar-collapse {
padding: 0px !important;
}
-}
-.nav-sidebar li a .count {
- float: right;
- background: #eee;
- padding: 0px 8px;
- @include border-radius(6px);
-}
+ li {
+ width: $sidebar_width;
-.nav-sidebar li {
-}
+ &.separate-item {
+ padding-top: 10px;
+ margin-top: 10px;
+ }
-.nav-sidebar li {
- &.separate-item {
- padding-top: 10px;
- margin-top: 10px;
- }
+ a {
+ padding: 8px 15px;
+ font-size: 13px;
+ line-height: 18px;
+ color: $gray;
+ display: block;
+ text-decoration: none;
+ padding-left: 16px;
- a {
- color: $gray;
- display: block;
- text-decoration: none;
- padding: 8px 15px;
- font-size: 13px;
- line-height: 20px;
- padding-left: 16px;
+ &:hover {
+ text-decoration: none;
+ }
- &:hover {
- text-decoration: none;
- }
+ &:active, &:focus {
+ text-decoration: none;
+ }
- &:active, &:focus {
- text-decoration: none;
- }
+ i {
+ width: 20px;
+ color: $gray-light;
+ margin-right: 23px;
+ }
- i {
- width: 20px;
- color: $gray-light;
- margin-right: 23px;
+ .count {
+ float: right;
+ background: #eee;
+ padding: 0px 8px;
+ @include border-radius(6px);
+ }
}
}
}
@@ -79,45 +86,39 @@
@mixin expanded-sidebar {
padding-left: $sidebar_width;
+ transition-duration: .3s;
.sidebar-wrapper {
width: $sidebar_width;
.nav-sidebar {
- margin-top: 29px;
- position: fixed;
- top: $header-height;
width: $sidebar_width;
}
- }
- .content-wrapper {
- padding: 20px;
+ .nav-sidebar li a{
+ width: 230px;
+
+ &.back-link {
+ i {
+ visibility: hidden;
+ }
+ }
+ }
}
}
@mixin folded-sidebar {
padding-left: 50px;
+ transition-duration: .3s;
.sidebar-wrapper {
width: $sidebar_collapsed_width;
.nav-sidebar {
- margin-top: 29px;
- position: fixed;
- top: $header-height;
width: $sidebar_collapsed_width;
li a {
- padding-left: 18px;
- font-size: 14px;
- padding: 8px 15px;
- text-align: center;
-
-
- & > span {
- display: none;
- }
+ padding-left: 16px;
}
}
@@ -127,9 +128,7 @@
}
.sidebar-user {
- .username {
- display: none;
- }
+ width: $sidebar_collapsed_width;
}
}
}
@@ -144,6 +143,7 @@
height: 28px;
text-align: center;
line-height: 28px;
+ transition-duration: .3s;
}
.collapse-nav a:hover {
@@ -176,12 +176,15 @@
}
.sidebar-user {
- position: absolute;
+ position: fixed;
bottom: 0;
- width: 100%;
+ width: $sidebar_width;
padding: 10px;
+ overflow: hidden;
+ transition-duration: .3s;
.username {
margin-top: 5px;
+ width: $sidebar_width - 2 * 10px;
}
}
diff --git a/app/assets/stylesheets/generic/typography.scss b/app/assets/stylesheets/generic/typography.scss
index 66767cb13cb..2db4213159a 100644
--- a/app/assets/stylesheets/generic/typography.scss
+++ b/app/assets/stylesheets/generic/typography.scss
@@ -17,6 +17,14 @@ pre {
background: #333;
color: $background-color;
}
+
+ &.plain-readme {
+ background: none;
+ border: none;
+ padding: 0;
+ margin: 0;
+ font-size: 14px;
+ }
}
.monospace {
diff --git a/app/assets/stylesheets/pages/issues.scss b/app/assets/stylesheets/pages/issues.scss
index ed938f86b35..3572f33e91f 100644
--- a/app/assets/stylesheets/pages/issues.scss
+++ b/app/assets/stylesheets/pages/issues.scss
@@ -145,9 +145,3 @@ h2.issue-title {
.issue-form .select2-container {
width: 250px !important;
}
-
-.issues-holder {
- .issue-info {
- margin-left: 20px;
- }
-}
diff --git a/app/assets/stylesheets/pages/notes.scss b/app/assets/stylesheets/pages/notes.scss
index 42b8ecabb38..4da65b28743 100644
--- a/app/assets/stylesheets/pages/notes.scss
+++ b/app/assets/stylesheets/pages/notes.scss
@@ -72,13 +72,28 @@ ul.notes {
.note {
display: block;
position:relative;
+
.note-body {
overflow: auto;
+
.note-text {
overflow: auto;
word-wrap: break-word;
@include md-typography;
+ // Reset ul style types since we're nested inside a ul already
+ & > ul {
+ list-style-type: disc;
+
+ ul {
+ list-style-type: circle;
+
+ ul {
+ list-style-type: square;
+ }
+ }
+ }
+
// Reduce left padding of first task list ul element
ul.task-list:first-child {
padding-left: 10px;
@@ -94,6 +109,7 @@ ul.notes {
}
}
}
+
.note-header {
padding-bottom: 3px;
}
diff --git a/app/assets/stylesheets/pages/projects.scss b/app/assets/stylesheets/pages/projects.scss
index e19b2eafa43..5f415f2d78f 100644
--- a/app/assets/stylesheets/pages/projects.scss
+++ b/app/assets/stylesheets/pages/projects.scss
@@ -15,48 +15,31 @@
}
.project-home-panel {
- margin-top: 10px;
- margin-bottom: 15px;
- position: relative;
- padding-left: 65px;
- min-height: 50px;
+ text-align: center;
+ margin-bottom: 20px;
.project-identicon-holder {
- position: absolute;
- left: 0;
- top: -14px;
+ margin-bottom: 15px;
- .avatar {
- width: 50px;
- height: 50px;
+ .avatar, .identicon {
+ margin: 0 auto;
+ float: none;
}
.identicon {
- font-size: 26px;
- line-height: 50px;
+ @include border-radius(50%);
}
}
- .project-home-row {
- @extend .clearfix;
- margin-bottom: 15px;
-
- &.project-home-row-top {
- margin-bottom: 15px;
+ .lead {
+ p {
+ display: inline;
}
+ }
- .project-home-desc {
- color: $gray;
- float: left;
- font-size: 16px;
- line-height: 1.3;
- margin-right: 250px;
-
- // Render Markdown-generated HTML inline for this block
- p {
- display: inline;
- }
- }
+ .git-clone-holder {
+ max-width: 600px;
+ margin: 0 auto;
}
.visibility-level-label {
@@ -67,22 +50,22 @@
}
.project-repo-buttons {
- margin-top: -3px;
- position: absolute;
- right: 0;
- width: 265px;
- text-align: right;
+ margin-top: 25px;
+ margin-bottom: 25px;
.btn {
+ @extend .btn-info;
+
+ margin-left: 10px;
font-weight: bold;
font-size: 14px;
line-height: 16px;
+ padding: 8px 12px;
.count {
- padding-left: 10px;
- border-left: 1px solid #ccc;
+ padding-left: 7px;
display: inline-block;
- margin-left: 10px;
+ margin-left: 7px;
}
}
}
@@ -307,3 +290,15 @@ table.table.protected-branches-list tr.no-border {
float: left;
margin-right: 10px;
}
+
+.project-stats {
+ text-align: center;
+
+ ul.nav-pills { display:inline-block; }
+ li { display:inline; }
+ a { float:left; }
+}
+
+pre.light-well {
+ border-color: #f1f1f1;
+}
diff --git a/app/assets/stylesheets/themes/gitlab-theme.scss b/app/assets/stylesheets/themes/gitlab-theme.scss
index 7cabeaefb93..3589cb88d03 100644
--- a/app/assets/stylesheets/themes/gitlab-theme.scss
+++ b/app/assets/stylesheets/themes/gitlab-theme.scss
@@ -35,9 +35,9 @@
.sidebar-wrapper {
background: $color-darker;
- border-right: 1px solid $color-darker;
.sidebar-user {
+ background: $color-darker;
color: $color-light;
&:hover {
@@ -72,7 +72,7 @@
&.active a {
color: #FFF;
- font-weight: bold;
+ background: $color-dark;
&.no-highlight {
border: none;
diff --git a/app/controllers/admin/identities_controller.rb b/app/controllers/admin/identities_controller.rb
new file mode 100644
index 00000000000..d28614731f9
--- /dev/null
+++ b/app/controllers/admin/identities_controller.rb
@@ -0,0 +1,41 @@
+class Admin::IdentitiesController < Admin::ApplicationController
+ before_action :user
+ before_action :identity, except: :index
+
+ def index
+ @identities = @user.identities
+ end
+
+ def edit
+ end
+
+ def update
+ if @identity.update_attributes(identity_params)
+ redirect_to admin_user_identities_path(@user), notice: 'User identity was successfully updated.'
+ else
+ render :edit
+ end
+ end
+
+ def destroy
+ if @identity.destroy
+ redirect_to admin_user_identities_path(@user), notice: 'User identity was successfully removed.'
+ else
+ redirect_to admin_user_identities_path(@user), alert: 'Failed to remove user identity.'
+ end
+ end
+
+ protected
+
+ def user
+ @user ||= User.find_by!(username: params[:user_id])
+ end
+
+ def identity
+ @identity ||= user.identities.find(params[:id])
+ end
+
+ def identity_params
+ params.require(:identity).permit(:provider, :extern_uid)
+ end
+end
diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb
index f616ccf5684..da5f5bb83fa 100644
--- a/app/controllers/admin/projects_controller.rb
+++ b/app/controllers/admin/projects_controller.rb
@@ -23,7 +23,8 @@ class Admin::ProjectsController < Admin::ApplicationController
end
def transfer
- ::Projects::TransferService.new(@project, current_user, params.dup).execute
+ namespace = Namespace.find_by(id: params[:new_namespace_id])
+ ::Projects::TransferService.new(@project, current_user, params.dup).execute(namespace)
@project.reload
redirect_to admin_namespace_project_path(@project.namespace, @project)
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 06d6d61e907..770fe00af51 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -1,5 +1,5 @@
class Admin::UsersController < Admin::ApplicationController
- before_action :user, only: [:show, :edit, :update, :destroy]
+ before_action :user, except: [:index, :new, :create]
def index
@users = User.order_name_asc.filter(params[:filter])
@@ -9,8 +9,17 @@ class Admin::UsersController < Admin::ApplicationController
end
def show
+ end
+
+ def projects
@personal_projects = user.personal_projects
@joined_projects = user.projects.joined(@user)
+ end
+
+ def groups
+ end
+
+ def keys
@keys = user.keys
end
@@ -38,6 +47,20 @@ class Admin::UsersController < Admin::ApplicationController
end
end
+ def unlock
+ if user.unlock_access!
+ redirect_to :back, alert: "Successfully unlocked"
+ else
+ redirect_to :back, alert: "Error occurred. User was not unlocked"
+ end
+ end
+
+ def disable_two_factor
+ user.disable_two_factor!
+ redirect_to admin_user_path(user),
+ notice: 'Two-factor Authentication has been disabled for this user'
+ end
+
def create
opts = {
force_random_password: true,
@@ -86,7 +109,7 @@ class Admin::UsersController < Admin::ApplicationController
end
def destroy
- DeleteUserService.new.execute(user)
+ DeleteUserService.new(current_user).execute(user)
respond_to do |format|
format.html { redirect_to admin_users_path }
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index a657d3c54ee..362b03e0d5e 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -56,7 +56,7 @@ class ApplicationController < ActionController::Base
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'
+ if current_user.nil? && root_path == request.path
redirect_to current_application_settings.home_page_url and return
end
end
@@ -183,7 +183,10 @@ class ApplicationController < ActionController::Base
headers['X-XSS-Protection'] = '1; mode=block'
headers['X-UA-Compatible'] = 'IE=edge'
headers['X-Content-Type-Options'] = 'nosniff'
- headers['Strict-Transport-Security'] = 'max-age=31536000' if Gitlab.config.gitlab.https
+ # Enabling HSTS for non-standard ports would send clients to the wrong port
+ if Gitlab.config.gitlab.https and Gitlab.config.gitlab.port == 443
+ headers['Strict-Transport-Security'] = 'max-age=31536000'
+ end
end
def add_gon_variables
@@ -265,6 +268,7 @@ class ApplicationController < ActionController::Base
params[:scope] = 'all' if params[:scope].blank?
params[:state] = 'opened' if params[:state].blank?
+ @sort = params[:sort]
@filter_params = params.dup
if @project
diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb
index 11af9895261..52e9c58b47c 100644
--- a/app/controllers/autocomplete_controller.rb
+++ b/app/controllers/autocomplete_controller.rb
@@ -1,22 +1,35 @@
class AutocompleteController < ApplicationController
+ skip_before_action :authenticate_user!, only: [:users]
+
def users
- @users =
- if params[:project_id].present?
- project = Project.find(params[:project_id])
+ begin
+ @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_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
+ if can?(current_user, :read_group, group)
+ group.users
+ end
+ elsif current_user
+ User.all
end
- else
- User.all
+ rescue ActiveRecord::RecordNotFound
+ if current_user
+ return render json: {}, status: 404
end
+ end
+
+ if @users.nil? && current_user.nil?
+ authenticate_user!
+ end
+ @users ||= User.none
@users = @users.search(params[:search]) if params[:search].present?
@users = @users.active
@users = @users.page(params[:page]).per(PER_PAGE)
diff --git a/app/controllers/dashboard_controller.rb b/app/controllers/dashboard_controller.rb
index 17ddde68f93..d2f0c43929f 100644
--- a/app/controllers/dashboard_controller.rb
+++ b/app/controllers/dashboard_controller.rb
@@ -1,7 +1,7 @@
class DashboardController < Dashboard::ApplicationController
- before_action :load_projects, except: [:projects]
+ before_action :load_projects
before_action :event_filter, only: :show
-
+
respond_to :html
def show
diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index 2e381822e42..901c1cdddcb 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -121,6 +121,8 @@ class GroupsController < Groups::ApplicationController
def determine_layout
if [:new, :create].include?(action_name.to_sym)
'application'
+ elsif [:edit, :update, :projects].include?(action_name.to_sym)
+ 'group_settings'
else
'group'
end
diff --git a/app/controllers/oauth/authorized_applications_controller.rb b/app/controllers/oauth/authorized_applications_controller.rb
index 3ab6def511c..4193ac11399 100644
--- a/app/controllers/oauth/authorized_applications_controller.rb
+++ b/app/controllers/oauth/authorized_applications_controller.rb
@@ -4,7 +4,12 @@ class Oauth::AuthorizedApplicationsController < Doorkeeper::AuthorizedApplicatio
layout 'profile'
def destroy
- Doorkeeper::AccessToken.revoke_all_for(params[:id], current_resource_owner)
+ if params[:token_id].present?
+ current_resource_owner.oauth_authorized_tokens.find(params[:token_id]).revoke
+ else
+ Doorkeeper::AccessToken.revoke_all_for(params[:id], current_resource_owner)
+ end
+
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 765adaf2128..fd51b380da2 100644
--- a/app/controllers/omniauth_callbacks_controller.rb
+++ b/app/controllers/omniauth_callbacks_controller.rb
@@ -28,6 +28,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
# Do additional LDAP checks for the user filter and EE features
if @user.allowed?
+ log_audit_event(gl_user, with: :ldap)
sign_in_and_redirect(gl_user)
else
flash[:alert] = "Access denied for your LDAP account."
@@ -47,6 +48,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
if current_user
# Add new authentication method
current_user.identities.find_or_create_by(extern_uid: oauth['uid'], provider: oauth['provider'])
+ log_audit_event(current_user, with: oauth['provider'])
redirect_to profile_account_path, notice: 'Authentication method updated'
else
@user = Gitlab::OAuth::User.new(oauth)
@@ -54,6 +56,7 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
# Only allow properly saved users to login.
if @user.persisted? && @user.valid?
+ log_audit_event(@user.gl_user, with: oauth['provider'])
sign_in_and_redirect(@user.gl_user)
else
error_message =
@@ -83,4 +86,9 @@ class OmniauthCallbacksController < Devise::OmniauthCallbacksController
def oauth
@oauth ||= request.env['omniauth.auth']
end
+
+ def log_audit_event(user, options = {})
+ AuditEventService.new(user, user, options).
+ for_authentication.security_event
+ end
end
diff --git a/app/controllers/passwords_controller.rb b/app/controllers/passwords_controller.rb
index 145f27b67dd..8450ba31021 100644
--- a/app/controllers/passwords_controller.rb
+++ b/app/controllers/passwords_controller.rb
@@ -24,7 +24,7 @@ class PasswordsController < Devise::PasswordsController
super do |resource|
# TODO (rspeicher): In Devise master (> 3.4.1), we can set
# `Devise.sign_in_after_reset_password = false` and avoid this mess.
- if resource.errors.empty? && resource.try(:otp_required_for_login?)
+ if resource.errors.empty? && resource.try(:two_factor_enabled?)
resource.unlock_access! if unlockable?(resource)
# Since we are not signing this user in, we use the :updated_not_active
diff --git a/app/controllers/profiles/preferences_controller.rb b/app/controllers/profiles/preferences_controller.rb
index 538b09ca54d..f83b4abd1e2 100644
--- a/app/controllers/profiles/preferences_controller.rb
+++ b/app/controllers/profiles/preferences_controller.rb
@@ -32,6 +32,7 @@ class Profiles::PreferencesController < Profiles::ApplicationController
params.require(:user).permit(
:color_scheme_id,
:dashboard,
+ :project_view,
:theme_id
)
end
diff --git a/app/controllers/profiles/two_factor_auths_controller.rb b/app/controllers/profiles/two_factor_auths_controller.rb
index e7579c652fb..f9af0871cf1 100644
--- a/app/controllers/profiles/two_factor_auths_controller.rb
+++ b/app/controllers/profiles/two_factor_auths_controller.rb
@@ -10,7 +10,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
def create
if current_user.valid_otp?(params[:pin_code])
- current_user.otp_required_for_login = true
+ current_user.two_factor_enabled = true
@codes = current_user.generate_otp_backup_codes!
current_user.save!
@@ -29,13 +29,7 @@ class Profiles::TwoFactorAuthsController < Profiles::ApplicationController
end
def destroy
- current_user.update_attributes({
- otp_required_for_login: false,
- encrypted_otp_secret: nil,
- encrypted_otp_secret_iv: nil,
- encrypted_otp_secret_salt: nil,
- otp_backup_codes: nil
- })
+ current_user.disable_two_factor!
redirect_to profile_account_path
end
diff --git a/app/controllers/profiles_controller.rb b/app/controllers/profiles_controller.rb
index b4af9e490ed..26a4de15462 100644
--- a/app/controllers/profiles_controller.rb
+++ b/app/controllers/profiles_controller.rb
@@ -11,7 +11,8 @@ class ProfilesController < Profiles::ApplicationController
def applications
@applications = current_user.oauth_applications
@authorized_tokens = current_user.oauth_authorized_tokens
- @authorized_apps = @authorized_tokens.map(&:application).uniq
+ @authorized_anonymous_tokens = @authorized_tokens.reject(&:application)
+ @authorized_apps = @authorized_tokens.map(&:application).uniq - [nil]
end
def update
@@ -37,8 +38,11 @@ class ProfilesController < Profiles::ApplicationController
redirect_to profile_account_path
end
- def history
- @events = current_user.recent_events.page(params[:page]).per(PER_PAGE)
+ def audit_log
+ @events = AuditEvent.where(entity_type: "User", entity_id: current_user.id).
+ order("created_at DESC").
+ page(params[:page]).
+ per(PER_PAGE)
end
def update_username
diff --git a/app/controllers/projects/branches_controller.rb b/app/controllers/projects/branches_controller.rb
index 696011b94b9..117ae3aaa3d 100644
--- a/app/controllers/projects/branches_controller.rb
+++ b/app/controllers/projects/branches_controller.rb
@@ -32,7 +32,7 @@ class Projects::BranchesController < Projects::ApplicationController
end
def destroy
- DeleteBranchService.new(project, current_user).execute(params[:id])
+ status = DeleteBranchService.new(project, current_user).execute(params[:id])
@branch_name = params[:id]
respond_to do |format|
@@ -40,7 +40,7 @@ class Projects::BranchesController < Projects::ApplicationController
redirect_to namespace_project_branches_path(@project.namespace,
@project)
end
- format.js
+ format.js { render status: status[:return_code] }
end
end
end
diff --git a/app/controllers/projects/graphs_controller.rb b/app/controllers/projects/graphs_controller.rb
index a060ea6f998..0b6f7f5c91e 100644
--- a/app/controllers/projects/graphs_controller.rb
+++ b/app/controllers/projects/graphs_controller.rb
@@ -1,6 +1,9 @@
class Projects::GraphsController < Projects::ApplicationController
+ include ExtractsPath
+
# Authorize
before_action :require_non_empty_project
+ before_action :assign_ref_vars
before_action :authorize_download_code!
def show
@@ -13,7 +16,7 @@ class Projects::GraphsController < Projects::ApplicationController
end
def commits
- @commits = @project.repository.commits(nil, nil, 2000, 0, true)
+ @commits = @project.repository.commits(@ref, 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
@@ -23,7 +26,7 @@ class Projects::GraphsController < Projects::ApplicationController
private
def fetch_graph
- @commits = @project.repository.commits(nil, nil, 6000, 0, true)
+ @commits = @project.repository.commits(@ref, nil, 6000, 0, true)
@log = []
@commits.each do |commit|
diff --git a/app/controllers/projects/issues_controller.rb b/app/controllers/projects/issues_controller.rb
index 7d168aa827b..bfafdeeb1fb 100644
--- a/app/controllers/projects/issues_controller.rb
+++ b/app/controllers/projects/issues_controller.rb
@@ -6,10 +6,10 @@ class Projects::IssuesController < Projects::ApplicationController
before_action :authorize_read_issue!
# Allow write(create) issue
- before_action :authorize_write_issue!, only: [:new, :create]
+ before_action :authorize_create_issue!, only: [:new, :create]
# Allow modify issue
- before_action :authorize_modify_issue!, only: [:edit, :update]
+ before_action :authorize_update_issue!, only: [:edit, :update]
# Allow issues bulk update
before_action :authorize_admin_issues!, only: [:bulk_update]
@@ -55,6 +55,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
def show
+ @participants = @issue.participants(current_user, @project)
@note = @project.notes.new(noteable: @issue)
@notes = @issue.notes.inc_author.fresh
@noteable = @issue
@@ -121,8 +122,8 @@ class Projects::IssuesController < Projects::ApplicationController
end
end
- def authorize_modify_issue!
- return render_404 unless can?(current_user, :modify_issue, @issue)
+ def authorize_update_issue!
+ return render_404 unless can?(current_user, :update_issue, @issue)
end
def authorize_admin_issues!
diff --git a/app/controllers/projects/merge_requests_controller.rb b/app/controllers/projects/merge_requests_controller.rb
index 14069bafe71..d1265198318 100644
--- a/app/controllers/projects/merge_requests_controller.rb
+++ b/app/controllers/projects/merge_requests_controller.rb
@@ -14,10 +14,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
before_action :authorize_read_merge_request!
# Allow write(create) merge_request
- before_action :authorize_write_merge_request!, only: [:new, :create]
+ before_action :authorize_create_merge_request!, only: [:new, :create]
# Allow modify merge_request
- before_action :authorize_modify_merge_request!, only: [:close, :edit, :update, :sort]
+ before_action :authorize_update_merge_request!, only: [:close, :edit, :update, :sort]
def index
terms = params['issue_search']
@@ -71,7 +71,10 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def commits
- render 'show'
+ respond_to do |format|
+ format.html { render 'show' }
+ format.json { render json: { html: view_to_html_string('projects/merge_requests/show/_commits') } }
+ end
end
def new
@@ -215,8 +218,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
@closes_issues ||= @merge_request.closes_issues
end
- def authorize_modify_merge_request!
- return render_404 unless can?(current_user, :modify_merge_request, @merge_request)
+ def authorize_update_merge_request!
+ return render_404 unless can?(current_user, :update_merge_request, @merge_request)
end
def authorize_admin_merge_request!
@@ -243,6 +246,8 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
def define_show_vars
+ @participants = @merge_request.participants(current_user, @project)
+
# Build a note object for comment form
@note = @project.notes.new(noteable: @merge_request)
@notes = @merge_request.mr_and_commit_notes.inc_author.fresh
diff --git a/app/controllers/projects/milestones_controller.rb b/app/controllers/projects/milestones_controller.rb
index 61689488d13..9efe9704d1e 100644
--- a/app/controllers/projects/milestones_controller.rb
+++ b/app/controllers/projects/milestones_controller.rb
@@ -64,7 +64,12 @@ class Projects::MilestonesController < Projects::ApplicationController
end
def destroy
- return access_denied! unless can?(current_user, :admin_milestone, @milestone)
+ return access_denied! unless can?(current_user, :admin_milestone, @project)
+
+ update_params = { milestone: nil }
+ @milestone.issues.each do |issue|
+ Issues::UpdateService.new(@project, current_user, update_params).execute(issue)
+ end
@milestone.destroy
diff --git a/app/controllers/projects/notes_controller.rb b/app/controllers/projects/notes_controller.rb
index f3e521adb69..c4a87e9dbd8 100644
--- a/app/controllers/projects/notes_controller.rb
+++ b/app/controllers/projects/notes_controller.rb
@@ -1,7 +1,7 @@
class Projects::NotesController < Projects::ApplicationController
# Authorize
before_action :authorize_read_note!
- before_action :authorize_write_note!, only: [:create]
+ before_action :authorize_create_note!, only: [:create]
before_action :authorize_admin_note!, only: [:update, :destroy]
before_action :find_current_user_notes, except: [:destroy, :delete_attachment]
diff --git a/app/controllers/projects/refs_controller.rb b/app/controllers/projects/refs_controller.rb
index 01ca1537c0e..d83561cf32a 100644
--- a/app/controllers/projects/refs_controller.rb
+++ b/app/controllers/projects/refs_controller.rb
@@ -8,17 +8,21 @@ class Projects::RefsController < Projects::ApplicationController
def switch
respond_to do |format|
format.html do
- new_path = if params[:destination] == "tree"
- namespace_project_tree_path(@project.namespace, @project,
- (@id))
- elsif params[:destination] == "blob"
- namespace_project_blob_path(@project.namespace, @project,
- (@id))
- elsif params[:destination] == "graph"
- namespace_project_network_path(@project.namespace, @project, @id, @options)
- else
- namespace_project_commits_path(@project.namespace, @project, @id)
- end
+ new_path =
+ case params[:destination]
+ when "tree"
+ namespace_project_tree_path(@project.namespace, @project, @id)
+ when "blob"
+ namespace_project_blob_path(@project.namespace, @project, @id)
+ when "graph"
+ namespace_project_network_path(@project.namespace, @project, @id, @options)
+ when "graphs"
+ namespace_project_graph_path(@project.namespace, @project, @id)
+ when "graphs_commits"
+ commits_namespace_project_graph_path(@project.namespace, @project, @id)
+ else
+ namespace_project_commits_path(@project.namespace, @project, @id)
+ end
redirect_to new_path
end
diff --git a/app/controllers/projects/services_controller.rb b/app/controllers/projects/services_controller.rb
index dc18bbd8d5b..1e435be8275 100644
--- a/app/controllers/projects/services_controller.rb
+++ b/app/controllers/projects/services_controller.rb
@@ -7,7 +7,8 @@ class Projects::ServicesController < Projects::ApplicationController
:colorize_messages, :channels,
:push_events, :issues_events, :merge_requests_events, :tag_push_events,
:note_events, :send_from_committer_email, :disable_diffs, :external_wiki_url,
- :notify, :color]
+ :notify, :color,
+ :server_host, :server_port, :default_irc_uri]
# Authorize
before_action :authorize_admin_project!
before_action :service, only: [:edit, :update, :test]
diff --git a/app/controllers/projects/snippets_controller.rb b/app/controllers/projects/snippets_controller.rb
index 3d75abcc29d..64306637423 100644
--- a/app/controllers/projects/snippets_controller.rb
+++ b/app/controllers/projects/snippets_controller.rb
@@ -6,10 +6,10 @@ class Projects::SnippetsController < Projects::ApplicationController
before_action :authorize_read_project_snippet!
# Allow write(create) snippet
- before_action :authorize_write_project_snippet!, only: [:new, :create]
+ before_action :authorize_create_project_snippet!, only: [:new, :create]
# Allow modify snippet
- before_action :authorize_modify_project_snippet!, only: [:edit, :update]
+ before_action :authorize_update_project_snippet!, only: [:edit, :update]
# Allow destroy snippet
before_action :authorize_admin_project_snippet!, only: [:destroy]
@@ -75,8 +75,8 @@ class Projects::SnippetsController < Projects::ApplicationController
@snippet ||= @project.snippets.find(params[:id])
end
- def authorize_modify_project_snippet!
- return render_404 unless can?(current_user, :modify_project_snippet, @snippet)
+ def authorize_update_project_snippet!
+ return render_404 unless can?(current_user, :update_project_snippet, @snippet)
end
def authorize_admin_project_snippet!
diff --git a/app/controllers/projects/wikis_controller.rb b/app/controllers/projects/wikis_controller.rb
index 36ef86e1909..50512cb6dc3 100644
--- a/app/controllers/projects/wikis_controller.rb
+++ b/app/controllers/projects/wikis_controller.rb
@@ -2,7 +2,7 @@ require 'project_wiki'
class Projects::WikisController < Projects::ApplicationController
before_action :authorize_read_wiki!
- before_action :authorize_write_wiki!, only: [:edit, :create, :history]
+ before_action :authorize_create_wiki!, only: [:edit, :create, :history]
before_action :authorize_admin_wiki!, only: :destroy
before_action :load_project_wiki
include WikiHelper
@@ -28,7 +28,7 @@ class Projects::WikisController < Projects::ApplicationController
)
end
else
- return render('empty') unless can?(current_user, :write_wiki, @project)
+ return render('empty') unless can?(current_user, :create_wiki, @project)
@page = WikiPage.new(@project_wiki)
@page.title = params[:id]
@@ -43,7 +43,7 @@ class Projects::WikisController < Projects::ApplicationController
def update
@page = @project_wiki.find_page(params[:id])
- return render('empty') unless can?(current_user, :write_wiki, @project)
+ return render('empty') unless can?(current_user, :create_wiki, @project)
if @page.update(content, format, message)
redirect_to(
diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb
index be5968cd7b0..b191819a117 100644
--- a/app/controllers/projects_controller.rb
+++ b/app/controllers/projects_controller.rb
@@ -6,7 +6,7 @@ class ProjectsController < ApplicationController
# Authorize
before_action :authorize_admin_project!, only: [:edit, :update, :destroy, :transfer, :archive, :unarchive]
- before_action :event_filter, only: :show
+ before_action :event_filter, only: [:show, :activity]
layout :determine_layout
@@ -52,10 +52,21 @@ class ProjectsController < ApplicationController
end
def transfer
- 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
+ namespace = Namespace.find_by(id: params[:new_namespace_id])
+ ::Projects::TransferService.new(project, current_user).execute(namespace)
+
+ if @project.errors[:new_namespace].present?
+ flash[:alert] = @project.errors[:new_namespace].first
+ end
+ end
+
+ def activity
+ respond_to do |format|
+ format.html
+ format.json do
+ load_events
+ pager_json('events/_events', @events.count)
+ end
end
end
@@ -65,15 +76,12 @@ class ProjectsController < ApplicationController
return
end
- @show_star = !(current_user && current_user.starred?(@project))
-
respond_to do |format|
format.html do
if @project.repository_exists?
if @project.empty_repo?
render 'projects/empty'
else
- @last_push = current_user.recent_push(@project.id) if current_user
render :show
end
else
@@ -81,11 +89,6 @@ class ProjectsController < ApplicationController
end
end
- format.json do
- load_events
- pager_json('events/_events', @events.count)
- end
-
format.atom do
load_events
render layout: false
@@ -147,11 +150,14 @@ class ProjectsController < ApplicationController
def toggle_star
current_user.toggle_star(@project)
@project.reload
- render json: { star_count: @project.star_count }
+
+ render json: {
+ html: view_to_html_string("projects/buttons/_star")
+ }
end
def markdown_preview
- text = params[:text]
+ text = params[:text]
ext = Gitlab::ReferenceExtractor.new(@project, current_user)
ext.analyze(text)
diff --git a/app/controllers/registrations_controller.rb b/app/controllers/registrations_controller.rb
index 6ccc7934f2f..3b3dc86cb68 100644
--- a/app/controllers/registrations_controller.rb
+++ b/app/controllers/registrations_controller.rb
@@ -6,7 +6,7 @@ class RegistrationsController < Devise::RegistrationsController
end
def destroy
- DeleteUserService.new.execute(current_user)
+ DeleteUserService.new(current_user).execute(current_user)
respond_to do |format|
format.html { redirect_to new_user_session_path, notice: "Account successfully removed." }
diff --git a/app/controllers/sessions_controller.rb b/app/controllers/sessions_controller.rb
index 4d976fe6630..89629bc0581 100644
--- a/app/controllers/sessions_controller.rb
+++ b/app/controllers/sessions_controller.rb
@@ -37,6 +37,8 @@ class SessionsController < Devise::SessionsController
resource.update_attributes(reset_password_token: nil,
reset_password_sent_at: nil)
end
+ authenticated_with = user_params[:otp_attempt] ? "two-factor" : "standard"
+ log_audit_event(current_user, with: authenticated_with)
end
end
@@ -57,7 +59,7 @@ class SessionsController < Devise::SessionsController
def authenticate_with_two_factor
user = self.resource = find_user
- return unless user && user.otp_required_for_login
+ return unless user && user.two_factor_enabled?
if user_params[:otp_attempt].present? && session[:otp_user_id]
if valid_otp_attempt?(user)
@@ -95,4 +97,9 @@ class SessionsController < Devise::SessionsController
user.valid_otp?(user_params[:otp_attempt]) ||
user.invalidate_otp_backup_code!(user_params[:otp_attempt])
end
+
+ def log_audit_event(user, options = {})
+ AuditEventService.new(user, user, options).
+ for_authentication.security_event
+ end
end
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index cf672c5c093..8e7e45c781f 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -2,7 +2,7 @@ class SnippetsController < ApplicationController
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
# Allow modify snippet
- before_action :authorize_modify_snippet!, only: [:edit, :update]
+ before_action :authorize_update_snippet!, only: [:edit, :update]
# Allow destroy snippet
before_action :authorize_admin_snippet!, only: [:destroy]
@@ -87,8 +87,8 @@ class SnippetsController < ApplicationController
end
end
- def authorize_modify_snippet!
- return render_404 unless can?(current_user, :modify_personal_snippet, @snippet)
+ def authorize_update_snippet!
+ return render_404 unless can?(current_user, :update_personal_snippet, @snippet)
end
def authorize_admin_snippet!
diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb
index 0bed2115dc7..ab89aa2c53a 100644
--- a/app/finders/issuable_finder.rb
+++ b/app/finders/issuable_finder.rb
@@ -10,7 +10,7 @@
# state: 'open' or 'closed' or 'all'
# group_id: integer
# project_id: integer
-# milestone_id: integer
+# milestone_title: string
# assignee_id: integer
# search: string
# label_name: string
@@ -45,10 +45,10 @@ class IssuableFinder
def group
return @group if defined?(@group)
- @group =
+ @group =
if params[:group_id].present?
Group.find(params[:group_id])
- else
+ else
nil
end
end
@@ -56,10 +56,10 @@ class IssuableFinder
def project
return @project if defined?(@project)
- @project =
+ @project =
if params[:project_id].present?
Project.find(params[:project_id])
- else
+ else
nil
end
end
@@ -76,7 +76,7 @@ class IssuableFinder
return @milestones if defined?(@milestones)
@milestones =
- if milestones? && params[:milestone_title] != NONE
+ if milestones? && params[:milestone_title] != Milestone::None.title
Milestone.where(title: params[:milestone_title])
else
nil
@@ -90,7 +90,7 @@ class IssuableFinder
def assignee
return @assignee if defined?(@assignee)
- @assignee =
+ @assignee =
if assignee? && params[:assignee_id] != NONE
User.find(params[:assignee_id])
else
@@ -105,7 +105,7 @@ class IssuableFinder
def author
return @author if defined?(@author)
- @author =
+ @author =
if author? && params[:author_id] != NONE
User.find(params[:author_id])
else
@@ -148,8 +148,6 @@ class IssuableFinder
case params[:state]
when 'closed'
items.closed
- when 'rejected'
- items.respond_to?(:rejected) ? items.rejected : items.closed
when 'merged'
items.respond_to?(:merged) ? items.merged : items.closed
when 'all'
diff --git a/app/helpers/appearances_helper.rb b/app/helpers/appearances_helper.rb
index bb8d5683807..14df8d4cbd7 100644
--- a/app/helpers/appearances_helper.rb
+++ b/app/helpers/appearances_helper.rb
@@ -16,6 +16,6 @@ module AppearancesHelper
end
def brand_header_logo
- image_tag 'logo-white.png'
+ image_tag 'logo.svg'
end
end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
index 10d7aa11209..a803b66c502 100644
--- a/app/helpers/application_helper.rb
+++ b/app/helpers/application_helper.rb
@@ -179,14 +179,33 @@ module ApplicationHelper
BroadcastMessage.current
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.in_time_zone.stamp('Aug 21, 2011 9:23pm'),
- data: { toggle: 'tooltip', placement: placement }
-
- haml_tag :script, "$('." + html_class + "').timeago().tooltip()"
- end.html_safe
+ # Render a `time` element with Javascript-based relative date and tooltip
+ #
+ # time - Time object
+ # placement - Tooltip placement String (default: "top")
+ # html_class - Custom class for `time` element (default: "time_ago")
+ # skip_js - When true, exclude the `script` tag (default: false)
+ #
+ # By default also includes a `script` element with Javascript necessary to
+ # initialize the `timeago` jQuery extension. If this method is called many
+ # times, for example rendering hundreds of commits, it's advisable to disable
+ # this behavior using the `skip_js` argument and re-initializing `timeago`
+ # manually once all of the elements have been rendered.
+ #
+ # A `js-timeago` class is always added to the element, even when a custom
+ # `html_class` argument is provided.
+ #
+ # Returns an HTML-safe String
+ def time_ago_with_tooltip(time, placement: 'top', html_class: 'time_ago', skip_js: false)
+ element = content_tag :time, time.to_s,
+ class: "#{html_class} js-timeago",
+ datetime: time.getutc.iso8601,
+ title: time.in_time_zone.stamp('Aug 21, 2011 9:23pm'),
+ data: { toggle: 'tooltip', placement: placement }
+
+ element += javascript_tag "$('.js-timeago').timeago()" unless skip_js
+
+ element
end
def render_markup(file_name, file_content)
@@ -194,6 +213,10 @@ module ApplicationHelper
Haml::Helpers.preserve(markdown(file_content))
elsif asciidoc?(file_name)
asciidoc(file_content)
+ elsif plain?(file_name)
+ content_tag :pre, class: 'plain-readme' do
+ file_content
+ end
else
GitHub::Markup.render(file_name, file_content).
force_encoding(file_content.encoding).html_safe
@@ -202,6 +225,10 @@ module ApplicationHelper
simple_format(file_content)
end
+ def plain?(filename)
+ Gitlab::MarkupHelper.plain?(filename)
+ end
+
def markup?(filename)
Gitlab::MarkupHelper.markup?(filename)
end
@@ -214,39 +241,6 @@ module ApplicationHelper
Gitlab::MarkupHelper.asciidoc?(filename)
end
- # 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 promo_host
'about.gitlab.com'
end
@@ -295,10 +289,9 @@ module ApplicationHelper
def state_filters_text_for(entity, project)
titles = {
- opened: "Open",
- merged: "Accepted"
+ opened: "Open"
}
-
+
entity_title = titles[entity] || entity.to_s.humanize
count =
diff --git a/app/helpers/application_settings_helper.rb b/app/helpers/application_settings_helper.rb
index 63c3ff5674d..61d14383945 100644
--- a/app/helpers/application_settings_helper.rb
+++ b/app/helpers/application_settings_helper.rb
@@ -28,7 +28,7 @@ module ApplicationSettingsHelper
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 = 'btn'
css_class += ' active' if checked
checkbox_name = 'application_setting[restricted_visibility_levels][]'
diff --git a/app/helpers/broadcast_messages_helper.rb b/app/helpers/broadcast_messages_helper.rb
index 29ff47663da..6484dca6b55 100644
--- a/app/helpers/broadcast_messages_helper.rb
+++ b/app/helpers/broadcast_messages_helper.rb
@@ -1,9 +1,16 @@
module BroadcastMessagesHelper
def broadcast_styling(broadcast_message)
- if(broadcast_message.color || broadcast_message.font)
- "background-color:#{broadcast_message.color};color:#{broadcast_message.font}"
- else
- ""
+ styling = ''
+
+ if broadcast_message.color.present?
+ styling << "background-color: #{broadcast_message.color}"
+ styling << '; ' if broadcast_message.font.present?
end
+
+ if broadcast_message.font.present?
+ styling << "color: #{broadcast_message.font}"
+ end
+
+ styling
end
end
diff --git a/app/helpers/events_helper.rb b/app/helpers/events_helper.rb
index d440da050e1..8428281f8f6 100644
--- a/app/helpers/events_helper.rb
+++ b/app/helpers/events_helper.rb
@@ -189,7 +189,7 @@ module EventsHelper
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:%S%Z")
+ xml.updated event.created_at.xmlschema
xml.media :thumbnail, width: "40", height: "40", url: avatar_icon(event.author_email)
xml.author do |author|
xml.name event.author_name
diff --git a/app/helpers/gitlab_markdown_helper.rb b/app/helpers/gitlab_markdown_helper.rb
index 9aabe01f60e..eb3f72a307d 100644
--- a/app/helpers/gitlab_markdown_helper.rb
+++ b/app/helpers/gitlab_markdown_helper.rb
@@ -98,6 +98,29 @@ module GitlabMarkdownHelper
end
end
+ MARKDOWN_TIPS = [
+ "End a line with two or more spaces for a line-break, or soft-return",
+ "Inline code can be denoted by `surrounding it with backticks`",
+ "Blocks of code can be denoted by three backticks ``` or four leading spaces",
+ "Emoji can be added by :emoji_name:, for example :thumbsup:",
+ "Notify other participants using @user_name",
+ "Notify a specific group using @group_name",
+ "Notify the entire team using @all",
+ "Reference an issue using a hash, for example issue #123",
+ "Reference a merge request using an exclamation point, for example MR !123",
+ "Italicize words or phrases using *asterisks* or _underscores_",
+ "Bold words or phrases using **double asterisks** or __double underscores__",
+ "Strikethrough words or phrases using ~~two tildes~~",
+ "Make a bulleted list using + pluses, - minuses, or * asterisks",
+ "Denote blockquotes using > at the beginning of a line",
+ "Make a horizontal line using three or more hyphens ---, asterisks ***, or underscores ___"
+ ].freeze
+
+ # Returns a random markdown tip for use as a textarea placeholder
+ def random_markdown_tip
+ MARKDOWN_TIPS.sample
+ end
+
private
# Return +text+, truncated to +max_chars+ characters, excluding any HTML
diff --git a/app/helpers/gitlab_routing_helper.rb b/app/helpers/gitlab_routing_helper.rb
index 9703c8d9e9c..d0fae255a04 100644
--- a/app/helpers/gitlab_routing_helper.rb
+++ b/app/helpers/gitlab_routing_helper.rb
@@ -17,6 +17,10 @@ module GitlabRoutingHelper
namespace_project_path(project.namespace, project, *args)
end
+ def activity_project_path(project, *args)
+ activity_namespace_project_path(project.namespace, project, *args)
+ end
+
def edit_project_path(project, *args)
edit_namespace_project_path(project.namespace, project, *args)
end
@@ -52,4 +56,12 @@ module GitlabRoutingHelper
def project_snippet_url(entity, *args)
namespace_project_snippet_url(entity.project.namespace, entity.project, entity, *args)
end
+
+ def toggle_subscription_path(entity, *args)
+ if entity.is_a?(Issue)
+ toggle_subscription_namespace_project_issue_path(entity.project.namespace, entity.project, entity)
+ else
+ toggle_subscription_namespace_project_merge_request_path(entity.project.namespace, entity.project, entity)
+ end
+ end
end
diff --git a/app/helpers/groups_helper.rb b/app/helpers/groups_helper.rb
index 3569ac2af63..b067cb54a43 100644
--- a/app/helpers/groups_helper.rb
+++ b/app/helpers/groups_helper.rb
@@ -19,14 +19,6 @@ module GroupsHelper
end
end
- def group_settings_page?
- if current_controller?('groups')
- current_action?('edit') || current_action?('projects')
- else
- false
- end
- end
-
def group_icon(group)
if group.is_a?(String)
group = Group.find_by(path: group)
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index a730684f8f3..30b17a736a7 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -1,4 +1,6 @@
module IconsHelper
+ include FontAwesome::Rails::IconHelper
+
# Creates an icon tag given icon name(s) and possible icon modifiers.
#
# Right now this method simply delegates directly to `fa_icon` from the
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index 36d3f371c1b..d4c345fe431 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -45,13 +45,13 @@ module IssuesHelper
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')}"
+ ts = time_ago_with_tooltip(issue.created_at, placement: 'bottom', html_class: 'note_created_ago')
if issue.updated_at != issue.created_at
ts << capture_haml do
haml_tag :span do
haml_concat '&middot;'
haml_concat icon('edit', title: 'edited')
- haml_concat time_ago_with_tooltip(issue.updated_at, 'bottom', 'issue_edited_ago')
+ haml_concat time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_edited_ago')
end
end
end
diff --git a/app/helpers/milestones_helper.rb b/app/helpers/milestones_helper.rb
index 93e33ebefd8..132a893e532 100644
--- a/app/helpers/milestones_helper.rb
+++ b/app/helpers/milestones_helper.rb
@@ -29,6 +29,8 @@ module MilestonesHelper
end.active
grouped_milestones = Milestones::GroupService.new(milestones).execute
+ grouped_milestones.unshift(Milestone::None)
+
options_from_collection_for_select(grouped_milestones, 'title', 'title', params[:milestone_title])
end
end
diff --git a/app/helpers/notes_helper.rb b/app/helpers/notes_helper.rb
index a7c1fa0b071..dda9b17d61d 100644
--- a/app/helpers/notes_helper.rb
+++ b/app/helpers/notes_helper.rb
@@ -25,13 +25,13 @@ module NotesHelper
def note_timestamp(note)
# Shows the created at time and the updated at time if different
- ts = "#{time_ago_with_tooltip(note.created_at, 'bottom', 'note_created_ago')}"
+ ts = time_ago_with_tooltip(note.created_at, placement: 'bottom', html_class: 'note_created_ago')
if note.updated_at != note.created_at
ts << capture_haml do
haml_tag :span do
haml_concat '&middot;'
haml_concat icon('edit', title: 'edited')
- haml_concat time_ago_with_tooltip(note.updated_at, 'bottom', 'note_edited_ago')
+ haml_concat time_ago_with_tooltip(note.updated_at, placement: 'bottom', html_class: 'note_edited_ago')
end
end
end
diff --git a/app/helpers/notifications_helper.rb b/app/helpers/notifications_helper.rb
index f771fe761ef..2f8e64c375f 100644
--- a/app/helpers/notifications_helper.rb
+++ b/app/helpers/notifications_helper.rb
@@ -1,4 +1,6 @@
module NotificationsHelper
+ include IconsHelper
+
def notification_icon(notification)
if notification.disabled?
icon('volume-off', class: 'ns-mute')
diff --git a/app/helpers/oauth_helper.rb b/app/helpers/oauth_helper.rb
index 997b91de077..2fdca13ed40 100644
--- a/app/helpers/oauth_helper.rb
+++ b/app/helpers/oauth_helper.rb
@@ -13,7 +13,7 @@ module OauthHelper
def enabled_social_providers
enabled_oauth_providers.select do |name|
- [:twitter, :gitlab, :github, :bitbucket, :google_oauth2].include?(name.to_sym)
+ [:saml, :twitter, :gitlab, :github, :bitbucket, :google_oauth2].include?(name.to_sym)
end
end
diff --git a/app/helpers/preferences_helper.rb b/app/helpers/preferences_helper.rb
index bceff4fd52e..ea774e28ecf 100644
--- a/app/helpers/preferences_helper.rb
+++ b/app/helpers/preferences_helper.rb
@@ -42,6 +42,13 @@ module PreferencesHelper
end
end
+ def project_view_choices
+ [
+ ['Readme (default)', :readme],
+ ['Activity view', :activity]
+ ]
+ end
+
def user_application_theme
theme = Gitlab::Themes.by_id(current_user.try(:theme_id))
theme.css_class
@@ -50,4 +57,9 @@ module PreferencesHelper
def user_color_scheme_class
COLOR_SCHEMES[current_user.try(:color_scheme_id)] if defined?(current_user)
end
+
+ def prefer_readme?
+ !current_user ||
+ current_user.project_view == 'readme'
+ end
end
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index 94ce6646634..3cd52b381bd 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -84,53 +84,6 @@ module ProjectsHelper
@project.milestones.active.order("due_date, title ASC")
end
- def link_to_toggle_star(title, starred)
- cls = 'star-btn btn btn-sm btn-default'
-
- toggle_text =
- if starred
- ' Unstar'
- else
- ' Star'
- end
-
- toggle_html = content_tag('span', class: 'toggle') do
- icon('star') + toggle_text
- end
-
- count_html = content_tag('span', class: 'count') do
- @project.star_count.to_s
- end
-
- link_opts = {
- title: title,
- class: cls,
- method: :post,
- remote: true,
- data: { type: 'json' }
- }
-
- path = toggle_star_namespace_project_path(@project.namespace, @project)
-
- content_tag 'span', class: starred ? 'turn-on' : 'turn-off' do
- 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
@@ -139,6 +92,16 @@ module ProjectsHelper
end
end
+ def can_change_visibility_level?(project, current_user)
+ return false unless can?(current_user, :change_visibility_level, project)
+
+ if project.forked?
+ project.forked_from_project.visibility_level > Gitlab::VisibilityLevel::PRIVATE
+ else
+ true
+ end
+ end
+
private
def get_project_nav_tabs(project, current_user)
@@ -168,8 +131,12 @@ module ProjectsHelper
nav_tabs << :snippets
end
+ if can?(current_user, :read_label, project)
+ nav_tabs << :labels
+ end
+
if can?(current_user, :read_milestone, project)
- nav_tabs << [:milestones, :labels]
+ nav_tabs << :milestones
end
nav_tabs.flatten
@@ -211,7 +178,7 @@ module ProjectsHelper
def project_last_activity(project)
if project.last_activity_at
- time_ago_with_tooltip(project.last_activity_at, 'bottom', 'last_activity_time_ago')
+ time_ago_with_tooltip(project.last_activity_at, placement: 'bottom', html_class: 'last_activity_time_ago')
else
"Never"
end
@@ -285,16 +252,6 @@ module ProjectsHelper
end
end
- def service_field_value(type, value)
- return value unless type == 'password'
-
- if value.present?
- "***********"
- else
- nil
- end
- end
-
def user_max_access_in_project(user, project)
level = project.team.max_member_access(user)
@@ -306,4 +263,35 @@ module ProjectsHelper
def leave_project_message(project)
"Are you sure you want to leave \"#{project.name}\" project?"
end
+
+ def new_readme_path
+ ref = @repository.root_ref if @repository
+ ref ||= 'master'
+
+ namespace_project_new_blob_path(@project.namespace, @project, tree_join(ref), file_name: 'README.md')
+ end
+
+ def last_push_event
+ if current_user
+ current_user.recent_push(@project.id)
+ end
+ end
+
+ def readme_cache_key
+ [@project.id, @project.commit.sha, "readme"].join('-')
+ end
+
+ def round_commit_count(project)
+ count = project.commit_count
+
+ if count > 10000
+ '10000+'
+ elsif count > 5000
+ '5000+'
+ elsif count > 1000
+ '1000+'
+ else
+ count
+ end
+ end
end
diff --git a/app/helpers/submodule_helper.rb b/app/helpers/submodule_helper.rb
index 6def7793dc3..b3f50ceebe4 100644
--- a/app/helpers/submodule_helper.rb
+++ b/app/helpers/submodule_helper.rb
@@ -63,7 +63,7 @@ module SubmoduleHelper
namespace = components.pop.gsub(/^\.\.$/, '')
if namespace.empty?
- namespace = @project.namespace.name
+ namespace = @project.namespace.path
end
[
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index 00d4c7f1051..b52cd23aba2 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -86,4 +86,10 @@ module VisibilityLevelHelper
def default_snippet_visibility
current_application_settings.default_snippet_visibility
end
+
+ def skip_level?(form_model, level)
+ form_model.is_a?(Project) &&
+ form_model.forked? &&
+ !Gitlab::VisibilityLevel.allowed_fork_levels(form_model.forked_from_project.visibility_level).include?(level)
+ end
end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index bcd2adee00b..7dab50d47d4 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -35,6 +35,7 @@ class Ability
:read_project,
:read_wiki,
:read_issue,
+ :read_label,
:read_milestone,
:read_project_snippet,
:read_project_member,
@@ -68,6 +69,7 @@ class Ability
def project_abilities(user, project)
rules = []
key = "/user/#{user.id}/project/#{project.id}"
+
RequestStore.store[key] ||= begin
team = project.team
@@ -144,9 +146,9 @@ class Ability
:read_project_member,
:read_merge_request,
:read_note,
- :write_project,
- :write_issue,
- :write_note
+ :create_project,
+ :create_issue,
+ :create_note
]
end
@@ -154,27 +156,27 @@ class Ability
project_guest_rules + [
:download_code,
:fork_project,
- :write_project_snippet
+ :create_project_snippet,
+ :update_issue,
+ :admin_issue,
+ :admin_label,
]
end
def project_dev_rules
project_report_rules + [
- :write_merge_request,
- :write_wiki,
- :modify_issue,
- :admin_issue,
- :admin_label,
+ :create_merge_request,
+ :create_wiki,
:push_code
]
end
def project_archived_rules
[
- :write_merge_request,
+ :create_merge_request,
:push_code,
:push_code_to_protected_branches,
- :modify_merge_request,
+ :update_merge_request,
:admin_merge_request
]
end
@@ -182,10 +184,8 @@ class Ability
def project_master_rules
project_dev_rules + [
:push_code_to_protected_branches,
- :modify_issue,
- :modify_project_snippet,
- :modify_merge_request,
- :admin_issue,
+ :update_project_snippet,
+ :update_merge_request,
:admin_milestone,
:admin_project_snippet,
:admin_project_member,
@@ -245,30 +245,40 @@ class Ability
rules.flatten
end
- [:issue, :note, :project_snippet, :personal_snippet, :merge_request].each do |name|
+
+ [:issue, :merge_request].each do |name|
define_method "#{name}_abilities" do |user, subject|
- if subject.author == user || user.is_admin?
- rules = [
+ rules = []
+
+ if subject.author == user || (subject.respond_to?(:assignee) && subject.assignee == user)
+ rules += [
:"read_#{name}",
- :"write_#{name}",
- :"modify_#{name}",
- :"admin_#{name}"
+ :"update_#{name}",
]
- rules.push(:change_visibility_level) if subject.is_a?(Snippet)
- rules
- elsif subject.respond_to?(:assignee) && subject.assignee == user
- [
+ end
+
+ rules += project_abilities(user, subject.project)
+ rules
+ end
+ end
+
+ [:note, :project_snippet, :personal_snippet].each do |name|
+ define_method "#{name}_abilities" do |user, subject|
+ rules = []
+
+ if subject.author == user
+ rules += [
:"read_#{name}",
- :"write_#{name}",
- :"modify_#{name}",
+ :"update_#{name}",
+ :"admin_#{name}"
]
- else
- if subject.respond_to?(:project)
- project_abilities(user, subject.project)
- else
- []
- end
end
+
+ if subject.respond_to?(:project) && subject.project
+ rules += project_abilities(user, subject.project)
+ end
+
+ rules
end
end
@@ -277,13 +287,16 @@ class Ability
target_user = subject.user
group = subject.group
can_manage = group_abilities(user, group).include?(:admin_group)
+
if can_manage && (user != target_user)
- rules << :modify_group_member
+ rules << :update_group_member
rules << :destroy_group_member
end
+
if !group.last_owner?(user) && (can_manage || (user == target_user))
rules << :destroy_group_member
end
+
rules
end
@@ -300,8 +313,8 @@ class Ability
def named_abilities(name)
[
:"read_#{name}",
- :"write_#{name}",
- :"modify_#{name}",
+ :"create_#{name}",
+ :"update_#{name}",
:"admin_#{name}"
]
end
diff --git a/app/models/audit_event.rb b/app/models/audit_event.rb
new file mode 100644
index 00000000000..967ffd46db0
--- /dev/null
+++ b/app/models/audit_event.rb
@@ -0,0 +1,19 @@
+class AuditEvent < ActiveRecord::Base
+ serialize :details, Hash
+
+ belongs_to :user, foreign_key: :author_id
+
+ validates :author_id, presence: true
+ validates :entity_id, presence: true
+ validates :entity_type, presence: true
+
+ after_initialize :initialize_details
+
+ def initialize_details
+ self.details = {} if details.nil?
+ end
+
+ def author_name
+ self.user.name
+ end
+end
diff --git a/app/models/commit.rb b/app/models/commit.rb
index 9d721661629..aff329d71fa 100644
--- a/app/models/commit.rb
+++ b/app/models/commit.rb
@@ -157,11 +157,11 @@ class Commit
end
def author
- User.find_for_commit(author_email, author_name)
+ @author ||= User.find_by_any_email(author_email)
end
def committer
- User.find_for_commit(committer_email, committer_name)
+ @committer ||= User.find_by_any_email(committer_email)
end
def notes
diff --git a/app/models/concerns/mentionable.rb b/app/models/concerns/mentionable.rb
index 10c39cb1ece..5b0ae411642 100644
--- a/app/models/concerns/mentionable.rb
+++ b/app/models/concerns/mentionable.rb
@@ -75,26 +75,40 @@ module Mentionable
refs.reject! { |ref| without.include?(ref) }
refs.each do |ref|
- Note.create_cross_reference_note(ref, local_reference, a)
+ SystemNoteService.cross_reference(ref, local_reference, a)
end
end
- # 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)
- ch = changed_attributes
- original, mentionable_changed = "", false
- self.class.mentionable_attrs.each do |attr|
- if ch[attr]
- original << ch[attr]
- mentionable_changed = true
- end
- end
+ # When a mentionable field is changed, creates cross-reference notes that
+ # don't already exist
+ def create_new_cross_references!(p = project, a = author)
+ changes = detect_mentionable_changes
+
+ return if changes.empty?
- # Only proceed if the saved changes actually include a chance to an attr_mentionable field.
- return unless mentionable_changed
+ original_text = changes.collect { |_, vals| vals.first }.join(' ')
- preexisting = references(p, self.author, original)
+ preexisting = references(p, self.author, original_text)
create_cross_references!(p, a, preexisting)
end
+
+ private
+
+ # Returns a Hash of changed mentionable fields
+ #
+ # Preference is given to the `changes` Hash, but falls back to
+ # `previous_changes` if it's empty (i.e., the changes have already been
+ # persisted).
+ #
+ # See ActiveModel::Dirty.
+ #
+ # Returns a Hash.
+ def detect_mentionable_changes
+ source = (changes.present? ? changes : previous_changes).dup
+
+ mentionable = self.class.mentionable_attrs
+
+ # Only include changed fields that are mentionable
+ source.select { |key, val| mentionable.include?(key) }
+ end
end
diff --git a/app/models/concerns/participable.rb b/app/models/concerns/participable.rb
index 9f667f47e0d..7c9597333dd 100644
--- a/app/models/concerns/participable.rb
+++ b/app/models/concerns/participable.rb
@@ -14,7 +14,7 @@
#
# participant :author, :assignee, :mentioned_users, :notes
# end
-#
+#
# issue = Issue.last
# users = issue.participants
# # `users` will contain the issue's author, its assignee,
@@ -35,11 +35,13 @@ module Participable
end
end
+ # Be aware that this method makes a lot of sql queries.
+ # Save result into variable if you are going to reuse it inside same request
def participants(current_user = self.author, project = self.project)
participants = self.class.participant_attrs.flat_map do |attr|
meth = method(attr)
- value =
+ value =
if meth.arity == 1 || meth.arity == -1
meth.call(current_user)
else
@@ -59,7 +61,7 @@ module Participable
end
private
-
+
def participants_for(value, current_user = nil, project = nil)
case value
when User
diff --git a/app/models/event.rb b/app/models/event.rb
index c9a88ffa8e0..78f16c6304e 100644
--- a/app/models/event.rb
+++ b/app/models/event.rb
@@ -44,7 +44,7 @@ class Event < ActiveRecord::Base
after_create :reset_project_activity
# Scopes
- scope :recent, -> { order("created_at DESC") }
+ 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) }
diff --git a/app/models/identity.rb b/app/models/identity.rb
index 756d19adec7..ad60154be71 100644
--- a/app/models/identity.rb
+++ b/app/models/identity.rb
@@ -14,6 +14,7 @@ class Identity < ActiveRecord::Base
include Sortable
belongs_to :user
+ validates :provider, presence: true
validates :extern_uid, allow_blank: true, uniqueness: { scope: :provider }
validates :user_id, uniqueness: { scope: :provider }
end
diff --git a/app/models/key.rb b/app/models/key.rb
index bbc28678177..2dcae059c0e 100644
--- a/app/models/key.rb
+++ b/app/models/key.rb
@@ -24,6 +24,7 @@ class Key < ActiveRecord::Base
validates :title, presence: true, length: { within: 0..255 }
validates :key, presence: true, length: { within: 0..5000 }, format: { with: /\A(ssh|ecdsa)-.*\Z/ }, uniqueness: true
+ validates :key, format: { without: /\n|\r/, message: 'should be a single line' }
validates :fingerprint, uniqueness: true, presence: { message: 'cannot be generated' }
delegate :name, :email, to: :user, prefix: true
diff --git a/app/models/merge_request.rb b/app/models/merge_request.rb
index 487d62e65b6..53b3fc10ccb 100644
--- a/app/models/merge_request.rb
+++ b/app/models/merge_request.rb
@@ -125,16 +125,14 @@ 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 :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) }
scope :by_milestone, ->(milestone) { where(milestone_id: milestone) }
scope :in_projects, ->(project_ids) { where("source_project_id in (:project_ids) OR target_project_id in (:project_ids)", project_ids: project_ids) }
scope :of_projects, ->(ids) { where(target_project_id: ids) }
- # Closed scope for merge request should return
- # both merged and closed mr's
- scope :closed, -> { with_states(:closed, :merged) }
- scope :rejected, -> { with_states(:closed) }
+ scope :merged, -> { with_state(:merged) }
+ scope :closed, -> { with_state(:closed) }
+ scope :closed_and_merged, -> { with_states(:closed, :merged) }
def self.reference_prefix
'!'
@@ -207,7 +205,14 @@ class MergeRequest < ActiveRecord::Base
end
def check_if_can_be_merged
- if Gitlab::Satellite::MergeAction.new(self.author, self).can_be_merged?
+ can_be_merged =
+ if for_fork?
+ Gitlab::Satellite::MergeAction.new(self.author, self).can_be_merged?
+ else
+ project.repository.can_be_merged?(source_branch, target_branch)
+ end
+
+ if can_be_merged
mark_as_mergeable
else
mark_as_unmergeable
@@ -417,4 +422,14 @@ class MergeRequest < ActiveRecord::Base
def can_be_merged_by?(user)
::Gitlab::GitAccess.new(user, project).can_push_to_branch?(target_branch)
end
+
+ def state_human_name
+ if merged?
+ "Merged"
+ elsif closed?
+ "Closed"
+ else
+ "Open"
+ end
+ end
end
diff --git a/app/models/milestone.rb b/app/models/milestone.rb
index 9c543b37023..d28f3c8d3f9 100644
--- a/app/models/milestone.rb
+++ b/app/models/milestone.rb
@@ -14,6 +14,10 @@
#
class Milestone < ActiveRecord::Base
+ # Represents a "No Milestone" state used for filtering Issues and Merge
+ # Requests that have no milestone assigned.
+ None = Struct.new(:title).new('No Milestone')
+
include InternalId
include Sortable
@@ -56,7 +60,7 @@ class Milestone < ActiveRecord::Base
end
def closed_items_count
- self.issues.closed.count + self.merge_requests.closed.count
+ self.issues.closed.count + self.merge_requests.closed_and_merged.count
end
def total_items_count
diff --git a/app/models/note.rb b/app/models/note.rb
index 6a74d62b715..62567f471dc 100644
--- a/app/models/note.rb
+++ b/app/models/note.rb
@@ -63,11 +63,6 @@ class Note < ActiveRecord::Base
after_update :set_references
class << self
- # TODO (rspeicher): Update usages
- def create_cross_reference_note(*args)
- SystemNoteService.cross_reference(*args)
- end
-
def discussions_from_notes(notes)
discussion_ids = []
discussions = []
@@ -361,7 +356,7 @@ class Note < ActiveRecord::Base
end
def set_references
- notice_added_references(project, author)
+ create_new_cross_references!(project, author)
end
def editable?
diff --git a/app/models/project.rb b/app/models/project.rb
index b161cbe86b9..ff372ea9aa5 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -683,6 +683,10 @@ class Project < ActiveRecord::Base
update_attribute(:repository_size, repository.size)
end
+ def update_commit_count
+ update_attribute(:commit_count, repository.commit_count)
+ end
+
def forks_count
ForkedProjectLink.where(forked_from_project_id: self.id).count
end
diff --git a/app/models/project_services/gitlab_ci_service.rb b/app/models/project_services/gitlab_ci_service.rb
index 19b5859d5c9..5aaa4e85cbc 100644
--- a/app/models/project_services/gitlab_ci_service.rb
+++ b/app/models/project_services/gitlab_ci_service.rb
@@ -22,8 +22,12 @@ 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?
+ validates :project_url,
+ presence: true,
+ format: { with: /\A#{URI.regexp(%w(http https))}\z/, message: "should be a valid url" }, if: :activated?
+ validates :token,
+ presence: true,
+ format: { with: /\A([A-Za-z0-9]+)\z/ }, if: :activated?
after_save :compose_service_hook, if: :activated?
@@ -76,6 +80,7 @@ class GitlabCiService < CiService
params = {
id: new_project.id,
name_with_namespace: new_project.name_with_namespace,
+ path_with_namespace: new_project.path_with_namespace,
web_url: new_project.web_url,
default_branch: new_project.default_branch,
ssh_url_to_repo: new_project.ssh_url_to_repo
diff --git a/app/models/project_services/irker_service.rb b/app/models/project_services/irker_service.rb
index 89f312e8c98..d24aa317cf3 100644
--- a/app/models/project_services/irker_service.rb
+++ b/app/models/project_services/irker_service.rb
@@ -21,26 +21,11 @@
require 'uri'
class IrkerService < Service
+ prop_accessor :server_host, :server_port, :default_irc_uri
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)'
@@ -51,20 +36,6 @@ class IrkerService < Service
'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
@@ -77,30 +48,45 @@ class IrkerService < Service
return unless supported_events.include?(data[:object_kind])
IrkerWorker.perform_async(project_id, channels,
- colorize_messages, data, @settings)
+ colorize_messages, data, settings)
+ end
+
+ def settings
+ { server_host: server_host.present? ? server_host : 'localhost',
+ server_port: server_port.present? ? server_port : 6659
+ }
end
def fields
[
+ { type: 'text', name: 'server_host', placeholder: 'localhost',
+ help: 'Irker daemon hostname (defaults to localhost)' },
+ { type: 'text', name: 'server_port', placeholder: 6659,
+ help: 'Irker daemon port (defaults to 6659)' },
+ { type: 'text', name: 'default_irc_uri', title: 'Default IRC URI',
+ help: 'A default IRC URI to prepend before each recipient (optional)',
+ placeholder: 'irc://irc.network.net:6697/' },
{ type: 'textarea', name: 'recipients',
- placeholder: 'Recipients/channels separated by whitespaces' },
+ placeholder: 'Recipients/channels separated by whitespaces',
+ help: '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. Note that if you specify a ' \
+ ' default IRC URI to prepend before each recipient, you can just give ' \
+ ' a channel name.' },
{ 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
+ def help
+ ' NOTE: Irker does NOT have built-in authentication, which makes it' \
+ ' vulnerable to spamming IRC channels if it is hosted outside of a ' \
+ ' firewall. Please make sure you run the daemon within a secured network ' \
+ ' to prevent abuse. For more details, read: http://www.catb.org/~esr/irker/security.html.'
end
- def max_chans
- @settings[:max_channels]
- end
+ private
def get_channels
return true unless :activated?
@@ -114,40 +100,35 @@ class IrkerService < Service
def map_recipients
self.channels = recipients.split(/\s+/).map do |recipient|
- format_channel default_irc_uri, recipient
+ format_channel(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
+ def format_channel(recipient)
+ uri = nil
# Try to parse the chan as a full URI
begin
- uri = URI.parse(recipient)
- raise URI::InvalidURIError if uri.scheme.nil? && cnt == 0
+ uri = consider_uri(URI.parse(recipient))
rescue URI::InvalidURIError
- unless default_irc.nil?
- cnt += 1
- recipient = "#{default_irc}#{recipient}"
- retry if cnt == 1
+ end
+
+ unless uri.present? and default_irc_uri.nil?
+ begin
+ new_recipient = URI.join(default_irc_uri, '/', recipient).to_s
+ uri = consider_uri(URI.parse(new_recipient))
+ rescue
+ Rails.logger.error("Unable to create a valid URL from #{default_irc_uri} and #{recipient}")
end
- else
- url = consider_uri uri
end
- url
+
+ uri
end
def consider_uri(uri)
+ return nil if uri.scheme.nil?
+
# 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/
diff --git a/app/models/repository.rb b/app/models/repository.rb
index 2c6347222aa..1d208aa71c4 100644
--- a/app/models/repository.rb
+++ b/app/models/repository.rb
@@ -5,8 +5,13 @@ class Repository
def initialize(path_with_namespace, default_branch = nil, project = nil)
@path_with_namespace = path_with_namespace
- @raw_repository = Gitlab::Git::Repository.new(path_to_repo) if path_with_namespace
@project = project
+
+ if path_with_namespace
+ @raw_repository = Gitlab::Git::Repository.new(path_to_repo)
+ @raw_repository.autocrlf = :input
+ end
+
rescue Gitlab::Git::Repository::NoRepository
nil
end
@@ -89,18 +94,6 @@ class Repository
gitlab_shell.rm_tag(path_with_namespace, tag_name)
end
- def round_commit_count
- if commit_count > 10000
- '10000+'
- elsif commit_count > 5000
- '5000+'
- elsif commit_count > 1000
- '1000+'
- else
- commit_count
- end
- end
-
def branch_names
cache.fetch(:branch_names) { raw_repository.branch_names }
end
@@ -125,10 +118,29 @@ class Repository
cache.fetch(:size) { raw_repository.size }
end
- def expire_cache
+ def cache_keys
%i(size branch_names tag_names commit_count graph_log
- readme version contribution_guide changelog license).each do |key|
+ readme version contribution_guide changelog license)
+ end
+
+ def build_cache
+ cache_keys.each do |key|
+ unless cache.exist?(key)
+ send(key)
+ end
+ end
+ end
+
+ def expire_cache
+ cache_keys.each do |key|
+ cache.expire(key)
+ end
+ end
+
+ def rebuild_cache
+ cache_keys.each do |key|
cache.expire(key)
+ send(key)
end
end
@@ -168,7 +180,9 @@ class Repository
end
def blob_at(sha, path)
- Gitlab::Git::Blob.find(self, sha, path)
+ unless Gitlab::Git.blank_ref?(sha)
+ Gitlab::Git::Blob.find(self, sha, path)
+ end
end
def blob_by_oid(oid)
@@ -407,8 +421,6 @@ class Repository
Gitlab::Git::Blob.remove(raw_repository, options)
end
- private
-
def user_to_comitter(user)
{
email: user.email,
@@ -417,6 +429,50 @@ class Repository
}
end
+ def can_be_merged?(source_branch, target_branch)
+ our_commit = rugged.branches[target_branch].target
+ their_commit = rugged.branches[source_branch].target
+
+ if our_commit && their_commit
+ !rugged.merge_commits(our_commit, their_commit).conflicts?
+ end
+ end
+
+ def search_files(query, ref)
+ offset = 2
+ args = %W(git grep -i -n --before-context #{offset} --after-context #{offset} #{query} #{ref || root_ref})
+ Gitlab::Popen.popen(args, path_to_repo).first.scrub.split(/^--$/)
+ end
+
+ def parse_search_result(result)
+ ref = nil
+ filename = nil
+ startline = 0
+
+ result.each_line.each_with_index do |line, index|
+ if line =~ /^.*:.*:\d+:/
+ ref, filename, startline = line.split(':')
+ startline = startline.to_i - index
+ break
+ end
+ end
+
+ data = ""
+
+ result.each_line do |line|
+ data << line.sub(ref, '').sub(filename, '').sub(/^:-\d+-/, '').sub(/^::\d+:/, '')
+ end
+
+ OpenStruct.new(
+ filename: filename,
+ ref: ref,
+ startline: startline,
+ data: data
+ )
+ end
+
+ private
+
def cache
@cache ||= RepositoryCache.new(path_with_namespace)
end
diff --git a/app/models/security_event.rb b/app/models/security_event.rb
new file mode 100644
index 00000000000..d131c11cb6c
--- /dev/null
+++ b/app/models/security_event.rb
@@ -0,0 +1,2 @@
+class SecurityEvent < AuditEvent
+end
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 3ab9e834c63..b0831982aa7 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -34,7 +34,6 @@ 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 },
format: { with: Gitlab::Regex.file_name_regex,
message: Gitlab::Regex.file_name_regex_message }
diff --git a/app/models/user.rb b/app/models/user.rb
index 982c05212ce..fb330ff7185 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -50,12 +50,12 @@
# bitbucket_access_token :string(255)
# bitbucket_access_token_secret :string(255)
# location :string(255)
-# public_email :string(255) default(""), not null
# encrypted_otp_secret :string(255)
# encrypted_otp_secret_iv :string(255)
# encrypted_otp_secret_salt :string(255)
-# otp_required_for_login :boolean
+# otp_required_for_login :boolean default(FALSE), not null
# otp_backup_codes :text
+# public_email :string(255) default(""), not null
# dashboard :integer default(0)
#
@@ -80,6 +80,7 @@ class User < ActiveRecord::Base
devise :two_factor_authenticatable,
otp_secret_encryption_key: File.read(Rails.root.join('.secret')).chomp
+ alias_attribute :two_factor_enabled, :otp_required_for_login
devise :two_factor_backupable, otp_number_of_backup_codes: 10
serialize :otp_backup_codes, JSON
@@ -172,6 +173,13 @@ class User < ActiveRecord::Base
after_create :post_create_hook
after_destroy :post_destroy_hook
+ # User's Dashboard preference
+ # Note: When adding an option, it MUST go on the end of the array.
+ enum dashboard: [:projects, :stars]
+
+ # User's Project preference
+ # Note: When adding an option, it MUST go on the end of the array.
+ enum project_view: [:readme, :activity]
alias_attribute :private_token, :authentication_token
@@ -190,11 +198,13 @@ class User < ActiveRecord::Base
mount_uploader :avatar, AvatarUploader
# Scopes
- scope :admins, -> { where(admin: true) }
+ scope :admins, -> { where(admin: true) }
scope :blocked, -> { with_state(:blocked) }
scope :active, -> { with_state(:active) }
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 members)') }
+ scope :with_two_factor, -> { where(two_factor_enabled: true) }
+ scope :without_two_factor, -> { where(two_factor_enabled: false) }
#
# Class methods
@@ -219,18 +229,37 @@ class User < ActiveRecord::Base
end
end
- def find_for_commit(email, name)
- # Prefer email match over name match
- User.where(email: email).first ||
- User.joins(:emails).where(emails: { email: email }).first ||
- User.where(name: name).first
+ # Find a User by their primary email or any associated secondary email
+ def find_by_any_email(email)
+ user_table = arel_table
+ email_table = Email.arel_table
+
+ # Use ARel to build a query:
+ query = user_table.
+ # SELECT "users".* FROM "users"
+ project(user_table[Arel.star]).
+ # LEFT OUTER JOIN "emails"
+ join(email_table, Arel::Nodes::OuterJoin).
+ # ON "users"."id" = "emails"."user_id"
+ on(user_table[:id].eq(email_table[:user_id])).
+ # WHERE ("user"."email" = '<email>' OR "emails"."email" = '<email>')
+ where(user_table[:email].eq(email).or(email_table[:email].eq(email)))
+
+ find_by_sql(query.to_sql).first
end
def filter(filter_name)
case filter_name
- when "admins"; self.admins
- when "blocked"; self.blocked
- when "wop"; self.without_projects
+ when 'admins'
+ self.admins
+ when 'blocked'
+ self.blocked
+ when 'two_factor_disabled'
+ self.without_two_factor
+ when 'two_factor_enabled'
+ self.with_two_factor
+ when 'wop'
+ self.without_projects
else
self.active
end
@@ -297,6 +326,16 @@ class User < ActiveRecord::Base
@reset_token
end
+ def disable_two_factor!
+ update_attributes(
+ two_factor_enabled: false,
+ encrypted_otp_secret: nil,
+ encrypted_otp_secret_iv: nil,
+ encrypted_otp_secret_salt: nil,
+ otp_backup_codes: nil
+ )
+ end
+
def namespace_uniq
namespace_name = self.username
existing_namespace = Namespace.by_path(namespace_name)
@@ -322,6 +361,8 @@ class User < ActiveRecord::Base
end
def owns_public_email
+ return if self.public_email.blank?
+
self.errors.add(:public_email, "is not an email you own") unless self.all_emails.include?(self.public_email)
end
@@ -502,7 +543,7 @@ class User < ActiveRecord::Base
def set_public_email
if self.public_email.blank? || !self.all_emails.include?(self.public_email)
- self.public_email = nil
+ self.public_email = ''
end
end
@@ -704,8 +745,4 @@ class User < ActiveRecord::Base
def can_be_removed?
!solo_owned_groups.present?
end
-
- # User's Dashboard preference
- # Note: When adding an option, it MUST go on the end of the array.
- enum dashboard: [:projects, :stars]
end
diff --git a/app/services/audit_event_service.rb b/app/services/audit_event_service.rb
new file mode 100644
index 00000000000..a7f090655e1
--- /dev/null
+++ b/app/services/audit_event_service.rb
@@ -0,0 +1,25 @@
+class AuditEventService
+ def initialize(author, entity, details = {})
+ @author, @entity, @details = author, entity, details
+ end
+
+ def for_authentication
+ @details = {
+ with: @details[:with],
+ target_id: @author.id,
+ target_type: "User",
+ target_details: @author.name,
+ }
+
+ self
+ end
+
+ def security_event
+ SecurityEvent.create(
+ author_id: @author.id,
+ entity_id: @entity.id,
+ entity_type: @entity.class.name,
+ details: @details
+ )
+ end
+end
diff --git a/app/services/delete_user_service.rb b/app/services/delete_user_service.rb
index 9017a63af3b..e622fd5ea5d 100644
--- a/app/services/delete_user_service.rb
+++ b/app/services/delete_user_service.rb
@@ -1,4 +1,10 @@
class DeleteUserService
+ attr_accessor :current_user
+
+ def initialize(current_user)
+ @current_user = current_user
+ end
+
def execute(user)
if user.solo_owned_groups.present?
user.errors[:base] << 'You must transfer ownership or delete groups before you can remove user'
diff --git a/app/services/git_push_service.rb b/app/services/git_push_service.rb
index 68d3b915fc9..3511392d1d8 100644
--- a/app/services/git_push_service.rb
+++ b/app/services/git_push_service.rb
@@ -21,7 +21,6 @@ class GitPushService
project.ensure_satellite_exists
project.repository.expire_cache
- project.update_repository_size
if push_remove_branch?(ref, newrev)
@push_commits = []
@@ -61,6 +60,7 @@ class GitPushService
EventCreateService.new.push(project, user, @push_data)
project.execute_hooks(@push_data.dup, :push_hooks)
project.execute_services(@push_data.dup, :push_hooks)
+ ProjectCacheWorker.perform_async(project.id)
end
protected
@@ -105,7 +105,7 @@ class GitPushService
author ||= commit_user(commit)
refs.each do |r|
- Note.create_cross_reference_note(r, commit, author)
+ SystemNoteService.cross_reference(r, commit, author)
end
end
end
diff --git a/app/services/git_tag_push_service.rb b/app/services/git_tag_push_service.rb
index 075a6118da2..1cc42b0b0ad 100644
--- a/app/services/git_tag_push_service.rb
+++ b/app/services/git_tag_push_service.rb
@@ -2,15 +2,15 @@ class GitTagPushService
attr_accessor :project, :user, :push_data
def execute(project, user, oldrev, newrev, ref)
- @project, @user = project, user
+ project.repository.expire_cache
+ @project, @user = project, user
@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
+ ProjectCacheWorker.perform_async(project.id)
true
end
diff --git a/app/services/issuable_base_service.rb b/app/services/issuable_base_service.rb
index 1d99223cfe6..f1ef5ca84fe 100644
--- a/app/services/issuable_base_service.rb
+++ b/app/services/issuable_base_service.rb
@@ -26,4 +26,12 @@ class IssuableBaseService < BaseService
issuable, issuable.project, current_user, branch_type,
old_branch, new_branch)
end
+
+ def filter_params
+ unless can?(current_user, :admin_issue, project)
+ params.delete(:milestone_id)
+ params.delete(:label_ids)
+ params.delete(:assignee_id)
+ end
+ end
end
diff --git a/app/services/issues/bulk_update_service.rb b/app/services/issues/bulk_update_service.rb
index eb07413ee94..de8387c4900 100644
--- a/app/services/issues/bulk_update_service.rb
+++ b/app/services/issues/bulk_update_service.rb
@@ -10,7 +10,7 @@ module Issues
issues = Issue.where(id: issues_ids)
issues.each do |issue|
- next unless can?(current_user, :modify_issue, issue)
+ next unless can?(current_user, :update_issue, issue)
Issues::UpdateService.new(issue.project, current_user, issue_params).execute(issue)
end
diff --git a/app/services/issues/create_service.rb b/app/services/issues/create_service.rb
index d5c17906a55..1ea4b72216c 100644
--- a/app/services/issues/create_service.rb
+++ b/app/services/issues/create_service.rb
@@ -1,6 +1,7 @@
module Issues
class CreateService < Issues::BaseService
def execute
+ filter_params
label_params = params[:label_ids]
issue = project.issues.new(params.except(:label_ids))
issue.author = current_user
diff --git a/app/services/issues/update_service.rb b/app/services/issues/update_service.rb
index 6af942a5ca4..eabab65c9b0 100644
--- a/app/services/issues/update_service.rb
+++ b/app/services/issues/update_service.rb
@@ -1,26 +1,20 @@
module Issues
class UpdateService < Issues::BaseService
def execute(issue)
- state = params[:state_event]
-
- case state
+ case params.delete(:state_event)
when 'reopen'
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
params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE
params[:milestone_id] = "" if params[:milestone_id] == IssuableFinder::NONE
+ filter_params
old_labels = issue.labels.to_a
- if params.present? && issue.update_attributes(params.except(:state_event,
- :task_num))
+ if params.present? && issue.update_attributes(params)
issue.reset_events_cache
if issue.labels != old_labels
@@ -41,7 +35,7 @@ module Issues
create_title_change_note(issue, issue.previous_changes['title'].first)
end
- issue.notice_added_references(issue.project, current_user)
+ issue.create_new_cross_references!(issue.project, current_user)
execute_hooks(issue, 'update')
end
diff --git a/app/services/merge_requests/auto_merge_service.rb b/app/services/merge_requests/auto_merge_service.rb
index 378b39bb9d6..df793fc997d 100644
--- a/app/services/merge_requests/auto_merge_service.rb
+++ b/app/services/merge_requests/auto_merge_service.rb
@@ -5,17 +5,20 @@ module MergeRequests
# mark merge request as merged and execute all hooks and notifications
# Called when you do merge via GitLab UI
class AutoMergeService < BaseMergeService
+ attr_reader :merge_request, :commit_message
+
def execute(merge_request, commit_message)
+ @commit_message = commit_message
+ @merge_request = merge_request
+
merge_request.lock_mr
- if Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message)
+ if merge!
merge_request.merge
-
create_merge_event(merge_request, current_user)
create_note(merge_request)
notification_service.merge_mr(merge_request, current_user)
- execute_hooks(merge_request)
-
+ execute_hooks(merge_request, 'merge')
true
else
merge_request.unlock_mr
@@ -26,5 +29,39 @@ module MergeRequests
merge_request.mark_as_unmergeable
false
end
+
+ def merge!
+ if merge_request.for_fork?
+ Gitlab::Satellite::MergeAction.new(current_user, merge_request).merge!(commit_message)
+ else
+ # Merge local branches using rugged instead of satellites
+ if sha = commit
+ after_commit(sha, merge_request.target_branch)
+ end
+ end
+ end
+
+ def commit
+ committer = repository.user_to_comitter(current_user)
+
+ options = {
+ message: commit_message,
+ author: committer,
+ committer: committer
+ }
+
+ repository.merge(merge_request.source_branch, merge_request.target_branch, options)
+ end
+
+ def after_commit(sha, branch)
+ commit = repository.commit(sha)
+ full_ref = 'refs/heads/' + branch
+ old_sha = commit.parent_id || Gitlab::Git::BLANK_SHA
+ GitPushService.new.execute(project, current_user, old_sha, sha, full_ref)
+ end
+
+ def repository
+ project.repository
+ end
end
end
diff --git a/app/services/merge_requests/create_service.rb b/app/services/merge_requests/create_service.rb
index ca8d80f6c0c..f431c5d5534 100644
--- a/app/services/merge_requests/create_service.rb
+++ b/app/services/merge_requests/create_service.rb
@@ -1,6 +1,7 @@
module MergeRequests
class CreateService < MergeRequests::BaseService
def execute
+ filter_params
label_params = params[:label_ids]
merge_request = MergeRequest.new(params.except(:label_ids))
merge_request.source_project = project
diff --git a/app/services/merge_requests/update_service.rb b/app/services/merge_requests/update_service.rb
index 4f6c6cba9a9..589fad16165 100644
--- a/app/services/merge_requests/update_service.rb
+++ b/app/services/merge_requests/update_service.rb
@@ -11,27 +11,20 @@ module MergeRequests
params.except!(:target_project_id)
params.except!(:source_branch)
- state = params[:state_event]
-
- case state
+ case params.delete(:state_event)
when 'reopen'
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
params[:assignee_id] = "" if params[:assignee_id] == IssuableFinder::NONE
params[:milestone_id] = "" if params[:milestone_id] == IssuableFinder::NONE
+ filter_params
old_labels = merge_request.labels.to_a
- if params.present? && merge_request.update_attributes(
- params.except(:state_event, :task_num)
- )
+ if params.present? && merge_request.update_attributes(params)
merge_request.reset_events_cache
if merge_request.labels != old_labels
@@ -66,7 +59,7 @@ module MergeRequests
merge_request.mark_as_unchecked
end
- merge_request.notice_added_references(merge_request.project, current_user)
+ merge_request.create_new_cross_references!(merge_request.project, current_user)
execute_hooks(merge_request, 'update')
end
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 0ff37c41743..482c0444049 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -15,7 +15,7 @@ module Notes
# 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)
+ SystemNoteService.cross_reference(mentioned, note.noteable, note.author)
end
execute_hooks(note)
diff --git a/app/services/notes/update_service.rb b/app/services/notes/update_service.rb
index 45a0db761ec..b5611d46257 100644
--- a/app/services/notes/update_service.rb
+++ b/app/services/notes/update_service.rb
@@ -13,7 +13,7 @@ module Notes
# 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)
+ SystemNoteService.cross_reference(mentioned, note.noteable, note.author)
end
end
end
diff --git a/app/services/projects/transfer_service.rb b/app/services/projects/transfer_service.rb
index 489e03bd5ef..f43c0ef70e9 100644
--- a/app/services/projects/transfer_service.rb
+++ b/app/services/projects/transfer_service.rb
@@ -11,19 +11,16 @@ module Projects
include Gitlab::ShellAdapter
class TransferError < StandardError; end
- def execute
- namespace_id = params[:new_namespace_id]
- namespace = Namespace.find_by(id: namespace_id)
-
- if allowed_transfer?(current_user, project, namespace)
- transfer(project, namespace)
+ def execute(new_namespace)
+ if allowed_transfer?(current_user, project, new_namespace)
+ transfer(project, new_namespace)
else
- project.errors.add(:namespace, 'is invalid')
+ project.errors.add(:new_namespace, 'is invalid')
false
end
rescue Projects::TransferService::TransferError => ex
project.reload
- project.errors.add(:namespace_id, ex.message)
+ project.errors.add(:new_namespace, ex.message)
false
end
diff --git a/app/services/update_snippet_service.rb b/app/services/update_snippet_service.rb
index 9d181c2d2ab..e9328bb7323 100644
--- a/app/services/update_snippet_service.rb
+++ b/app/services/update_snippet_service.rb
@@ -9,9 +9,9 @@ class UpdateSnippetService < BaseService
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)
+ unless Gitlab::VisibilityLevel.allowed_for?(current_user, new_visibility)
deny_visibility_level(snippet, new_visibility)
return snippet
end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index d5a49fc41f4..6bef33c6d7a 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -6,19 +6,36 @@
%p= msg
%fieldset
- %legend Features
+ %legend Visibility and Access Controls
.form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :signup_enabled do
- = f.check_box :signup_enabled
- Signup enabled
+ = 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.project-visibility-level-holder
+ = f.label :default_project_visibility, class: 'control-label col-sm-2'
+ .col-sm-10
+ = render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: 'Project')
+ .form-group.project-visibility-level-holder
+ = f.label :default_snippet_visibility, class: 'control-label col-sm-2'
+ .col-sm-10
+ = render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: 'Snippet')
+ .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
.col-sm-offset-2.col-sm-10
.checkbox
- = f.label :signin_enabled do
- = f.check_box :signin_enabled
- Signin enabled
+ = f.label :version_check_enabled do
+ = f.check_box :version_check_enabled
+ Version check enabled
+
+ %fieldset
+ %legend Account and Limit Settings
.form-group
.col-sm-offset-2.col-sm-10
.checkbox
@@ -33,37 +50,45 @@
Twitter enabled
%span.help-block#twitter_help_block Show users a button to share their newly created public or internal projects on twitter
.form-group
- .col-sm-offset-2.col-sm-10
- .checkbox
- = f.label :version_check_enabled do
- = f.check_box :version_check_enabled
- Version check enabled
- %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'
+ = f.label :max_attachment_size, 'Maximum attachment size (MB)', 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.project-visibility-level-holder
- = f.label :default_project_visibility, class: 'control-label col-sm-2'
+ = f.number_field :max_attachment_size, class: 'form-control'
+ .form-group
+ = f.label :session_expire_delay, 'Session duration (minutes)', class: 'control-label col-sm-2'
.col-sm-10
- = render('shared/visibility_radios', model_method: :default_project_visibility, form: f, selected_level: @application_setting.default_project_visibility, form_model: 'Project')
- .form-group.project-visibility-level-holder
- = f.label :default_snippet_visibility, class: 'control-label col-sm-2'
+ = f.number_field :session_expire_delay, class: 'form-control'
+ %span.help-block#session_expire_delay_help_block GitLab restart is required to apply changes
+ .form-group
+ = f.label :user_oauth_applications, 'User OAuth applications', class: 'control-label col-sm-2'
.col-sm-10
- = render('shared/visibility_radios', model_method: :default_snippet_visibility, form: f, selected_level: @application_setting.default_snippet_visibility, form_model: 'Snippet')
+ .checkbox
+ = f.label :user_oauth_applications do
+ = f.check_box :user_oauth_applications
+ Allow users to register any application to use GitLab as an OAuth provider
+
+ %fieldset
+ %legend Sign-in Restrictions
.form-group
- = f.label :restricted_visibility_levels, class: 'control-label col-sm-2'
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :signup_enabled do
+ = f.check_box :signup_enabled
+ Sign-up enabled
+ .form-group
+ .col-sm-offset-2.col-sm-10
+ .checkbox
+ = f.label :signin_enabled do
+ = f.check_box :signin_enabled
+ Sign-in enabled
+ .form-group
+ = f.label :restricted_signup_domains, 'Restricted domains for sign-ups', 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
+ = f.text_area :restricted_signup_domains_raw, placeholder: 'domain.com', class: 'form-control'
+ .help-block Only users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com
.form-group
= f.label :home_page_url, class: 'control-label col-sm-2'
.col-sm-10
@@ -79,27 +104,6 @@
.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-group
- = f.label :session_expire_delay, 'Session duration (minutes)', class: 'control-label col-sm-2'
- .col-sm-10
- = f.number_field :session_expire_delay, class: 'form-control'
- %span.help-block#session_expire_delay_help_block GitLab restart is required to apply changes
- .form-group
- = f.label :restricted_signup_domains, 'Restricted domains for sign-ups', class: 'control-label col-sm-2'
- .col-sm-10
- = f.text_area :restricted_signup_domains_raw, placeholder: 'domain.com', class: 'form-control'
- .help-block Only users with e-mail addresses that match these domain(s) will be able to sign-up. Wildcards allowed. Use separate lines for multiple entries. Ex: domain.com, *.domain.com
- .form_group
- = f.label :user_oauth_applications, 'User OAuth applications', class: 'control-label col-sm-2'
- .col-sm-10
- .checkbox
- = f.label :user_oauth_applications do
- = f.check_box :user_oauth_applications
- Allow users to register any application to use GitLab as an OAuth provider
.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
index 1632dd8affa..e9c7ca9d5aa 100644
--- a/app/views/admin/application_settings/show.html.haml
+++ b/app/views/admin/application_settings/show.html.haml
@@ -1,4 +1,4 @@
- page_title "Settings"
-%h3.page-title Application settings
+%h3.page-title Settings
%hr
= render 'form'
diff --git a/app/views/admin/deploy_keys/index.html.haml b/app/views/admin/deploy_keys/index.html.haml
index 6405a69fad3..2bf1689cbc6 100644
--- a/app/views/admin/deploy_keys/index.html.haml
+++ b/app/views/admin/deploy_keys/index.html.haml
@@ -16,8 +16,7 @@
- @deploy_keys.each do |deploy_key|
%tr
%td
- = link_to admin_deploy_key_path(deploy_key) do
- %strong= deploy_key.title
+ %strong= deploy_key.title
%td
%code.key-fingerprint= deploy_key.fingerprint
%td
diff --git a/app/views/admin/identities/_form.html.haml b/app/views/admin/identities/_form.html.haml
new file mode 100644
index 00000000000..0525552ebf8
--- /dev/null
+++ b/app/views/admin/identities/_form.html.haml
@@ -0,0 +1,19 @@
+= form_for [:admin, @user, @identity], html: { class: 'form-horizontal fieldset-form' } do |f|
+ - if @identity.errors.any?
+ #error_explanation
+ .alert.alert-danger
+ - @identity.errors.full_messages.each do |msg|
+ %p= msg
+
+ .form-group
+ = f.label :provider, class: 'control-label'
+ .col-sm-10
+ = f.select :provider, Gitlab::OAuth::Provider.names, { allow_blank: false }, class: 'form-control'
+ .form-group
+ = f.label :extern_uid, "Identifier", class: 'control-label'
+ .col-sm-10
+ = f.text_field :extern_uid, class: 'form-control', required: true
+
+ .form-actions
+ = f.submit 'Save changes', class: "btn btn-save"
+
diff --git a/app/views/admin/identities/_identity.html.haml b/app/views/admin/identities/_identity.html.haml
new file mode 100644
index 00000000000..671c4fbc677
--- /dev/null
+++ b/app/views/admin/identities/_identity.html.haml
@@ -0,0 +1,12 @@
+%tr
+ %td
+ = identity.provider
+ %td
+ = identity.extern_uid
+ %td
+ = link_to edit_admin_user_identity_path(@user, identity), class: 'btn btn-xs btn-grouped' do
+ Edit
+ = link_to [:admin, @user, identity], method: :delete,
+ class: 'btn btn-xs btn-danger',
+ data: { confirm: "Are you sure you want to remove this identity?" } do
+ Delete
diff --git a/app/views/admin/identities/edit.html.haml b/app/views/admin/identities/edit.html.haml
new file mode 100644
index 00000000000..515d46b0f29
--- /dev/null
+++ b/app/views/admin/identities/edit.html.haml
@@ -0,0 +1,6 @@
+- page_title "Edit", @identity.provider, "Identities", @user.name, "Users"
+%h3.page-title
+ Edit identity for #{@user.name}
+%hr
+
+= render 'form'
diff --git a/app/views/admin/identities/index.html.haml b/app/views/admin/identities/index.html.haml
new file mode 100644
index 00000000000..ae57e3adc4d
--- /dev/null
+++ b/app/views/admin/identities/index.html.haml
@@ -0,0 +1,13 @@
+- page_title "Identities", @user.name, "Users"
+= render 'admin/users/head'
+
+- if @identities.present?
+ %table.table
+ %thead
+ %tr
+ %th Provider
+ %th Identifier
+ %th
+ = render @identities
+- else
+ %h4 This user has no identities
diff --git a/app/views/admin/users/_head.html.haml b/app/views/admin/users/_head.html.haml
new file mode 100644
index 00000000000..9d5e934c8ba
--- /dev/null
+++ b/app/views/admin/users/_head.html.haml
@@ -0,0 +1,23 @@
+%h3.page-title
+ = @user.name
+ - if @user.blocked?
+ %span.cred (Blocked)
+ - if @user.admin
+ %span.cred (Admin)
+
+ .pull-right
+ = link_to edit_admin_user_path(@user), class: "btn btn-grouped" do
+ %i.fa.fa-pencil-square-o
+ Edit
+%hr
+%ul.nav.nav-tabs
+ = nav_link(path: 'users#show') do
+ = link_to "Account", admin_user_path(@user)
+ = nav_link(path: 'users#groups') do
+ = link_to "Groups", groups_admin_user_path(@user)
+ = nav_link(path: 'users#projects') do
+ = link_to "Projects", projects_admin_user_path(@user)
+ = nav_link(path: 'users#keys') do
+ = link_to "SSH keys", keys_admin_user_path(@user)
+ = nav_link(controller: :identities) do
+ = link_to "Identities", admin_user_identities_path(@user)
diff --git a/app/views/admin/users/groups.html.haml b/app/views/admin/users/groups.html.haml
new file mode 100644
index 00000000000..dbecb7bbfd6
--- /dev/null
+++ b/app/views/admin/users/groups.html.haml
@@ -0,0 +1,19 @@
+- page_title "Groups", @user.name, "Users"
+= render 'admin/users/head'
+
+- if @user.group_members.present?
+ .panel.panel-default
+ .panel-heading Groups:
+ %ul.well-list
+ - @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= 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.
diff --git a/app/views/admin/users/index.html.haml b/app/views/admin/users/index.html.haml
index 45dee86b017..b0d31170704 100644
--- a/app/views/admin/users/index.html.haml
+++ b/app/views/admin/users/index.html.haml
@@ -13,6 +13,14 @@
= link_to admin_users_path(filter: "admins") do
Admins
%small.pull-right= User.admins.count
+ %li.filter-two-factor-enabled{class: "#{'active' if params[:filter] == 'two_factor_enabled'}"}
+ = link_to admin_users_path(filter: 'two_factor_enabled') do
+ 2FA Enabled
+ %small.pull-right= User.with_two_factor.count
+ %li.filter-two-factor-disabled{class: "#{'active' if params[:filter] == 'two_factor_disabled'}"}
+ = link_to admin_users_path(filter: 'two_factor_disabled') do
+ 2FA Disabled
+ %small.pull-right= User.without_two_factor.count
%li{class: "#{'active' if params[:filter] == "blocked"}"}
= link_to admin_users_path(filter: "blocked") do
Blocked
@@ -85,6 +93,8 @@
= link_to 'Unblock', unblock_admin_user_path(user), method: :put, class: "btn btn-xs btn-success"
- else
= link_to 'Block', block_admin_user_path(user), data: {confirm: 'USER WILL BE BLOCKED! Are you sure?'}, method: :put, class: "btn btn-xs btn-warning"
+ - if user.access_locked?
+ = link_to 'Unlock', unlock_admin_user_path(user), method: :put, class: "btn btn-xs btn-success", data: { confirm: 'Are you sure?' }
- if user.can_be_removed?
= 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-xs btn-remove"
= paginate @users, theme: "gitlab"
diff --git a/app/views/admin/users/keys.html.haml b/app/views/admin/users/keys.html.haml
new file mode 100644
index 00000000000..07110717082
--- /dev/null
+++ b/app/views/admin/users/keys.html.haml
@@ -0,0 +1,3 @@
+- page_title "Keys", @user.name, "Users"
+= render 'admin/users/head'
+= render 'profiles/keys/key_table', admin: true
diff --git a/app/views/admin/users/projects.html.haml b/app/views/admin/users/projects.html.haml
new file mode 100644
index 00000000000..0d7a1a25a80
--- /dev/null
+++ b/app/views/admin/users/projects.html.haml
@@ -0,0 +1,43 @@
+- page_title "Projects", @user.name, "Users"
+= render 'admin/users/head'
+
+- if @user.groups.any?
+ .panel.panel-default
+ .panel-heading Group projects
+ %ul.well-list
+ - @user.groups.each do |group|
+ %li
+ %strong= group.name
+ &ndash; access to
+ #{pluralize(group.projects.count, 'project')}
+
+.row
+ .col-md-6
+ - if @personal_projects.present?
+ = render 'users/projects', projects: @personal_projects
+ - else
+ .nothing-here-block This user has no personal projects.
+
+
+ .col-md-6
+ .panel.panel-default
+ .panel-heading Joined projects (#{@joined_projects.count})
+ %ul.well-list
+ - @joined_projects.sort_by(&:name_with_namespace).each do |project|
+ - member = project.team.find_member(@user.id)
+ %li.project_member
+ .list-item-name
+ = link_to admin_namespace_project_path(project.namespace, project), class: dom_class(project) do
+ = project.name_with_namespace
+
+ - if member
+ .pull-right
+ - if member.owner?
+ %span.light Owner
+ - else
+ %span.light= member.human_access
+
+ - 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
+
diff --git a/app/views/admin/users/show.html.haml b/app/views/admin/users/show.html.haml
index f7195ac3326..33730ff05df 100644
--- a/app/views/admin/users/show.html.haml
+++ b/app/views/admin/users/show.html.haml
@@ -1,232 +1,163 @@
- page_title @user.name, "Users"
-%h3.page-title
- User:
- = @user.name
- - if @user.blocked?
- %span.cred (Blocked)
- - if @user.admin
- %span.cred (Admin)
-
- .pull-right
- = link_to edit_admin_user_path(@user), class: "btn btn-grouped" do
- %i.fa.fa-pencil-square-o
- Edit
-%hr
-%ul.nav.nav-tabs
- %li.active
- %a{"data-toggle" => "tab", href: "#account"} Account
- %li
- %a{"data-toggle" => "tab", href: "#profile"} Profile
- %li
- %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
- .row
- .col-md-6
- .panel.panel-default
- .panel-heading
- Account:
- %ul.well-list
- %li
- %span.light Name:
- %strong= @user.name
- %li
- %span.light Username:
- %strong
- = @user.username
- %li
- %span.light Email:
- %strong
- = mail_to @user.email
- - @user.emails.each do |email|
- %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-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:
- %strong
- = @user.can_create_group ? "Yes" : "No"
- %li
- %span.light Personal projects limit:
- %strong
- = @user.projects_limit
- %li
- %span.light Member since:
- %strong
- = @user.created_at.stamp("Nov 12, 2031")
- - if @user.confirmed_at
- %li
- %span.light Confirmed at:
- %strong
- = @user.confirmed_at.stamp("Nov 12, 2031")
+= render 'admin/users/head'
+
+.row
+ .col-md-6
+ .panel.panel-default
+ .panel-heading
+ = @user.name
+ %ul.well-list
+ %li
+ = image_tag avatar_icon(@user.email, 60), class: "avatar s60"
+ %li
+ %span.light Profile page:
+ %strong
+ = link_to user_path(@user) do
+ = @user.username
+ = render 'users/profile', user: @user
+
+ .panel.panel-default
+ .panel-heading
+ Account:
+ %ul.well-list
+ %li
+ %span.light Name:
+ %strong= @user.name
+ %li
+ %span.light Username:
+ %strong
+ = @user.username
+ %li
+ %span.light Email:
+ %strong
+ = mail_to @user.email
+ - @user.emails.each do |email|
+ %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-xs btn btn-remove pull-right", title: 'Remove secondary email', id: "remove_email_#{email.id}" do
+ %i.fa.fa-times
+
+ %li.two-factor-status
+ %span.light Two-factor Authentication:
+ %strong{class: @user.two_factor_enabled? ? 'cgreen' : 'cred'}
+ - if @user.two_factor_enabled?
+ Enabled
+ = link_to 'Disable', disable_two_factor_admin_user_path(@user), data: {confirm: 'Are you sure?'}, method: :patch, class: 'btn btn-xs btn-remove pull-right', title: 'Disable Two-factor Authentication'
- else
- %li
- %span.light Confirmed:
- %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
- - if @user.last_sign_in_at
- = @user.last_sign_in_at.stamp("Nov 12, 2031")
- - else
- never
-
- %li
- %span.light Sign-in count:
- %strong
- = @user.sign_in_count
-
- - if @user.ldap_user?
- %li
- %span.light LDAP uid:
- %strong
- = @user.ldap_identity.extern_uid
-
- - if @user.created_by
- %li
- %span.light Created by:
- %strong
- = link_to @user.created_by.name, [:admin, @user.created_by]
-
- .col-md-6
- - unless @user == current_user
- - if @user.blocked?
- .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
- .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"
-
- .panel.panel-danger
- .panel-heading
- Remove user
- .panel-body
- - if @user.can_be_removed?
- %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
- %br
- = link_to 'Remove user', [:admin, @user], data: { confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-remove"
- - else
- - if @user.solo_owned_groups.present?
- %p
- This user is currently an owner in these groups:
- %strong #{@user.solo_owned_groups.map(&:name).join(', ')}
- %p
- You must transfer ownership or delete these groups before you can delete this user.
+ Disabled
+
+ %li
+ %span.light Can create groups:
+ %strong
+ = @user.can_create_group ? "Yes" : "No"
+ %li
+ %span.light Personal projects limit:
+ %strong
+ = @user.projects_limit
+ %li
+ %span.light Member since:
+ %strong
+ = @user.created_at.stamp("Nov 12, 2031")
+ - if @user.confirmed_at
+ %li
+ %span.light Confirmed at:
+ %strong
+ = @user.confirmed_at.stamp("Nov 12, 2031")
+ - else
+ %li
+ %span.light Confirmed:
+ %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
- #profile.tab-pane
- .row
- .col-md-6
- .panel.panel-default
+ %li
+ %span.light Last sign-in at:
+ %strong
+ - if @user.last_sign_in_at
+ = @user.last_sign_in_at.stamp("Nov 12, 2031")
+ - else
+ never
+
+ %li
+ %span.light Sign-in count:
+ %strong
+ = @user.sign_in_count
+
+ - if @user.ldap_user?
+ %li
+ %span.light LDAP uid:
+ %strong
+ = @user.ldap_identity.extern_uid
+
+ - if @user.created_by
+ %li
+ %span.light Created by:
+ %strong
+ = link_to @user.created_by.name, [:admin, @user.created_by]
+
+ .col-md-6
+ - unless @user == current_user
+ - if @user.blocked?
+ .panel.panel-info
.panel-heading
- = @user.name
- %ul.well-list
- %li
- = image_tag avatar_icon(@user.email, 60), class: "avatar s60"
- %li
- %span.light Profile page:
- %strong
- = link_to user_path(@user) do
- = @user.username
- .col-md-6
- = render 'users/profile', user: @user
-
- #groups.tab-pane
- - if @user.group_members.present?
- .panel.panel-default
- .panel-heading Groups:
- %ul.well-list
- - @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= 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.
-
- #projects.tab-pane
- - if @user.groups.any?
- .panel.panel-default
- .panel-heading Group projects
- %ul.well-list
- - @user.groups.each do |group|
- %li
- %strong= group.name
- &ndash; access to
- #{pluralize(group.projects.count, 'project')}
-
- .row
- .col-md-6
- = render 'users/projects', projects: @personal_projects
-
- .col-md-6
- .panel.panel-default
- .panel-heading Joined projects (#{@joined_projects.count})
- %ul.well-list
- - @joined_projects.sort_by(&:name_with_namespace).each do |project|
- - member = project.team.find_member(@user.id)
- %li.project_member
- .list-item-name
- = link_to admin_namespace_project_path(project.namespace, project), class: dom_class(project) do
- = project.name_with_namespace
-
- - if member
- .pull-right
- - if member.owner?
- %span.light Owner
- - else
- %span.light= member.human_access
-
- - 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
+ 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
+ .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"
+ - if @user.access_locked?
+ .panel.panel-info
+ .panel-heading
+ This account has been locked
+ .panel-body
+ %p This user has been temporarily locked due to excessive number of failed logins. You may manually unlock the account.
+ %br
+ = link_to 'Unlock user', unlock_admin_user_path(@user), method: :put, class: "btn btn-info", data: { confirm: 'Are you sure?' }
+
+ .panel.panel-danger
+ .panel-heading
+ Remove user
+ .panel-body
+ - if @user.can_be_removed?
+ %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
+ %br
+ = link_to 'Remove user', [:admin, @user], data: { confirm: "USER #{@user.name} WILL BE REMOVED! Are you sure?" }, method: :delete, class: "btn btn-remove"
+ - else
+ - if @user.solo_owned_groups.present?
+ %p
+ This user is currently an owner in these groups:
+ %strong #{@user.solo_owned_groups.map(&:name).join(', ')}
+ %p
+ You must transfer ownership or delete these groups before you can delete this user.
diff --git a/app/views/dashboard/issues.html.haml b/app/views/dashboard/issues.html.haml
index 0dd2edbb1bc..94318d1bcf5 100644
--- a/app/views/dashboard/issues.html.haml
+++ b/app/views/dashboard/issues.html.haml
@@ -17,5 +17,5 @@
= link_to issues_dashboard_url(format: :atom, private_token: current_user.private_token), class: 'btn' do
%i.fa.fa-rss
- = render 'shared/issuable_filter', type: :issues
+ = render 'shared/issuable/filter', type: :issues
= render 'shared/issues'
diff --git a/app/views/dashboard/merge_requests.html.haml b/app/views/dashboard/merge_requests.html.haml
index 61d2fbe538c..90611d562b0 100644
--- a/app/views/dashboard/merge_requests.html.haml
+++ b/app/views/dashboard/merge_requests.html.haml
@@ -7,5 +7,5 @@
List all merge requests from all projects you have access to.
%hr
.append-bottom-20
- = render 'shared/issuable_filter', type: :merge_requests
+ = render 'shared/issuable/filter', type: :merge_requests
= render 'shared/merge_requests'
diff --git a/app/views/doorkeeper/authorized_applications/_delete_form.html.haml b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml
index 4bba72167e3..bfa95ce79a7 100644
--- a/app/views/doorkeeper/authorized_applications/_delete_form.html.haml
+++ b/app/views/doorkeeper/authorized_applications/_delete_form.html.haml
@@ -1,4 +1,9 @@
- submit_btn_css ||= 'btn btn-link btn-remove'
-= form_tag oauth_authorized_application_path(application) do
+- if defined?(token)
+ - path = oauth_authorized_application_path(0, token_id: token)
+- else
+ - path = oauth_authorized_application_path(application)
+
+= form_tag path 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
+ = submit_tag 'Revoke', onclick: "return confirm('Are you sure?')", class: 'btn btn-link btn-remove btn-sm'
diff --git a/app/views/events/_event.html.haml b/app/views/events/_event.html.haml
index 02b1dec753c..b8409f64665 100644
--- a/app/views/events/_event.html.haml
+++ b/app/views/events/_event.html.haml
@@ -3,14 +3,16 @@
.event-item-timestamp
#{time_ago_with_tooltip(event.created_at)}
- = 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.commented?
- = render "events/event/note", event: event
- - elsif event.created_project?
+ - if event.created_project?
+ = cache [event, current_user] do
+ = image_tag avatar_icon(event.author_email, 24), class: "avatar s24", alt:''
= render "events/event/created_project", event: event
- - else
- = render "events/event/common", event: event \ No newline at end of file
+ - else
+ = cache event do
+ = image_tag avatar_icon(event.author_email, 24), class: "avatar s24", alt:''
+ - if event.push?
+ = render "events/event/push", event: event
+ - elsif event.commented?
+ = render "events/event/note", event: event
+ - else
+ = render "events/event/common", event: event
diff --git a/app/views/explore/projects/_project.html.haml b/app/views/explore/projects/_project.html.haml
index d65fb529373..d769c91545d 100644
--- a/app/views/explore/projects/_project.html.haml
+++ b/app/views/explore/projects/_project.html.haml
@@ -14,7 +14,7 @@
.repo-info
- unless project.empty_repo?
- = link_to pluralize(project.repository.round_commit_count, 'commit'), namespace_project_commits_path(project.namespace, project, project.default_branch)
+ = link_to pluralize(round_commit_count(project), 'commit'), namespace_project_commits_path(project.namespace, project, project.default_branch)
&middot;
= link_to pluralize(project.repository.branch_names.count, 'branch'), namespace_project_branches_path(project.namespace, project)
&middot;
diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml
index aa13ed85b53..2ff4b7e23ea 100644
--- a/app/views/groups/edit.html.haml
+++ b/app/views/groups/edit.html.haml
@@ -1,4 +1,3 @@
-- page_title "Settings"
.panel.panel-default
.panel-heading
%strong= @group.name
diff --git a/app/views/groups/group_members/_group_member.html.haml b/app/views/groups/group_members/_group_member.html.haml
index ec39a755f0f..b460e0ff59e 100644
--- a/app/views/groups/group_members/_group_member.html.haml
+++ b/app/views/groups/group_members/_group_member.html.haml
@@ -32,7 +32,7 @@
%span.pull-right
%strong= member.human_access
- if show_controls
- - if can?(current_user, :modify_group_member, member)
+ - if can?(current_user, :update_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
diff --git a/app/views/groups/issues.html.haml b/app/views/groups/issues.html.haml
index e0756e909be..f0d90782556 100644
--- a/app/views/groups/issues.html.haml
+++ b/app/views/groups/issues.html.haml
@@ -21,5 +21,5 @@
= link_to issues_group_url(@group, format: :atom, private_token: current_user.private_token), class: 'btn' do
%i.fa.fa-rss
- = render 'shared/issuable_filter', type: :issues
+ = render 'shared/issuable/filter', type: :issues
= render 'shared/issues'
diff --git a/app/views/groups/merge_requests.html.haml b/app/views/groups/merge_requests.html.haml
index 3d9e857cc52..ca85a158707 100644
--- a/app/views/groups/merge_requests.html.haml
+++ b/app/views/groups/merge_requests.html.haml
@@ -10,5 +10,5 @@
To see all merge requests you should visit #{link_to 'dashboard', merge_requests_dashboard_path} page.
%hr
.append-bottom-20
- = render 'shared/issuable_filter', type: :merge_requests
+ = render 'shared/issuable/filter', type: :merge_requests
= render 'shared/merge_requests'
diff --git a/app/views/help/_shortcuts.html.haml b/app/views/help/_shortcuts.html.haml
index 825acb0ae3e..e809d99ba71 100644
--- a/app/views/help/_shortcuts.html.haml
+++ b/app/views/help/_shortcuts.html.haml
@@ -80,6 +80,12 @@
.key g
.key p
%td
+ Go to the project's home page
+ %tr
+ %td.shortcut
+ .key g
+ .key e
+ %td
Go to the project's activity feed
%tr
%td.shortcut
diff --git a/app/views/layouts/_head.html.haml b/app/views/layouts/_head.html.haml
index dbc68c39bf1..54cddc30b74 100644
--- a/app/views/layouts/_head.html.haml
+++ b/app/views/layouts/_head.html.haml
@@ -7,14 +7,29 @@
%title= page_title
= favicon_link_tag 'favicon.ico'
- = stylesheet_link_tag "application", :media => "all"
- = stylesheet_link_tag "print", :media => "print"
+
+ = stylesheet_link_tag "application", media: "all"
+ = stylesheet_link_tag "print", media: "print"
+
= javascript_include_tag "application"
+
= csrf_meta_tags
+
= include_gon
+
%meta{name: 'viewport', content: 'width=device-width, initial-scale=1, maximum-scale=1'}
%meta{name: 'theme-color', content: '#474D57'}
+ -# Apple Safari/iOS home screen icons
+ = favicon_link_tag 'touch-icon-iphone.png', rel: 'apple-touch-icon'
+ = favicon_link_tag 'touch-icon-ipad.png', rel: 'apple-touch-icon', sizes: '76x76'
+ = favicon_link_tag 'touch-icon-iphone-retina.png', rel: 'apple-touch-icon', sizes: '120x120'
+ = favicon_link_tag 'touch-icon-ipad-retina.png', rel: 'apple-touch-icon', sizes: '152x152'
+
+ -# Windows 8 pinned site tile
+ %meta{name: 'msapplication-TileImage', content: image_url('msapplication-tile.png')}
+ %meta{name: 'msapplication-TileColor', content: '#30353E'}
+
= yield :meta_tags
= render 'layouts/google_analytics' if extra_config.has_key?('google_analytics_id')
diff --git a/app/views/layouts/_page.html.haml b/app/views/layouts/_page.html.haml
index f17f6fdd91c..96e15783a36 100644
--- a/app/views/layouts/_page.html.haml
+++ b/app/views/layouts/_page.html.haml
@@ -1,6 +1,6 @@
.page-with-sidebar{ class: nav_sidebar_class }
= render "layouts/broadcast"
- .sidebar-wrapper
+ .sidebar-wrapper.nicescroll
- if defined?(sidebar) && sidebar
= render "layouts/nav/#{sidebar}"
- elsif current_user
diff --git a/app/views/layouts/group.html.haml b/app/views/layouts/group.html.haml
index 5edc03129d2..db7dbf9bfe3 100644
--- a/app/views/layouts/group.html.haml
+++ b/app/views/layouts/group.html.haml
@@ -1,5 +1,5 @@
- page_title @group.name
- header_title @group.name, group_path(@group)
-- sidebar "group"
+- sidebar "group" unless sidebar
= render template: "layouts/application"
diff --git a/app/views/layouts/group_settings.html.haml b/app/views/layouts/group_settings.html.haml
new file mode 100644
index 00000000000..e303a561628
--- /dev/null
+++ b/app/views/layouts/group_settings.html.haml
@@ -0,0 +1,4 @@
+- page_title "Settings"
+- sidebar "group_settings"
+
+= render template: "layouts/group"
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 1403b86f377..b3cd7b0e37b 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -3,7 +3,8 @@
.header-logo
= link_to root_path, class: 'home', title: 'Dashboard', id: 'js-shortcuts-home', data: {toggle: 'tooltip', placement: 'bottom'} do
= brand_header_logo
- %h3 GitLab
+ .gitlab-text-container
+ %h3 GitLab
.header-content
%button.navbar-toggle{type: 'button'}
%span.sr-only Toggle navigation
diff --git a/app/views/layouts/header/_empty.html.haml b/app/views/layouts/header/_empty.html.haml
index a52a3c8f0ef..2ed4edb1136 100644
--- a/app/views/layouts/header/_empty.html.haml
+++ b/app/views/layouts/header/_empty.html.haml
@@ -1,4 +1,4 @@
%header.navbar.navbar-fixed-top.navbar-empty
.container
.center-logo
- = image_tag 'logo-white.png', width: 32, height: 32
+ = brand_header_logo
diff --git a/app/views/layouts/header/_public.html.haml b/app/views/layouts/header/_public.html.haml
index 2c5884a5b6d..15c2e292be3 100644
--- a/app/views/layouts/header/_public.html.haml
+++ b/app/views/layouts/header/_public.html.haml
@@ -3,7 +3,8 @@
.header-logo
= link_to explore_root_path, class: "home" do
= brand_header_logo
- %h3 GitLab
+ .gitlab-text-container
+ %h3 GitLab
.header-content
- unless current_controller?('sessions')
.pull-right
diff --git a/app/views/layouts/nav/_group.html.haml b/app/views/layouts/nav/_group.html.haml
index 9f1654b25b4..695ce68a201 100644
--- a/app/views/layouts/nav/_group.html.haml
+++ b/app/views/layouts/nav/_group.html.haml
@@ -1,9 +1,17 @@
%ul.nav.nav-sidebar
+ = nav_link do
+ = link_to root_path, title: 'Back to dashboard', data: {placement: 'right'}, class: 'back-link' do
+ = icon('caret-square-o-left fw')
+ %span
+ Back to Dashboard
+
+ %li.separate-item
+
= nav_link(path: 'groups#show', html_options: {class: 'home'}) do
= link_to group_path(@group), title: 'Home', data: {placement: 'right'} do
= icon('dashboard fw')
%span
- Activity
+ Group
- if current_user
= nav_link(controller: [:group, :milestones]) do
= link_to group_milestones_path(@group), title: 'Milestones', data: {placement: 'right'} do
@@ -29,25 +37,9 @@
= icon('users fw')
%span
Members
-
- 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', data: {placement: 'right'} do
+ = nav_link(html_options: { class: "separate-item" }) do
+ = link_to edit_group_path(@group), title: 'Settings', data: {placement: 'right'} do
= icon ('cogs fw')
%span
Settings
- = icon ('angle-down fw')
-
- - if group_settings_page?
- %ul.sidebar-subnav
- = nav_link(path: 'groups#edit') do
- = link_to edit_group_path(@group), title: 'Group', data: {placement: 'right'} do
- = icon('pencil-square-o')
- %span
- Group Settings
- = nav_link(path: 'groups#projects') do
- = link_to projects_group_path(@group), title: 'Projects', data: {placement: 'right'} do
- = icon('folder')
- %span
- Projects
-
diff --git a/app/views/layouts/nav/_group_settings.html.haml b/app/views/layouts/nav/_group_settings.html.haml
new file mode 100644
index 00000000000..8075fe32fbc
--- /dev/null
+++ b/app/views/layouts/nav/_group_settings.html.haml
@@ -0,0 +1,20 @@
+%ul.nav.nav-sidebar
+ = nav_link do
+ = link_to group_path(@group), title: 'Back to group', data: {placement: 'right'}, class: 'back-link' do
+ = icon('caret-square-o-left fw')
+ %span
+ Back to group
+
+ %li.separate-item
+
+ %ul.sidebar-subnav
+ = nav_link(path: 'groups#edit') do
+ = link_to edit_group_path(@group), title: 'Group Settings', data: {placement: 'right'} do
+ = icon ('pencil-square-o fw')
+ %span
+ Group Settings
+ = nav_link(path: 'groups#projects') do
+ = link_to projects_group_path(@group), title: 'Projects', data: {placement: 'right'} do
+ = icon('folder fw')
+ %span
+ Projects
diff --git a/app/views/layouts/nav/_profile.html.haml b/app/views/layouts/nav/_profile.html.haml
index 914e1b83d1f..33fd5fcef6c 100644
--- a/app/views/layouts/nav/_profile.html.haml
+++ b/app/views/layouts/nav/_profile.html.haml
@@ -1,4 +1,12 @@
%ul.nav.nav-sidebar
+ = nav_link do
+ = link_to root_path, title: 'Back to dashboard', data: {placement: 'right'}, class: 'back-link' do
+ = icon('caret-square-o-left fw')
+ %span
+ Back to Dashboard
+
+ %li.separate-item
+
= nav_link(path: 'profiles#show', html_options: {class: 'home'}) do
= link_to profile_path, title: 'Profile', data: {placement: 'right'} do
= icon('user fw')
@@ -44,8 +52,8 @@
= icon('image fw')
%span
Preferences
- = nav_link(path: 'profiles#history') do
- = link_to history_profile_path, title: 'History', data: {placement: 'right'} do
+ = nav_link(path: 'profiles#audit_log') do
+ = link_to audit_log_profile_path, title: 'Audit Log', data: {placement: 'right'} do
= icon('history fw')
%span
- History
+ Audit Log
diff --git a/app/views/layouts/nav/_project.html.haml b/app/views/layouts/nav/_project.html.haml
index cbcf560d0af..d17d1c5fbd4 100644
--- a/app/views/layouts/nav/_project.html.haml
+++ b/app/views/layouts/nav/_project.html.haml
@@ -1,9 +1,29 @@
-%ul.project-navigation.nav.nav-sidebar
+%ul.nav.nav-sidebar
+ - if @project.group
+ = nav_link do
+ = link_to group_path(@project.group), title: 'Back to group', data: {placement: 'right'}, class: 'back-link' do
+ = icon('caret-square-o-left fw')
+ %span
+ Back to Group
+ - else
+ = nav_link do
+ = link_to root_path, title: 'Back to dashboard', data: {placement: 'right'}, class: 'back-link' do
+ = icon('caret-square-o-left fw')
+ %span
+ Back to Dashboard
+
+ %li.separate-item
+
= nav_link(path: 'projects#show', html_options: {class: 'home'}) do
= link_to project_path(@project), title: 'Project', class: 'shortcuts-project', data: {placement: 'right'} do
- = icon('dashboard fw')
+ = icon('home fw')
%span
Project
+ = nav_link(path: 'projects#activity') do
+ = link_to activity_project_path(@project), title: 'Project Activity', class: 'shortcuts-project-activity', data: {placement: 'right'} do
+ = icon('dashboard fw')
+ %span
+ Activity
- 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', data: {placement: 'right'} do
@@ -86,7 +106,7 @@
- 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', data: {placement: 'right'} do
+ = link_to edit_project_path(@project), title: 'Settings', data: {placement: 'right'} do
= icon('cogs fw')
%span
Settings
diff --git a/app/views/layouts/nav/_project_settings.html.haml b/app/views/layouts/nav/_project_settings.html.haml
index 633c6ae6bfb..857fb199957 100644
--- a/app/views/layouts/nav/_project_settings.html.haml
+++ b/app/views/layouts/nav/_project_settings.html.haml
@@ -1,15 +1,15 @@
-%ul.project-navigation.nav.nav-sidebar
+%ul.nav.nav-sidebar
= nav_link do
- = link_to project_path(@project), title: 'Back to project', data: {placement: 'right'} do
+ = link_to project_path(@project), title: 'Back to project', data: {placement: 'right'}, class: 'back-link' do
= icon('caret-square-o-left fw')
%span
Back to project
%li.separate-item
- %ul.project-settings-nav.sidebar-subnav
+ %ul.sidebar-subnav
= nav_link(path: 'projects#edit') do
- = link_to edit_project_path(@project), title: 'Project', class: 'stat-tab tab', data: {placement: 'right'} do
+ = link_to edit_project_path(@project), title: 'Project Settings', data: {placement: 'right'} do
= icon('pencil-square-o fw')
%span
Project Settings
@@ -32,5 +32,5 @@
= link_to namespace_project_protected_branches_path(@project.namespace, @project), title: 'Protected Branches', data: {placement: 'right'} do
= icon('lock fw')
%span
- Protected branches
+ Protected Branches
diff --git a/app/views/profiles/_event_table.html.haml b/app/views/profiles/_event_table.html.haml
new file mode 100644
index 00000000000..c19ac429d52
--- /dev/null
+++ b/app/views/profiles/_event_table.html.haml
@@ -0,0 +1,16 @@
+%table.table#audits
+ %thead
+ %tr
+ %th Action
+ %th When
+
+ %tbody
+ - events.each do |event|
+ %tr
+ %td
+ %span
+ Signed in with
+ %b= event.details[:with]
+ authentication
+ %td #{time_ago_in_words event.created_at} ago
+= paginate events, theme: "gitlab"
diff --git a/app/views/profiles/accounts/show.html.haml b/app/views/profiles/accounts/show.html.haml
index ed009c86568..378dfa2dce0 100644
--- a/app/views/profiles/accounts/show.html.haml
+++ b/app/views/profiles/accounts/show.html.haml
@@ -36,7 +36,7 @@
.panel-heading
Two-factor Authentication
.panel-body
- - if current_user.otp_required_for_login
+ - if current_user.two_factor_enabled?
.pull-right
= link_to 'Disable Two-factor Authentication', profile_two_factor_auth_path, method: :delete, class: 'btn btn-close btn-sm',
data: { confirm: 'Are you sure?' }
diff --git a/app/views/profiles/applications.html.haml b/app/views/profiles/applications.html.haml
index 2c4f0804f0b..3a3e6e1b1c4 100644
--- a/app/views/profiles/applications.html.haml
+++ b/app/views/profiles/applications.html.haml
@@ -56,5 +56,14 @@
%td= token.created_at
%td= token.scopes
%td= render 'doorkeeper/authorized_applications/delete_form', application: app
+ - @authorized_anonymous_tokens.each do |token|
+ %tr
+ %td
+ Anonymous
+ %div.help-block
+ %em Authorization was granted by entering your username and password in the application.
+ %td= token.created_at
+ %td= token.scopes
+ %td= render 'doorkeeper/authorized_applications/delete_form', token: token
- else
- %p.light You dont have any authorized applications
+ %p.light You don't have any authorized applications
diff --git a/app/views/profiles/audit_log.html.haml b/app/views/profiles/audit_log.html.haml
new file mode 100644
index 00000000000..698d6037428
--- /dev/null
+++ b/app/views/profiles/audit_log.html.haml
@@ -0,0 +1,5 @@
+- page_title "Audit Log"
+%h3.page-title Audit Log
+%p.light History of authentications
+
+= render 'event_table', events: @events \ No newline at end of file
diff --git a/app/views/profiles/history.html.haml b/app/views/profiles/history.html.haml
deleted file mode 100644
index b414fb69f4e..00000000000
--- a/app/views/profiles/history.html.haml
+++ /dev/null
@@ -1,11 +0,0 @@
-- page_title "History"
-%h3.page-title
- Your Account History
-%p.light
- All events created by your account are listed below.
-%hr
-.profile_history
- = render @events
-%hr
-= paginate @events, theme: "gitlab"
-
diff --git a/app/views/profiles/preferences/show.html.haml b/app/views/profiles/preferences/show.html.haml
index aa99280fde6..1134317ee06 100644
--- a/app/views/profiles/preferences/show.html.haml
+++ b/app/views/profiles/preferences/show.html.haml
@@ -38,5 +38,13 @@
= link_to('(?)', help_page_path('profile', 'preferences') + '#default-dashboard', target: '_blank')
.col-sm-10
= f.select :dashboard, dashboard_choices, {}, class: 'form-control'
+ .form-group
+ = f.label :project_view, class: 'control-label' do
+ Project view
+ = link_to('(?)', help_page_path('profile', 'preferences') + '#default-project-view', target: '_blank')
+ .col-sm-10
+ = f.select :project_view, project_view_choices, {}, class: 'form-control'
+ .help-block
+ Choose what content you want to see when visit project page
.panel-footer
= f.submit 'Save', class: 'btn btn-save'
diff --git a/app/views/projects/_activity.html.haml b/app/views/projects/_activity.html.haml
new file mode 100644
index 00000000000..ee02b7f6a6c
--- /dev/null
+++ b/app/views/projects/_activity.html.haml
@@ -0,0 +1,15 @@
+= render 'projects/last_push'
+.hidden-xs
+ - if current_user
+ %ul.nav.nav-pills.event_filter.pull-right
+ %li
+ = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'rss-btn' do
+ %i.fa.fa-rss
+
+ = render 'shared/event_filter'
+ %hr
+.content_list{:"data-href" => activity_project_path(@project)}
+= spinner
+
+:coffeescript
+ new Activities()
diff --git a/app/views/projects/_aside.html.haml b/app/views/projects/_aside.html.haml
deleted file mode 100644
index c9c17110d2b..00000000000
--- a/app/views/projects/_aside.html.haml
+++ /dev/null
@@ -1,108 +0,0 @@
-.clearfix
- - unless @project.empty_repo?
- .panel.panel-default
- .panel-heading
- = visibility_level_icon(@project.visibility_level)
- = "#{visibility_level_label(@project.visibility_level).capitalize} project"
-
- .panel-body
- - if @repository.changelog || @repository.license || @repository.contribution_guide
- %ul.nav.nav-pills
- - if @repository.changelog
- %li.hidden-xs
- = link_to changelog_url(@project) do
- Changelog
- - if @repository.license
- %li
- = link_to license_url(@project) do
- License
- - if @repository.contribution_guide
- %li
- = link_to contribution_guide_url(@project) do
- Contribution guide
-
- .actions
- - if can? current_user, :write_issue, @project
- = link_to url_for_new_issue(@project, only_path: true), title: "New Issue", class: 'btn btn-sm append-right-10' do
- = icon("exclamation-circle fw")
- New Issue
-
- - if can? current_user, :write_merge_request, @project
- = link_to new_namespace_project_merge_request_path(@project.namespace, @project), class: "btn btn-sm", title: "New Merge Request" do
- = icon("plus fw")
- New Merge Request
-
- - if forked_from_project = @project.forked_from_project
- .panel-footer
- = icon("code-fork fw")
- Forked from
- .pull-right
- = link_to forked_from_project.namespace.try(:name), project_path(forked_from_project)
-
-
- - @project.ci_services.each do |ci_service|
- - if ci_service.active? && ci_service.respond_to?(:builds_path)
- .panel-footer
- = icon("check fw")
- = ci_service.title
- .pull-right
- - 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", class: 'ci-status-image'
- - else
- = link_to 'view builds', ci_service.builds_path, :'data-no-turbolink' => 'data-no-turbolink'
-
-
- - unless @project.empty_repo?
- .panel.panel-default
- .panel-heading
- = icon("folder-o fw")
- Repository
- .panel-body
- %ul.nav.nav-pills
- %li
- = link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref) do
- = pluralize(number_with_delimiter(@repository.commit_count), 'commit')
- %li
- = link_to namespace_project_branches_path(@project.namespace, @project) do
- = pluralize(number_with_delimiter(@repository.branch_names.count), 'branch')
- %li
- = link_to namespace_project_tags_path(@project.namespace, @project) do
- = pluralize(number_with_delimiter(@repository.tag_names.count), 'tag')
-
- .actions
- = link_to namespace_project_compare_index_path(@project.namespace, @project, from: @repository.root_ref, to: @ref || @repository.root_ref), class: 'btn btn-sm append-right-10' 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-group-sm'
- - if version = @repository.version
- .panel-footer
- = icon("clock-o fw")
- Version
- .pull-right
- = link_to version_url(@project) do
- = @repository.blob_by_oid(version.id).data
-
- = render "shared/clone_panel"
-
- - if @project.archived?
- %br
- .alert.alert-warning
- %h4
- = icon("exclamation-triangle fw")
- Archived project!
- %p Repository is read-only
-
- - if current_user
- - access = user_max_access_in_project(current_user, @project)
- - if access
- .light-well.light.prepend-top-20
- %small
- You have #{access} access to this project.
- - if @project.project_member_by_id(current_user)
- %br
- = link_to leave_namespace_project_project_members_path(@project.namespace, @project),
- data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project' do
- Leave this project
diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml
index 076afb11a9d..7b6b4b35c8d 100644
--- a/app/views/projects/_home_panel.html.haml
+++ b/app/views/projects/_home_panel.html.haml
@@ -1,37 +1,28 @@
- empty_repo = @project.empty_repo?
.project-home-panel.clearfix{:class => ("empty-project" if empty_repo)}
.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?
- = markdown(@project.description, pipeline: :description)
- - if can?(current_user, :admin_project, @project)
- &ndash;
- = link_to 'Edit', edit_namespace_project_path
- - elsif !empty_repo && @repository.readme
- - readme = @repository.readme
- &ndash;
- = 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 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
+ = project_icon(@project, alt: '', class: 'project-avatar avatar s90')
+ .project-home-desc.lead
+ - if @project.description.present?
+ = markdown(@project.description, pipeline: :description)
+
+
+ .project-repo-buttons
+ = render 'projects/buttons/star'
+
+ - unless empty_repo
+ = render 'projects/buttons/fork'
+
+ - if forked_from_project = @project.forked_from_project
+ = link_to project_path(forked_from_project), class: 'btn' do
+ = icon("code-fork fw")
+ Forked from
+ = forked_from_project.namespace.try(:name)
+
+ - if can? current_user, :download_code, @project
+ = link_to archive_namespace_project_repository_path(@project.namespace, @project, ref: @ref, format: 'zip'), class: 'btn', rel: 'nofollow' do
+ %i.fa.fa-download
+
+ = render 'projects/buttons/dropdown'
+
+ = render "shared/clone_panel"
diff --git a/app/views/projects/_last_push.html.haml b/app/views/projects/_last_push.html.haml
new file mode 100644
index 00000000000..30622d8a910
--- /dev/null
+++ b/app/views/projects/_last_push.html.haml
@@ -0,0 +1,14 @@
+- if event = last_push_event
+ - if show_last_push_widget?(event)
+ .hidden-xs.center
+ .slead
+ %span You pushed to
+ = link_to namespace_project_commits_path(event.project.namespace, event.project, event.ref_name) do
+ %strong= event.ref_name
+ branch
+ #{time_ago_with_tooltip(event.created_at)}
+
+ %div
+ = link_to new_mr_path_from_push_event(event), title: "New Merge Request", class: "btn btn-info btn-sm" do
+ Create Merge Request
+ %hr
diff --git a/app/views/projects/_readme.html.haml b/app/views/projects/_readme.html.haml
new file mode 100644
index 00000000000..5038edb95ed
--- /dev/null
+++ b/app/views/projects/_readme.html.haml
@@ -0,0 +1,24 @@
+- if readme = @repository.readme
+ %article.readme-holder#README
+ .clearfix
+ .pull-right
+ &nbsp;
+ - if can?(current_user, :push_code, @project)
+ = link_to namespace_project_edit_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)), class: 'light' do
+ %i.fa.fa-pencil
+ .wiki
+ = cache(readme_cache_key) do
+ = render_readme(readme)
+- else
+ %h3.page-title
+ This project does not have README yet
+ - if can?(current_user, :push_code, @project)
+ %p.slead
+ A
+ %code README
+ file contains information about other files in a repository and is commonly
+ distributed with computer software, forming part of its documentation.
+ %br
+ We recommend you to
+ = link_to "add README", new_readme_path, class: 'underlined-link'
+ file to the repository and GitLab will render it here instead of this message.
diff --git a/app/views/projects/_section.html.haml b/app/views/projects/_section.html.haml
deleted file mode 100644
index d7b06197f67..00000000000
--- a/app/views/projects/_section.html.haml
+++ /dev/null
@@ -1,36 +0,0 @@
-%ul.nav.nav-tabs
- %li.active
- = link_to '#tab-activity', 'data-toggle' => 'tab' do
- = icon("tachometer")
- Activity
- - if @repository.readme
- %li
- = link_to '#tab-readme', 'data-toggle' => 'tab' do
- = icon("file-text-o")
- Readme
-.tab-content
- .tab-pane.active#tab-activity
- .hidden-xs
- = render "events/event_last_push", event: @last_push
-
- - if current_user
- %ul.nav.nav-pills.event_filter.pull-right
- %li
- = link_to namespace_project_path(@project.namespace, @project, format: :atom, private_token: current_user.private_token), title: "Feed", class: 'rss-btn' do
- %i.fa.fa-rss
-
- = render 'shared/event_filter'
- %hr
- .content_list
- = spinner
-
- - if readme = @repository.readme
- .tab-pane#tab-readme
- %article.readme-holder#README
- .clearfix
- %small.pull-right
- = link_to namespace_project_blob_path(@project.namespace, @project, tree_join(@repository.root_ref, readme.name)), class: 'light' do
- %i.fa.fa-file
- = readme.name
- .wiki
- = render_readme(readme)
diff --git a/app/views/projects/_zen.html.haml b/app/views/projects/_zen.html.haml
index e27394ab30b..6a41cdbc907 100644
--- a/app/views/projects/_zen.html.haml
+++ b/app/views/projects/_zen.html.haml
@@ -2,7 +2,7 @@
%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: 'You can tag other participants like this: @michael do you think this is a good idea? Also @deborah, what do you think?'
+ = f.text_area attr, class: classes, placeholder: ''
= link_to nil, class: 'zen-enter-link', tabindex: '-1' do
%i.fa.fa-expand
Edit in fullscreen
diff --git a/app/views/projects/activity.html.haml b/app/views/projects/activity.html.haml
new file mode 100644
index 00000000000..65674913bb0
--- /dev/null
+++ b/app/views/projects/activity.html.haml
@@ -0,0 +1 @@
+= render 'projects/activity'
diff --git a/app/views/projects/blob/_remove.html.haml b/app/views/projects/blob/_remove.html.haml
index b8d8451880a..cae5ff01099 100644
--- a/app/views/projects/blob/_remove.html.haml
+++ b/app/views/projects/blob/_remove.html.haml
@@ -9,13 +9,10 @@
%strong= @ref
.modal-body
- = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal' do
+ = form_tag namespace_project_blob_path(@project.namespace, @project, @id), method: :delete, class: 'form-horizontal js-requires-input' do
= render 'shared/commit_message_container', params: params,
placeholder: 'Removed this file because...'
.form-group
.col-sm-offset-2.col-sm-10
= 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/edit.html.haml b/app/views/projects/blob/edit.html.haml
index e78181f8801..a12cd660fc1 100644
--- a/app/views/projects/blob/edit.html.haml
+++ b/app/views/projects/blob/edit.html.haml
@@ -11,10 +11,9 @@
%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
+ = form_tag(namespace_project_update_blob_path(@project.namespace, @project, @id), method: :put, class: 'form-horizontal js-requires-input') do
= render 'projects/blob/editor', ref: @ref, path: @path, blob_data: @blob.data
- = render 'shared/commit_message_container', params: params,
- placeholder: "Update #{@blob.name}"
+ = render 'shared/commit_message_container', params: params, placeholder: "Update #{@blob.name}"
.form-group.branch
= label_tag 'branch', class: 'control-label' do
@@ -25,8 +24,7 @@
= 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
+ = 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
index f7ddf74b4fc..7c2a4fece94 100644
--- a/app/views/projects/blob/new.html.haml
+++ b/app/views/projects/blob/new.html.haml
@@ -1,7 +1,7 @@
- page_title "New File", @ref
%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
+ = form_tag(namespace_project_create_blob_path(@project.namespace, @project, @id), method: :post, class: 'form-horizontal form-new-file js-requires-input') do
= render 'projects/blob/editor', ref: @ref
= render 'shared/commit_message_container', params: params,
placeholder: 'Add new file'
diff --git a/app/views/projects/blob/show.html.haml b/app/views/projects/blob/show.html.haml
index a1d464bac59..bd2fc43633c 100644
--- a/app/views/projects/blob/show.html.haml
+++ b/app/views/projects/blob/show.html.haml
@@ -1,4 +1,7 @@
- page_title @blob.path, @ref
+
+= render 'projects/last_push'
+
%div.tree-ref-holder
= render 'shared/ref_switcher', destination: 'blob', path: @path
diff --git a/app/views/projects/branches/new.html.haml b/app/views/projects/branches/new.html.haml
index cac5dc91afd..29e82b93883 100644
--- a/app/views/projects/branches/new.html.haml
+++ b/app/views/projects/branches/new.html.haml
@@ -6,7 +6,7 @@
%h3.page-title
%i.fa.fa-code-fork
New branch
-= form_tag namespace_project_branches_path, method: :post, id: "new-branch-form", class: "form-horizontal" do
+= form_tag namespace_project_branches_path, method: :post, id: "new-branch-form", class: "form-horizontal js-requires-input" do
.form-group
= label_tag :branch_name, 'Name for new branch', class: 'control-label'
.col-sm-10
@@ -20,7 +20,6 @@
= 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/buttons/_dropdown.html.haml b/app/views/projects/buttons/_dropdown.html.haml
new file mode 100644
index 00000000000..99c2ed62545
--- /dev/null
+++ b/app/views/projects/buttons/_dropdown.html.haml
@@ -0,0 +1,31 @@
+- if current_user
+ %span.dropdown
+ %a.dropdown-toggle.btn.btn-new{href: '#', "data-toggle" => "dropdown"}
+ %i.fa.fa-plus
+ %ul.dropdown-menu
+ - if @project.issues_enabled && can?(current_user, :create_issue, @project)
+ %li
+ = link_to url_for_new_issue, title: "New Issue" do
+ New issue
+ - if @project.merge_requests_enabled && can?(current_user, :create_merge_request, @project)
+ %li
+ = link_to new_namespace_project_merge_request_path(@project.namespace, @project), title: "New Merge Request" do
+ New merge request
+ - if @project.snippets_enabled && can?(current_user, :create_snippet, @project)
+ %li
+ = link_to new_namespace_project_snippet_path(@project.namespace, @project), title: "New Snippet" do
+ New snippet
+ - if can?(current_user, :admin_project_member, @project)
+ %li
+ = link_to namespace_project_project_members_path(@project.namespace, @project), title: "New project member" do
+ New project member
+ - if can? current_user, :push_code, @project
+ %li.divider
+ %li
+ = link_to new_namespace_project_branch_path(@project.namespace, @project) do
+ New git branch
+ %li
+ = link_to new_namespace_project_tag_path(@project.namespace, @project) do
+ New git tag
+
+
diff --git a/app/views/projects/buttons/_fork.html.haml b/app/views/projects/buttons/_fork.html.haml
new file mode 100644
index 00000000000..f0483c79edc
--- /dev/null
+++ b/app/views/projects/buttons/_fork.html.haml
@@ -0,0 +1,13 @@
+- if current_user && can?(current_user, :fork_project, @project)
+ - 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' do
+ = icon('code-fork')
+ Fork
+ %span.count
+ = @project.forks_count
+ - else
+ = link_to new_namespace_project_fork_path(@project.namespace, @project), title: "Fork project", class: 'btn' do
+ = icon('code-fork')
+ Fork
+ %span.count
+ = @project.forks_count
diff --git a/app/views/projects/buttons/_star.html.haml b/app/views/projects/buttons/_star.html.haml
new file mode 100644
index 00000000000..b5f14b43bfd
--- /dev/null
+++ b/app/views/projects/buttons/_star.html.haml
@@ -0,0 +1,22 @@
+- if current_user
+ = link_to toggle_star_namespace_project_path(@project.namespace, @project), class: 'btn star-btn toggle-star', method: :post, remote: true do
+ = icon('star')
+ - if current_user.starred?(@project)
+ Unstar
+ - else
+ Star
+ %span.count
+ = @project.star_count
+
+ :coffeescript
+ $('.project-home-panel .toggle-star').on 'ajax:success', (e, data, status, xhr) ->
+ $(@).replaceWith(data.html)
+ .on 'ajax:error', (e, xhr, status, error) ->
+ new Flash('Star toggle failed. Try again later.', 'alert')
+
+- else
+ = link_to new_user_session_path, class: 'btn has_tooltip star-btn', title: 'You must sign in to star a project' do
+ = icon('star')
+ Star
+ %span.count
+ = @project.star_count
diff --git a/app/views/projects/commits/_commit.html.haml b/app/views/projects/commits/_commit.html.haml
index 083fca9b658..74f8d8b15cf 100644
--- a/app/views/projects/commits/_commit.html.haml
+++ b/app/views/projects/commits/_commit.html.haml
@@ -1,33 +1,34 @@
-%li.commit.js-toggle-container
- .commit-row-title
- %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 ...
+- if @note_counts
+ - note_count = @note_counts.fetch(commit.id, 0)
+- else
+ - notes = commit.notes
+ - note_count = notes.user.count
- .pull-right
- = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
+= cache [project.id, commit.id, note_count] do
+ %li.commit.js-toggle-container
+ .commit-row-title
+ %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 ...
- .notes_count
- - if @note_counts
- - note_count = @note_counts.fetch(commit.id, 0)
- - else
- - notes = commit.notes
- - note_count = notes.user.count
+ .pull-right
+ = link_to commit.short_id, namespace_project_commit_path(project.namespace, project, commit), class: "commit_short_id"
- - if note_count > 0
- %span.light
- %i.fa.fa-comments
- = note_count
+ .notes_count
+ - if note_count > 0
+ %span.light
+ %i.fa.fa-comments
+ = note_count
- - if commit.description?
- .commit-row-description.js-toggle-content
- %pre
- = preserve(gfm(escape_once(commit.description)))
+ - if commit.description?
+ .commit-row-description.js-toggle-content
+ %pre
+ = preserve(gfm(escape_once(commit.description)))
- .commit-row-info
- = commit_author_link(commit, avatar: true, size: 24)
- authored
- .committed_ago
- #{time_ago_with_tooltip(commit.committed_date)} &nbsp;
- = link_to_browse_code(project, commit)
+ .commit-row-info
+ = commit_author_link(commit, avatar: true, size: 24)
+ authored
+ .committed_ago
+ #{time_ago_with_tooltip(commit.committed_date, skip_js: true)} &nbsp;
+ = link_to_browse_code(project, commit)
diff --git a/app/views/projects/commits/show.html.haml b/app/views/projects/commits/show.html.haml
index 9682100a54c..55054a31977 100644
--- a/app/views/projects/commits/show.html.haml
+++ b/app/views/projects/commits/show.html.haml
@@ -15,9 +15,8 @@
Create Merge Request
- if current_user && current_user.private_token
- = link_to namespace_project_commits_path(@project.namespace, @project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Feed", class: 'prepend-left-10 btn' do
+ = link_to namespace_project_commits_path(@project.namespace, @project, @ref, {format: :atom, private_token: current_user.private_token}), title: "Commits Feed", class: 'prepend-left-10 btn' do
= icon("rss")
- Commits Feed
%ul.breadcrumb.repo-breadcrumb
diff --git a/app/views/projects/compare/_form.html.haml b/app/views/projects/compare/_form.html.haml
index a0e904cfd8b..3019893d12c 100644
--- a/app/views/projects/compare/_form.html.haml
+++ b/app/views/projects/compare/_form.html.haml
@@ -1,16 +1,16 @@
-= form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline' do
+= form_tag namespace_project_compare_index_path(@project.namespace, @project), method: :post, class: 'form-inline js-requires-input' 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'}
.form-group
.input-group.inline-input-group
%span.input-group-addon from
- = text_field_tag :from, params[:from], class: "form-control"
+ = text_field_tag :from, params[:from], class: "form-control", required: true
= "..."
.form-group
.input-group.inline-input-group
%span.input-group-addon to
- = text_field_tag :to, params[:to], class: "form-control"
+ = text_field_tag :to, params[:to], class: "form-control", required: true
&nbsp;
= button_tag "Compare", class: "btn btn-create commits-compare-btn"
- if create_mr_button?
@@ -26,5 +26,3 @@
source: availableTags,
minLength: 1
});
-
- disableButtonIfEmptyField('#to', '.commits-compare-btn');
diff --git a/app/views/projects/diffs/_diffs.html.haml b/app/views/projects/diffs/_diffs.html.haml
index ec8974c5475..52c1e03040c 100644
--- a/app/views/projects/diffs/_diffs.html.haml
+++ b/app/views/projects/diffs/_diffs.html.haml
@@ -20,6 +20,3 @@
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/_parallel_view.html.haml b/app/views/projects/diffs/_parallel_view.html.haml
index cb41dd852d3..37fd1b1ec8a 100644
--- a/app/views/projects/diffs/_parallel_view.html.haml
+++ b/app/views/projects/diffs/_parallel_view.html.haml
@@ -18,7 +18,7 @@
- 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
- - if @comments_allowed && can?(current_user, :write_note, @project)
+ - if @comments_allowed && can?(current_user, :create_note, @project)
= link_to_new_diff_note(line_code_left, 'old')
%td.line_content{class: "parallel noteable_line #{type_left} #{line_code_left}", "line_code" => line_code_left }= raw line_content_left
@@ -31,7 +31,7 @@
%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
- - if @comments_allowed && can?(current_user, :write_note, @project)
+ - if @comments_allowed && can?(current_user, :create_note, @project)
= link_to_new_diff_note(line_code_right, 'new')
%td.line_content.parallel{class: "noteable_line #{new_line_class} #{new_line_code}", "line_code" => new_line_code}= raw line_content_right
diff --git a/app/views/projects/diffs/_text_file.html.haml b/app/views/projects/diffs/_text_file.html.haml
index a6373181b45..ed4c601bcdb 100644
--- a/app/views/projects/diffs/_text_file.html.haml
+++ b/app/views/projects/diffs/_text_file.html.haml
@@ -16,7 +16,7 @@
- else
%td.old_line
= link_to raw(type == "new" ? "&nbsp;" : line_old), "##{line_code}", id: line_code
- - if @comments_allowed && can?(current_user, :write_note, @project)
+ - if @comments_allowed && can?(current_user, :create_note, @project)
= link_to_new_diff_note(line_code)
%td.new_line{data: {linenumber: line.new_pos}}
= link_to raw(type == "old" ? "&nbsp;" : line.new_pos) , "##{line_code}", id: line_code
diff --git a/app/views/projects/diffs/_warning.html.haml b/app/views/projects/diffs/_warning.html.haml
index bd0b7376ba7..da3d4be84ba 100644
--- a/app/views/projects/diffs/_warning.html.haml
+++ b/app/views/projects/diffs/_warning.html.haml
@@ -1,6 +1,6 @@
.alert.alert-warning
%h4
- Too many changes.
+ Too many changes to show.
.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"
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 3fecd25c324..e8e65d87f47 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -29,7 +29,7 @@
.col-sm-10= f.select(:default_branch, @repository.branch_names, {}, {class: 'select2 select-wide'})
- = render 'shared/visibility_level', f: f, visibility_level: @project.visibility_level, can_change_visibility_level: can?(current_user, :change_visibility_level, @project), form_model: @project
+ = render 'shared/visibility_level', f: f, visibility_level: @project.visibility_level, can_change_visibility_level: can_change_visibility_level?(@project, current_user), form_model: @project
.form-group
= f.label :tag_list, "Tags", class: 'control-label'
@@ -176,7 +176,7 @@
.form-group
= label_tag :new_namespace_id, nil, class: 'control-label' do
%span Namespace
- .col-sm-10
+ .col-sm-9
.form-group
= select_tag :new_namespace_id, namespaces_options(@project.namespace_id), { prompt: 'Choose a project namespace', class: 'select2' }
%ul
diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml
index 8080a904978..dfe45a3802d 100644
--- a/app/views/projects/empty.html.haml
+++ b/app/views/projects/empty.html.haml
@@ -4,30 +4,30 @@
= render "home_panel"
-.center.well
- %h3
+.center.light-well
+ %h3.page-title
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
- &nbsp;or do a push via the command line.
+ %p
+ If you already have files you can push them using command line instructions below.
+ %br
+ Otherwise you can start with
+ = link_to "adding README", new_readme_path, class: 'underlined-link'
+ file to this project.
-.well
- = render "shared/clone_panel"
-%h4
- %strong Command line instructions
+.prepend-top-20
+%h3.page-title
+ Command line instructions
%div.git-empty
%fieldset
- %legend Git global setup
- %pre.dark
+ %h5 Git global setup
+ %pre.light-well
:preserve
git config --global user.name "#{git_user_name}"
git config --global user.email "#{git_user_email}"
%fieldset
- %legend Create a new repository
- %pre.dark
+ %h5 Create a new repository
+ %pre.light-well
:preserve
git clone #{ content_tag(:span, default_url_to_repo, class: 'clone')}
cd #{@project.path}
@@ -37,8 +37,8 @@
git push -u origin master
%fieldset
- %legend Existing folder or Git repository
- %pre.dark
+ %h5 Existing folder or Git repository
+ %pre.light-well
:preserve
cd existing_folder
git init
diff --git a/app/views/projects/graphs/commits.html.haml b/app/views/projects/graphs/commits.html.haml
index 254a76e108b..141acbdcf72 100644
--- a/app/views/projects/graphs/commits.html.haml
+++ b/app/views/projects/graphs/commits.html.haml
@@ -1,9 +1,11 @@
- page_title "Commit statistics"
+.tree-ref-holder
+ = render 'shared/ref_switcher', destination: 'graphs_commits'
= render 'head'
%p.lead
Commit statistics for
- %strong #{@repository.root_ref}
+ %strong #{@ref}
#{@commits_graph.start_date.strftime('%b %d')} - #{@commits_graph.end_date.strftime('%b %d')}
.row
diff --git a/app/views/projects/graphs/show.html.haml b/app/views/projects/graphs/show.html.haml
index 3a8dc89f84c..ecdd0eaf52f 100644
--- a/app/views/projects/graphs/show.html.haml
+++ b/app/views/projects/graphs/show.html.haml
@@ -1,5 +1,8 @@
- page_title "Contributor statistics"
+.tree-ref-holder
+ = render 'shared/ref_switcher', destination: 'graphs'
= render 'head'
+
.loading-graph
.center
%h3.page-title
@@ -11,7 +14,7 @@
.header.clearfix
%h3#date_header.page-title
%p.light
- Commits to #{@project.default_branch}, excluding merge commits. Limited by 6,000 commits
+ Commits to #{@ref}, excluding merge commits. Limited by 6,000 commits
%input#brush_change{:type => "hidden"}
.graphs
#contributors-master
@@ -35,4 +38,3 @@
$(".stat-graph").fadeIn();
$(".loading-graph").hide();
dataType: "json"
-
diff --git a/app/views/projects/issues/_discussion.html.haml b/app/views/projects/issues/_discussion.html.haml
index 48858fa32da..f61ae957208 100644
--- a/app/views/projects/issues/_discussion.html.haml
+++ b/app/views/projects/issues/_discussion.html.haml
@@ -1,5 +1,5 @@
- content_for :note_actions do
- - if can?(current_user, :modify_issue, @issue)
+ - if can?(current_user, :update_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
@@ -12,8 +12,8 @@
.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|
+ %span= pluralize(@participants.count, 'participant')
+ - @participants.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
@@ -23,7 +23,7 @@
= cross_project_reference(@project, @issue)
%hr
.context
- = render partial: 'issue_context', locals: { issue: @issue }
+ = render 'shared/issuable/context', issuable: @issue
- if @issue.labels.any?
.issuable-context-title
diff --git a/app/views/projects/issues/_form.html.haml b/app/views/projects/issues/_form.html.haml
index 8d2564be55e..f39bb7d2574 100644
--- a/app/views/projects/issues/_form.html.haml
+++ b/app/views/projects/issues/_form.html.haml
@@ -3,7 +3,7 @@
%hr
= 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
+ = render 'shared/issuable/form', f: f, issuable: @issue
:javascript
$('.assign-to-me-link').on('click', function(e){
diff --git a/app/views/projects/issues/_issue.html.haml b/app/views/projects/issues/_issue.html.haml
index 64d62b45657..1b45bb1af0c 100644
--- a/app/views/projects/issues/_issue.html.haml
+++ b/app/views/projects/issues/_issue.html.haml
@@ -1,44 +1,45 @@
%li{ id: dom_id(issue), class: issue_css_classes(issue), url: issue_path(issue) }
- - if controller.controller_name == 'issues'
+ - if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project)
.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)
+ = check_box_tag dom_id(issue,"selected"), nil, false, 'data-id' => issue.id, class: "selected_issue"
- .issue-title
- %span.issue-title-text
- = link_to_gfm issue.title, issue_path(issue), class: "row_title"
- .issue-labels
- - issue.labels.each do |label|
- = link_to_label(label, project: issue.project)
- .pull-right.light
- - if issue.closed?
- %span
- CLOSED
- - if issue.assignee
- = link_to_member(@project, issue.assignee, name: false)
- - note_count = issue.notes.user.count
- - if note_count > 0
+ = cache issue do
+ .issue-title
+ %span.issue-title-text
+ = link_to_gfm issue.title, issue_path(issue), class: "row_title"
+ .issue-labels
+ - issue.labels.each do |label|
+ = link_to_label(label, project: issue.project)
+ .pull-right.light
+ - if issue.closed?
+ %span
+ CLOSED
+ - if issue.assignee
+ = link_to_member(@project, issue.assignee, name: false)
+ - note_count = issue.notes.user.count
+ - if note_count > 0
+ &nbsp;
+ %span
+ %i.fa.fa-comments
+ = note_count
+ - else
+ &nbsp;
+ %span.issue-no-comments
+ %i.fa.fa-comments
+ = 0
+
+ .issue-info
+ = "#{issue.to_reference} opened #{time_ago_with_tooltip(issue.created_at, placement: 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)}".html_safe
+ - if issue.votes_count > 0
+ = render 'votes/votes_inline', votable: issue
+ - if issue.milestone
&nbsp;
%span
- %i.fa.fa-comments
- = note_count
- - else
- &nbsp;
- %span.issue-no-comments
- %i.fa.fa-comments
- = 0
-
- .issue-info
- = "##{issue.iid} opened #{time_ago_with_tooltip(issue.created_at, 'bottom')} by #{link_to_member(@project, issue.author, avatar: false)}".html_safe
- - if issue.votes_count > 0
- = render 'votes/votes_inline', votable: issue
- - if issue.milestone
- &nbsp;
- %span
- %i.fa.fa-clock-o
- = issue.milestone.title
- - if issue.tasks?
- %span.task-status
- = issue.task_status
+ %i.fa.fa-clock-o
+ = issue.milestone.title
+ - 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')}
+ .pull-right.issue-updated-at
+ %small updated #{time_ago_with_tooltip(issue.updated_at, placement: 'bottom', html_class: 'issue_update_ago')}
diff --git a/app/views/projects/issues/_issue_context.html.haml b/app/views/projects/issues/_issue_context.html.haml
deleted file mode 100644
index 323f5c84a85..00000000000
--- a/app/views/projects/issues/_issue_context.html.haml
+++ /dev/null
@@ -1,46 +0,0 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @issue], remote: true, html: {class: 'edit-issue inline-update js-issue-update'} do |f|
- %div.prepend-top-20
- .issuable-context-title
- %label
- Assignee:
- - if issue.assignee
- %strong= link_to_member(@project, @issue.assignee, size: 24)
- - else
- 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)
-
- %div.prepend-top-20.clearfix
- .issuable-context-title
- %label
- Milestone:
- - 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
- - 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{:type => '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/index.html.haml b/app/views/projects/issues/index.html.haml
index 1d5597602d1..d06225f5488 100644
--- a/app/views/projects/issues/index.html.haml
+++ b/app/views/projects/issues/index.html.haml
@@ -11,14 +11,14 @@
= 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)
+ = render 'shared/issuable/search_form', path: namespace_project_issues_path(@project.namespace, @project)
- - if can? current_user, :write_issue, @project
+ - if can? current_user, :create_issue, @project
= link_to new_namespace_project_issue_path(@project.namespace, @project, issue: { assignee_id: @issuable_finder.assignee.try(:id), milestone_id: @issuable_finder.milestones.try(:first).try(: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', type: :issues
+ = render 'shared/issuable/filter', type: :issues
.issues-holder
= render "issues"
diff --git a/app/views/projects/issues/show.html.haml b/app/views/projects/issues/show.html.haml
index ee1b2a08bc4..54d33a5ddd1 100644
--- a/app/views/projects/issues/show.html.haml
+++ b/app/views/projects/issues/show.html.haml
@@ -1,6 +1,6 @@
- page_title "#{@issue.title} (##{@issue.iid})", "Issues"
.issue
- .issue-details
+ .issue-details.issuable-details
%h4.page-title
.issue-box{ class: issue_box_class(@issue) }
- if @issue.closed?
@@ -12,11 +12,11 @@
&middot; created by #{link_to_member(@project, @issue.author)} #{issue_timestamp(@issue)}
.pull-right
- - if can?(current_user, :write_issue, @project)
+ - if can?(current_user, :create_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
= icon('plus')
New Issue
- - if can?(current_user, :modify_issue, @issue)
+ - if can?(current_user, :update_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
@@ -31,7 +31,7 @@
= gfm escape_once(@issue.title)
%div
- if @issue.description.present?
- .description{class: can?(current_user, :modify_issue, @issue) ? 'js-task-list-container' : ''}
+ .description{class: can?(current_user, :update_issue, @issue) ? 'js-task-list-container' : ''}
.wiki
= preserve do
= markdown(@issue.description)
diff --git a/app/views/projects/issues/update.js.haml b/app/views/projects/issues/update.js.haml
index 1d38662bff8..b7735aaf3c1 100644
--- a/app/views/projects/issues/update.js.haml
+++ b/app/views/projects/issues/update.js.haml
@@ -1,17 +1,3 @@
-- if params[:status_only]
- - if @issue.valid?
- :plain
- $("##{dom_id(@issue)}").fadeOut();
-- elsif params[:issue_context]
- $('.context').html("#{escape_javascript(render partial: 'issue_context', locals: { issue: @issue })}");
- $('.context').effect('highlight');
- - if @issue.milestone
- $('.milestone-nav-link').replaceWith("<span class='milestone-nav-link'>| <span class='light'>Milestone</span> #{escape_javascript(link_to @issue.milestone.title, namespace_project_milestone_path(@issue.project.namespace, @issue.project, @issue.milestone))}</span>")
- - else
- $('.milestone-nav-link').html('')
-
-
-$('select.select2').select2({width: 'resolve', dropdownAutoWidth: true})
-$('.edit-issue.inline-update input[type="submit"]').hide();
-new UsersSelect()
+$('.context').html("#{escape_javascript(render 'shared/issuable/context', issuable: @issue)}");
+$('.context').effect('highlight')
new Issue();
diff --git a/app/views/projects/labels/_form.html.haml b/app/views/projects/labels/_form.html.haml
index d791ed3410c..534c545329b 100644
--- a/app/views/projects/labels/_form.html.haml
+++ b/app/views/projects/labels/_form.html.haml
@@ -1,4 +1,4 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @label], html: { class: 'form-horizontal label-form' } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @label], html: { class: 'form-horizontal label-form js-requires-input' } do |f|
-if @label.errors.any?
.row
.col-sm-offset-2.col-sm-10
diff --git a/app/views/projects/labels/_label.html.haml b/app/views/projects/labels/_label.html.haml
index 7fa1ee53f76..c6ebfa281a1 100644
--- a/app/views/projects/labels/_label.html.haml
+++ b/app/views/projects/labels/_label.html.haml
@@ -6,5 +6,5 @@
= pluralize label.open_issues_count, 'open issue'
- if can? current_user, :admin_label, @project
- = 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?"}
+ = link_to 'Edit', edit_namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm'
+ = link_to 'Remove', namespace_project_label_path(@project.namespace, @project, label), class: 'btn btn-sm btn-remove remove-row', method: :delete, remote: true, data: {confirm: "Remove this label? Are you sure?"}
diff --git a/app/views/projects/merge_requests/_discussion.html.haml b/app/views/projects/merge_requests/_discussion.html.haml
index eb3dba6858d..f855dfec321 100644
--- a/app/views/projects/merge_requests/_discussion.html.haml
+++ b/app/views/projects/merge_requests/_discussion.html.haml
@@ -1,5 +1,5 @@
- content_for :note_actions do
- - if can?(current_user, :modify_merge_request, @merge_request)
+ - if can?(current_user, :update_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?
@@ -20,7 +20,7 @@
= cross_project_reference(@project, @merge_request)
%hr
.context
- = render partial: 'projects/merge_requests/show/context', locals: { merge_request: @merge_request }
+ = render 'shared/issuable/context', issuable: @merge_request
- if @merge_request.labels.any?
.issuable-context-title
diff --git a/app/views/projects/merge_requests/_form.html.haml b/app/views/projects/merge_requests/_form.html.haml
index be73f087449..9cf389dbe38 100644
--- a/app/views/projects/merge_requests/_form.html.haml
+++ b/app/views/projects/merge_requests/_form.html.haml
@@ -1,9 +1,8 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form' } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input' } do |f|
.merge-request-form-info
- = render 'projects/issuable_form', f: f, issuable: @merge_request
+ = render 'shared/issuable/form', f: f, issuable: @merge_request
:javascript
- disableButtonIfEmptyField("#merge_request_title", ".btn-save");
$('.assign-to-me-link').on('click', function(e){
$('#merge_request_assignee_id').val("#{current_user.id}").trigger("change");
e.preventDefault();
diff --git a/app/views/projects/merge_requests/_merge_request.html.haml b/app/views/projects/merge_requests/_merge_request.html.haml
index c16df27ee8f..0bcd543fee7 100644
--- a/app/views/projects/merge_requests/_merge_request.html.haml
+++ b/app/views/projects/merge_requests/_merge_request.html.haml
@@ -9,11 +9,11 @@
- if merge_request.merged?
%span
%i.fa.fa-check
- ACCEPTED
+ MERGED
- elsif merge_request.closed?
%span
%i.fa.fa-ban
- REJECTED
+ CLOSED
- else
%span.hidden-xs.hidden-sm
%span.label-branch<
@@ -35,7 +35,7 @@
= 0
.merge-request-info
- = "##{merge_request.iid} opened #{time_ago_with_tooltip(merge_request.created_at, 'bottom')} by #{link_to_member(@project, merge_request.author, avatar: false)}".html_safe
+ = "##{merge_request.iid} opened #{time_ago_with_tooltip(merge_request.created_at, placement: 'bottom')} by #{link_to_member(@project, merge_request.author, avatar: false)}".html_safe
- if merge_request.votes_count > 0
= render 'votes/votes_inline', votable: merge_request
- if merge_request.milestone_id?
@@ -48,4 +48,4 @@
= merge_request.task_status
.pull-right.hidden-xs
- %small updated #{time_ago_with_tooltip(merge_request.updated_at, 'bottom', 'merge_request_updated_ago')}
+ %small updated #{time_ago_with_tooltip(merge_request.updated_at, placement: 'bottom', html_class: 'merge_request_updated_ago')}
diff --git a/app/views/projects/merge_requests/_new_compare.html.haml b/app/views/projects/merge_requests/_new_compare.html.haml
index e611b23bca6..ff9c0cdb283 100644
--- a/app/views/projects/merge_requests/_new_compare.html.haml
+++ b/app/views/projects/merge_requests/_new_compare.html.haml
@@ -1,6 +1,6 @@
%p.lead Compare branches for new Merge Request
-= 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|
+= 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 js-requires-input" } do |f|
.hide.alert.alert-danger.mr-compare-errors
.merge-request-branches.row
.col-md-6
@@ -8,9 +8,9 @@
.panel-heading
%strong Source branch
.panel-body
- = f.select(:source_project_id, [[@merge_request.source_project_path,@merge_request.source_project.id]] , {}, { class: 'source_project select2 span3', disabled: @merge_request.persisted? })
+ = f.select(:source_project_id, [[@merge_request.source_project_path,@merge_request.source_project.id]] , {}, { class: 'source_project select2 span3', disabled: @merge_request.persisted?, required: true })
&nbsp;
- = f.select(:source_branch, @merge_request.source_branches, { include_blank: "Select branch" }, {class: 'source_branch select2 span2'})
+ = f.select(:source_branch, @merge_request.source_branches, { include_blank: "Select branch" }, {class: 'source_branch select2 span2', required: true})
.panel-footer
.mr_source_commit
@@ -20,9 +20,9 @@
%strong Target branch
.panel-body
- projects = @project.forked_from_project.nil? ? [@project] : [@project, @project.forked_from_project]
- = f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace', f.object.target_project_id), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted? })
+ = f.select(:target_project_id, options_from_collection_for_select(projects, 'id', 'path_with_namespace', f.object.target_project_id), {}, { class: 'target_project select2 span3', disabled: @merge_request.persisted?, required: true })
&nbsp;
- = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, {class: 'target_branch select2 span2'})
+ = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, {class: 'target_branch select2 span2', required: true})
.panel-footer
.mr_target_commit
diff --git a/app/views/projects/merge_requests/_new_submit.html.haml b/app/views/projects/merge_requests/_new_submit.html.haml
index 6792104569b..633a54f3620 100644
--- a/app/views/projects/merge_requests/_new_submit.html.haml
+++ b/app/views/projects/merge_requests/_new_submit.html.haml
@@ -9,9 +9,9 @@
%span.pull-right
= link_to 'Change branches', mr_change_branches_path(@merge_request)
%hr
-= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form' } do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], html: { class: 'merge-request-form form-horizontal gfm-form js-requires-input' } do |f|
.merge-request-form-info
- = render 'projects/issuable_form', f: f, issuable: @merge_request
+ = render 'shared/issuable/form', f: f, issuable: @merge_request
= f.hidden_field :source_project_id
= f.hidden_field :source_branch
= f.hidden_field :target_project_id
diff --git a/app/views/projects/merge_requests/_show.html.haml b/app/views/projects/merge_requests/_show.html.haml
index 5d7e73f2b28..b6d9b135c70 100644
--- a/app/views/projects/merge_requests/_show.html.haml
+++ b/app/views/projects/merge_requests/_show.html.haml
@@ -1,6 +1,6 @@
- page_title "#{@merge_request.title} (##{@merge_request.iid})", "Merge Requests"
.merge-request{'data-url' => merge_request_path(@merge_request)}
- .merge-request-details
+ .merge-request-details.issuable-details
= render "projects/merge_requests/show/mr_title"
%hr
= render "projects/merge_requests/show/mr_box"
@@ -56,7 +56,8 @@
#notes.notes.tab-pane.voting_notes
= render "projects/merge_requests/discussion"
#commits.commits.tab-pane
- = render "projects/merge_requests/show/commits"
+ - if current_page?(action: 'commits')
+ = render "projects/merge_requests/show/commits"
#diffs.diffs.tab-pane
- if current_page?(action: 'diffs')
= render "projects/merge_requests/show/diffs"
@@ -64,7 +65,6 @@
.mr-loading-status
= spinner
-
:javascript
var merge_request;
diff --git a/app/views/projects/merge_requests/branch_from.js.haml b/app/views/projects/merge_requests/branch_from.js.haml
index 8372afa61b5..9210798f39c 100644
--- a/app/views/projects/merge_requests/branch_from.js.haml
+++ b/app/views/projects/merge_requests/branch_from.js.haml
@@ -1,2 +1,3 @@
:plain
$(".mr_source_commit").html("#{commit_to_html(@commit, @source_project, false)}");
+ $('.js-timeago').timeago()
diff --git a/app/views/projects/merge_requests/branch_to.js.haml b/app/views/projects/merge_requests/branch_to.js.haml
index f7ede0ded53..32fe2d535f3 100644
--- a/app/views/projects/merge_requests/branch_to.js.haml
+++ b/app/views/projects/merge_requests/branch_to.js.haml
@@ -1,2 +1,3 @@
:plain
$(".mr_target_commit").html("#{commit_to_html(@commit, @target_project, false)}");
+ $('.js-timeago').timeago()
diff --git a/app/views/projects/merge_requests/index.html.haml b/app/views/projects/merge_requests/index.html.haml
index fa591b0537e..72fbe2e27a7 100644
--- a/app/views/projects/merge_requests/index.html.haml
+++ b/app/views/projects/merge_requests/index.html.haml
@@ -1,13 +1,14 @@
- page_title "Merge Requests"
+= render 'projects/last_push'
.append-bottom-10
.pull-right
- = render 'shared/issuable_search_form', path: namespace_project_merge_requests_path(@project.namespace, @project)
+ = render 'shared/issuable/search_form', path: namespace_project_merge_requests_path(@project.namespace, @project)
- - if can? current_user, :write_merge_request, @project
+ - if can? current_user, :create_merge_request, @project
.pull-left.hidden-xs
= link_to new_namespace_project_merge_request_path(@project.namespace, @project), class: "btn btn-new", title: "New Merge Request" do
%i.fa.fa-plus
New Merge Request
- = render 'shared/issuable_filter', type: :merge_requests
+ = render 'shared/issuable/filter', type: :merge_requests
.merge-requests-holder
= render 'merge_requests'
diff --git a/app/views/projects/merge_requests/show/_context.html.haml b/app/views/projects/merge_requests/show/_context.html.haml
deleted file mode 100644
index 1d0e2e350b0..00000000000
--- a/app/views/projects/merge_requests/show/_context.html.haml
+++ /dev/null
@@ -1,48 +0,0 @@
-= form_for [@project.namespace.becomes(Namespace), @project, @merge_request], remote: true, html: {class: 'edit-merge_request inline-update js-merge-request-update'} do |f|
- %div.prepend-top-20
- .issuable-context-title
- %label
- Assignee:
- - if @merge_request.assignee
- %strong= link_to_member(@project, @merge_request.assignee, size: 24)
- - else
- none
- .issuable-context-selectbox
- - if can?(current_user, :modify_merge_request, @merge_request)
- = users_select_tag('merge_request[assignee_id]', placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: @merge_request.assignee_id, project: @target_project, 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
- = icon('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'
-
- - if current_user
- %div.prepend-top-20.clearfix
- .issuable-context-title
- %label
- Subscription:
- %button.btn.btn-block.subscribe-button{:type => 'button'}
- = icon('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/_mr_box.html.haml b/app/views/projects/merge_requests/show/_mr_box.html.haml
index b3470ba37d6..e3cd4346872 100644
--- a/app/views/projects/merge_requests/show/_mr_box.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_box.html.haml
@@ -3,7 +3,7 @@
%div
- if @merge_request.description.present?
- .description{class: can?(current_user, :modify_merge_request, @merge_request) ? 'js-task-list-container' : ''}
+ .description{class: can?(current_user, :update_merge_request, @merge_request) ? 'js-task-list-container' : ''}
.wiki
= preserve do
= markdown(@merge_request.description)
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 0690fdb769f..4e8144b4de2 100644
--- a/app/views/projects/merge_requests/show/_mr_title.html.haml
+++ b/app/views/projects/merge_requests/show/_mr_title.html.haml
@@ -1,18 +1,13 @@
%h4.page-title
.issue-box{ class: issue_box_class(@merge_request) }
- - if @merge_request.merged?
- Accepted
- - elsif @merge_request.closed?
- Rejected
- - else
- Open
+ = @merge_request.state_human_name
= "Merge Request ##{@merge_request.iid}"
%small.creator
&middot;
created by #{link_to_member(@project, @merge_request.author)} #{time_ago_with_tooltip(@merge_request.created_at)}
.issue-btn-group.pull-right
- - if can?(current_user, :modify_merge_request, @merge_request)
+ - if can?(current_user, :update_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", 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
diff --git a/app/views/projects/merge_requests/show/_participants.html.haml b/app/views/projects/merge_requests/show/_participants.html.haml
index 9c93fa55fe6..c67afe963e7 100644
--- a/app/views/projects/merge_requests/show/_participants.html.haml
+++ b/app/views/projects/merge_requests/show/_participants.html.haml
@@ -1,4 +1,4 @@
.participants
- %span #{@merge_request.participants(current_user).count} participants
- - @merge_request.participants(current_user).each do |participant|
+ %span #{@participants.count} participants
+ - @participants.each do |participant|
= link_to_member(@project, participant, name: false, size: 24)
diff --git a/app/views/projects/merge_requests/update.js.haml b/app/views/projects/merge_requests/update.js.haml
index b4df1d20737..25583b2cc6f 100644
--- a/app/views/projects/merge_requests/update.js.haml
+++ b/app/views/projects/merge_requests/update.js.haml
@@ -1,8 +1,3 @@
-- if params[:merge_request_context]
- $('.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();
+$('.context').html("#{escape_javascript(render 'shared/issuable/context', issuable: @merge_request)}");
+$('.context').effect('highlight')
+merge_request = new MergeRequest();
diff --git a/app/views/projects/merge_requests/widget/_closed.html.haml b/app/views/projects/merge_requests/widget/_closed.html.haml
index 18164ba771f..b5704c502c8 100644
--- a/app/views/projects/merge_requests/widget/_closed.html.haml
+++ b/app/views/projects/merge_requests/widget/_closed.html.haml
@@ -2,7 +2,7 @@
= render 'projects/merge_requests/widget/heading'
.mr-widget-body
%h4
- Rejected
+ Closed
- if @merge_request.closed_event
by #{link_to_member(@project, @merge_request.closed_event.author, avatar: true)}
#{time_ago_with_tooltip(@merge_request.closed_event.created_at)}
diff --git a/app/views/projects/merge_requests/widget/_heading.html.haml b/app/views/projects/merge_requests/widget/_heading.html.haml
index 107c61477e3..4cc9c652b61 100644
--- a/app/views/projects/merge_requests/widget/_heading.html.haml
+++ b/app/views/projects/merge_requests/widget/_heading.html.haml
@@ -23,6 +23,12 @@
= icon("spinner spin")
Checking for CI status for #{@merge_request.last_commit_short_sha}
+ .ci_widget.ci-not_found{style: "display:none"}
+ = icon("times")
+ %span Can not find commit in the CI server
+ for #{@merge_request.last_commit_short_sha}.
+
+
.ci_widget.ci-canceled{style: "display:none"}
= icon("times")
%span CI build canceled
diff --git a/app/views/projects/merge_requests/widget/_merged.html.haml b/app/views/projects/merge_requests/widget/_merged.html.haml
index 17c3fdacda8..a3b13140810 100644
--- a/app/views/projects/merge_requests/widget/_merged.html.haml
+++ b/app/views/projects/merge_requests/widget/_merged.html.haml
@@ -2,7 +2,7 @@
= render 'projects/merge_requests/widget/heading'
.mr-widget-body
%h4
- Accepted
+ Merged
- if @merge_request.merge_event
by #{link_to_member(@project, @merge_request.merge_event.author, avatar: true)}
#{time_ago_with_tooltip(@merge_request.merge_event.created_at)}
diff --git a/app/views/projects/merge_requests/widget/open/_accept.html.haml b/app/views/projects/merge_requests/widget/open/_accept.html.haml
index 41aa66dd76b..f5bacaf280a 100644
--- a/app/views/projects/merge_requests/widget/open/_accept.html.haml
+++ b/app/views/projects/merge_requests/widget/open/_accept.html.haml
@@ -1,4 +1,4 @@
-= form_for [:automerge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form' } do |f|
+= form_for [:automerge, @project.namespace.becomes(Namespace), @project, @merge_request], remote: true, method: :post, html: { class: 'accept-mr-form js-requires-input' } do |f|
= hidden_field_tag :authenticity_token, form_authenticity_token
.accept-merge-holder.clearfix.js-toggle-container
.accept-action
@@ -25,8 +25,6 @@
= link_to "command line", "#modal_merge_info", class: "how_to_merge_link vlink", title: "How To Merge", "data-toggle" => "modal"
:coffeescript
- disableButtonIfEmptyField '#commit_message', '.accept_merge_request'
-
$('.accept-mr-form').on 'ajax:before', ->
btn = $('.accept_merge_request')
btn.disable()
diff --git a/app/views/projects/milestones/_form.html.haml b/app/views/projects/milestones/_form.html.haml
index 5650607f31f..b93462e5bdf 100644
--- a/app/views/projects/milestones/_form.html.haml
+++ b/app/views/projects/milestones/_form.html.haml
@@ -5,7 +5,7 @@
%hr
-= form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form'} do |f|
+= form_for [@project.namespace.becomes(Namespace), @project, @milestone], html: {class: 'form-horizontal milestone-form gfm-form js-requires-input'} do |f|
-if @milestone.errors.any?
.alert.alert-danger
%ul
@@ -16,7 +16,7 @@
.form-group
= f.label :title, "Title", class: "control-label"
.col-sm-10
- = f.text_field :title, maxlength: 255, class: "form-control"
+ = f.text_field :title, maxlength: 255, class: "form-control", required: true
%p.hint Required
.form-group.milestone-description
= f.label :description, "Description", class: "control-label"
@@ -45,7 +45,6 @@
:javascript
- disableButtonIfEmptyField("#milestone_title", ".btn-save");
$( ".datepicker" ).datepicker({
dateFormat: "yy-mm-dd",
onSelect: function(dateText, inst) { $("#milestone_due_date").val(dateText) }
diff --git a/app/views/projects/milestones/_milestone.html.haml b/app/views/projects/milestones/_milestone.html.haml
index 14a0580f966..2ce5358fa74 100644
--- a/app/views/projects/milestones/_milestone.html.haml
+++ b/app/views/projects/milestones/_milestone.html.haml
@@ -5,6 +5,10 @@
%i.fa.fa-pencil-square-o
Edit
= 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"
+ = link_to namespace_project_milestone_path(milestone.project.namespace, milestone.project, milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-sm btn-remove" do
+ %i.fa.fa-trash-o
+ Remove
+
%h4
= 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?
diff --git a/app/views/projects/milestones/show.html.haml b/app/views/projects/milestones/show.html.haml
index 417eaa1b09d..7b1681df336 100644
--- a/app/views/projects/milestones/show.html.haml
+++ b/app/views/projects/milestones/show.html.haml
@@ -19,6 +19,9 @@
= 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', namespace_project_milestone_path(@project.namespace, @project, @milestone, milestone: {state_event: :activate }), method: :put, class: "btn btn-reopen btn-grouped"
+ = link_to namespace_project_milestone_path(@project.namespace, @project, @milestone), data: { confirm: 'Are you sure?' }, method: :delete, class: "btn btn-grouped btn-remove" do
+ %i.fa.fa-trash-o
+ Remove
%hr
- if @milestone.issues.any? && @milestone.can_be_closed?
@@ -62,7 +65,7 @@
%span.badge= @users.count
.pull-right
- - if can?(current_user, :write_issue, @project)
+ - if can?(current_user, :create_issue, @project)
= 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
@@ -86,10 +89,10 @@
.col-md-3
= render('merge_requests', title: 'Waiting for merge (open and assigned)', merge_requests: @merge_requests.opened.assigned, id: 'ongoing')
.col-md-3
- = render('merge_requests', title: 'Rejected (closed)', merge_requests: @merge_requests.rejected, id: 'closed')
+ = render('merge_requests', title: 'Rejected (closed)', merge_requests: @merge_requests.closed, id: 'closed')
.col-md-3
.panel.panel-primary
- .panel-heading Accepted
+ .panel-heading Merged
%ul.well-list
- @merge_requests.merged.each do |merge_request|
= render 'merge_request', merge_request: merge_request
diff --git a/app/views/projects/network/show.html.haml b/app/views/projects/network/show.html.haml
index c67a7d256a8..a88cf167511 100644
--- a/app/views/projects/network/show.html.haml
+++ b/app/views/projects/network/show.html.haml
@@ -4,8 +4,8 @@
.controls
= 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
+ = button_tag class: 'btn btn-success' do
+ = icon('search')
.inline.prepend-left-20
.checkbox.light
= label_tag :filter_ref do
@@ -16,8 +16,6 @@
= spinner nil, true
:javascript
- 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")}',
diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml
index e56d8615132..d49eacb30dd 100644
--- a/app/views/projects/new.html.haml
+++ b/app/views/projects/new.html.haml
@@ -5,13 +5,13 @@
= render 'projects/errors'
.project-edit-content
- = form_for @project, html: { class: 'new_project form-horizontal' } do |f|
+ = form_for @project, html: { class: 'new_project form-horizontal js-requires-input' } do |f|
.form-group.project-name-holder
= f.label :path, class: 'control-label' do
%strong Project path
.col-sm-10
.input-group
- = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 1, autofocus: true
+ = f.text_field :path, placeholder: "my-awesome-project", class: "form-control", tabindex: 1, autofocus: true, required: true
.input-group-addon
\.git
@@ -85,7 +85,7 @@
%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"}.
+ To migrate an SVN repository, check out #{link_to "this document", "http://doc.gitlab.com/ce/workflow/importing/migrating_from_svn.html"}.
%hr.prepend-botton-10
diff --git a/app/views/projects/notes/_edit_form.html.haml b/app/views/projects/notes/_edit_form.html.haml
index a663950f031..7472b33bb53 100644
--- a/app/views/projects/notes/_edit_form.html.haml
+++ b/app/views/projects/notes/_edit_form.html.haml
@@ -5,8 +5,8 @@
= render 'projects/zen', f: f, attr: :note, classes: 'note_text js-note-text js-task-list-field'
.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 &amp; dropping or #{link_to 'selecting them', '#', class: 'markdown-selector', tabindex: -1 }.
+ .pull-left #{link_to 'Markdown ', help_page_path('markdown', 'markdown'),{ target: '_blank', tabindex: -1 }}
+ .pull-right #{link_to 'Attach a file', '#', class: 'markdown-selector', tabindex: -1 }
.note-form-actions
.buttons
diff --git a/app/views/projects/notes/_form.html.haml b/app/views/projects/notes/_form.html.haml
index 3fb044d736e..64f98741d45 100644
--- a/app/views/projects/notes/_form.html.haml
+++ b/app/views/projects/notes/_form.html.haml
@@ -12,8 +12,14 @@
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 &amp; dropping or #{link_to "selecting them", '#', class: 'markdown-selector', tabindex: -1 }.
+ .pull-left
+ = link_to "Markdown ", help_page_path("markdown", "markdown"),{ target: '_blank', tabindex: -1 }
+ tip:
+ = random_markdown_tip
+ .pull-right
+ = link_to '#', class: 'markdown-selector', tabindex: -1 do
+ Attach a file
+ = icon('paperclip')
.error-alert
.note-form-actions
diff --git a/app/views/projects/notes/_note.html.haml b/app/views/projects/notes/_note.html.haml
index 0a77f200f56..c8d705687da 100644
--- a/app/views/projects/notes/_note.html.haml
+++ b/app/views/projects/notes/_note.html.haml
@@ -21,7 +21,7 @@
- if member
%span.note-role.label
= member.human_access
-
+
- if note.system
= link_to user_path(note.author) do
= image_tag avatar_icon(note.author_email), class: 'avatar s16', alt: ''
@@ -56,9 +56,10 @@
.note-body{class: note_editable?(note) ? 'js-task-list-container' : ''}
- .note-text
- = preserve do
- = markdown(note.note, {no_header_anchors: true})
+ = cache [note, 'markdown', user_color_scheme_class] do
+ .note-text
+ = preserve do
+ = markdown(note.note, {no_header_anchors: true})
= render 'projects/notes/edit_form', note: note
- if note.attachment.url
diff --git a/app/views/projects/notes/_notes_with_form.html.haml b/app/views/projects/notes/_notes_with_form.html.haml
index a202e74a892..04222b8f7c4 100644
--- a/app/views/projects/notes/_notes_with_form.html.haml
+++ b/app/views/projects/notes/_notes_with_form.html.haml
@@ -3,7 +3,7 @@
.js-notes-busy
.js-main-target-form
-- if can? current_user, :write_note, @project
+- if can? current_user, :create_note, @project
= render "projects/notes/form", view: params[:view]
:javascript
diff --git a/app/views/projects/notes/discussions/_active.html.haml b/app/views/projects/notes/discussions/_active.html.haml
index e7a3854701c..4f15a99d061 100644
--- a/app/views/projects/notes/discussions/_active.html.haml
+++ b/app/views/projects/notes/discussions/_active.html.haml
@@ -16,7 +16,7 @@
= link_to_member(@project, last_note.author, avatar: false)
%span.discussion-last-update
- #{time_ago_with_tooltip(last_note.updated_at, 'bottom', 'discussion_updated_ago')}
-
+ #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
+
.discussion-body.js-toggle-content
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
diff --git a/app/views/projects/notes/discussions/_commit.html.haml b/app/views/projects/notes/discussions/_commit.html.haml
index 62609cfc1c8..6903fad4a0a 100644
--- a/app/views/projects/notes/discussions/_commit.html.haml
+++ b/app/views/projects/notes/discussions/_commit.html.haml
@@ -14,7 +14,7 @@
last updated by
= link_to_member(@project, last_note.author, avatar: false)
%span.discussion-last-update
- #{time_ago_with_tooltip(last_note.updated_at, 'bottom', 'discussion_updated_ago')}
+ #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
.discussion-body.js-toggle-content
- if note.for_diff_line?
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
diff --git a/app/views/projects/notes/discussions/_outdated.html.haml b/app/views/projects/notes/discussions/_outdated.html.haml
index 52a1d342f55..218b0da3977 100644
--- a/app/views/projects/notes/discussions/_outdated.html.haml
+++ b/app/views/projects/notes/discussions/_outdated.html.haml
@@ -14,6 +14,6 @@
last updated by
= link_to_member(@project, last_note.author, avatar: false)
%span.discussion-last-update
- #{time_ago_with_tooltip(last_note.updated_at, 'bottom', 'discussion_updated_ago')}
+ #{time_ago_with_tooltip(last_note.updated_at, placement: 'bottom', html_class: 'discussion_updated_ago')}
.discussion-body.js-toggle-content.hide
= render "projects/notes/discussions/diff", discussion_notes: discussion_notes, note: note
diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml
index 2259dea0865..b90cadfb1e2 100644
--- a/app/views/projects/show.html.haml
+++ b/app/views/projects/show.html.haml
@@ -6,11 +6,54 @@
= render 'shared/no_ssh'
= render 'shared/no_password'
+= render 'projects/last_push'
= render "home_panel"
-= render 'shared/show_aside'
-.row
- %section.col-md-8
- = render 'section'
- %aside.col-md-4.project-side
- = render 'aside'
+.project-stats
+ %ul.nav.nav-pills
+ %li
+ = link_to namespace_project_commits_path(@project.namespace, @project, @ref || @repository.root_ref) do
+ = pluralize(number_with_delimiter(@project.commit_count), 'commit')
+ %li
+ = link_to namespace_project_branches_path(@project.namespace, @project) do
+ = pluralize(number_with_delimiter(@repository.branch_names.count), 'branch')
+ %li
+ = link_to namespace_project_tags_path(@project.namespace, @project) do
+ = pluralize(number_with_delimiter(@repository.tag_names.count), 'tag')
+ - if @repository.changelog
+ %li
+ = link_to changelog_url(@project) do
+ Changelog
+ - if @repository.license
+ %li
+ = link_to license_url(@project) do
+ License
+ - if @repository.contribution_guide
+ %li
+ = link_to contribution_guide_url(@project) do
+ Contribution guide
+
+- if @project.archived?
+ .text-warning.center.prepend-top-20
+ %p
+ = icon("exclamation-triangle fw")
+ Archived project! Repository is read-only
+
+%hr
+%section
+ - if prefer_readme?
+ = render 'projects/readme'
+ - else
+ = render 'projects/activity'
+
+
+- if current_user
+ - access = user_max_access_in_project(current_user, @project)
+ - if access
+ %hr
+ %p.light
+ You have #{access} access to this project.
+ - if @project.project_member_by_id(current_user)
+ = link_to leave_namespace_project_project_members_path(@project.namespace, @project),
+ data: { confirm: leave_project_message(@project) }, method: :delete, title: 'Leave project', class: 'cred' do
+ Leave this project
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index da9401bd8c1..30081673ffc 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -1,7 +1,7 @@
- page_title "Snippets"
%h3.page-title
Snippets
- - if can? current_user, :write_project_snippet, @project
+ - if can? current_user, :create_project_snippet, @project
= link_to new_namespace_project_snippet_path(@project.namespace, @project), class: "btn btn-new pull-right", title: "New Snippet" do
Add new snippet
diff --git a/app/views/projects/snippets/show.html.haml b/app/views/projects/snippets/show.html.haml
index 5725d804df3..8cbb813c758 100644
--- a/app/views/projects/snippets/show.html.haml
+++ b/app/views/projects/snippets/show.html.haml
@@ -28,7 +28,7 @@
= @snippet.file_name
.file-actions
.btn-group
- - if can?(current_user, :modify_project_snippet, @snippet)
+ - if can?(current_user, :update_project_snippet, @snippet)
= 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)
diff --git a/app/views/projects/tree/show.html.haml b/app/views/projects/tree/show.html.haml
index 04590f65b27..c9e59428e78 100644
--- a/app/views/projects/tree/show.html.haml
+++ b/app/views/projects/tree/show.html.haml
@@ -2,7 +2,9 @@
= content_for :meta_tags do
- if current_user
= auto_discovery_link_tag(:atom, namespace_project_commits_url(@project.namespace, @project, @ref, format: :atom, private_token: current_user.private_token), title: "#{@project.name}:#{@ref} commits")
-
+
+= render 'projects/last_push'
+
.tree-ref-holder
= render 'shared/ref_switcher', destination: 'tree', path: @path
diff --git a/app/views/projects/wikis/_main_links.html.haml b/app/views/projects/wikis/_main_links.html.haml
index 633214a4e86..788bb8cf1e2 100644
--- a/app/views/projects/wikis/_main_links.html.haml
+++ b/app/views/projects/wikis/_main_links.html.haml
@@ -2,7 +2,7 @@
- if (@page && @page.persisted?)
= 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)
+ - if can?(current_user, :create_wiki, @project)
= 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 693c3facb32..804a1b52dbe 100644
--- a/app/views/projects/wikis/_nav.html.haml
+++ b/app/views/projects/wikis/_nav.html.haml
@@ -10,7 +10,7 @@
%i.fa.fa-download
Git Access
- - if can?(current_user, :write_wiki, @project)
+ - if can?(current_user, :create_wiki, @project)
.pull-right
= link_to '#modal-new-wiki', class: "add-new-wiki btn btn-new", "data-toggle" => "modal" do
%i.fa.fa-plus
diff --git a/app/views/search/results/_blob.html.haml b/app/views/search/results/_blob.html.haml
index 84e9be82c44..58f58eff54d 100644
--- a/app/views/search/results/_blob.html.haml
+++ b/app/views/search/results/_blob.html.haml
@@ -1,3 +1,4 @@
+- blob = @project.repository.parse_search_result(blob)
.blob-result
.file-holder
.file-title
diff --git a/app/views/search/results/_merge_request.html.haml b/app/views/search/results/_merge_request.html.haml
index adfdd1c7506..2efa616d664 100644
--- a/app/views/search/results/_merge_request.html.haml
+++ b/app/views/search/results/_merge_request.html.haml
@@ -11,6 +11,6 @@
#{merge_request.project.name_with_namespace}
.pull-right
- if merge_request.merged?
- %span.label.label-primary Accepted
+ %span.label.label-primary Merged
- elsif merge_request.closed?
- %span.label.label-danger Rejected
+ %span.label.label-danger Closed
diff --git a/app/views/search/results/_wiki_blob.html.haml b/app/views/search/results/_wiki_blob.html.haml
index f9c5810e3d0..c03438eb952 100644
--- a/app/views/search/results/_wiki_blob.html.haml
+++ b/app/views/search/results/_wiki_blob.html.haml
@@ -1,3 +1,4 @@
+- wiki_blob = @project.repository.parse_search_result(wiki_blob)
.blob-result
.file-holder
.file-title
diff --git a/app/views/shared/_clone_panel.html.haml b/app/views/shared/_clone_panel.html.haml
index 6de2aed29ed..07672359dba 100644
--- a/app/views/shared/_clone_panel.html.haml
+++ b/app/views/shared/_clone_panel.html.haml
@@ -20,7 +20,7 @@
:"data-container" => "body"}
= gitlab_config.protocol.upcase
= text_field_tag :project_clone, default_url_to_repo(project), class: "js-select-on-focus form-control input-sm", readonly: true
- - if project.kind_of?(Project) && project.empty_repo?
+ - 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)
diff --git a/app/views/shared/_field.html.haml b/app/views/shared/_field.html.haml
index 30d37dceb30..45ec49280d2 100644
--- a/app/views/shared/_field.html.haml
+++ b/app/views/shared/_field.html.haml
@@ -1,6 +1,6 @@
- name = field[:name]
- title = field[:title] || name.humanize
-- value = service_field_value(field[:type], @service.send(name))
+- value = @service.send(name)
- type = field[:type]
- placeholder = field[:placeholder]
- choices = field[:choices]
@@ -19,6 +19,6 @@
- elsif type == 'select'
= form.select name, options_for_select(choices, value ? value : default_choice), {}, { class: "form-control" }
- elsif type == 'password'
- = form.password_field name, placeholder: value, class: 'form-control'
+ = form.password_field name, value: value, class: 'form-control'
- if help
%span.help-block= help
diff --git a/app/views/shared/_file_highlight.html.haml b/app/views/shared/_file_highlight.html.haml
index 86921f0a777..d6a2e177da1 100644
--- a/app/views/shared/_file_highlight.html.haml
+++ b/app/views/shared/_file_highlight.html.haml
@@ -1,11 +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|
+ - blob.data.lines.each_index do |index|
- offset = defined?(first_line_number) ? first_line_number : 1
- i = index + offset
- / We're not using `link_to` because it is too slow once we get to thousands of lines.
- %a{href: "#L#{i}", id: "L#{i}", rel: "#L#{i}"}
+ -# We're not using `link_to` because it is too slow once we get to thousands of lines.
+ %a{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}
%i.fa.fa-link
= i
:preserve
diff --git a/app/views/shared/_visibility_radios.html.haml b/app/views/shared/_visibility_radios.html.haml
index 02416125a72..ebe2eb0433d 100644
--- a/app/views/shared/_visibility_radios.html.haml
+++ b/app/views/shared/_visibility_radios.html.haml
@@ -1,4 +1,5 @@
- Gitlab::VisibilityLevel.values.each do |level|
+ - next if skip_level?(form_model, level)
.radio
- restricted = restricted_visibility_levels.include?(level)
= form.label "#{model_method}_#{level}" do
diff --git a/app/views/shared/issuable/_context.html.haml b/app/views/shared/issuable/_context.html.haml
new file mode 100644
index 00000000000..d1bd5ef968d
--- /dev/null
+++ b/app/views/shared/issuable/_context.html.haml
@@ -0,0 +1,50 @@
+= form_for [@project.namespace.becomes(Namespace), @project, issuable], remote: true, html: {class: 'issuable-context-form inline-update js-issuable-update'} do |f|
+ %div.prepend-top-20
+ .issuable-context-title
+ %label
+ Assignee:
+ - if issuable.assignee
+ %strong= link_to_member(@project, issuable.assignee, size: 24)
+ - else
+ none
+ .issuable-context-selectbox
+ - if can?(current_user, :"admin_#{issuable.class.to_s.underscore}", @project)
+ = users_select_tag("#{issuable.class.table_name.singularize}[assignee_id]", placeholder: 'Select assignee', class: 'custom-form-control js-select2 js-assignee', selected: issuable.assignee_id, project: @target_project, null_user: true)
+
+ %div.prepend-top-20.clearfix
+ .issuable-context-title
+ %label
+ Milestone:
+ - if issuable.milestone
+ %span.back-to-milestone
+ = link_to namespace_project_milestone_path(@project.namespace, @project, issuable.milestone) do
+ %strong
+ = icon('clock-o')
+ = issuable.milestone.title
+ - else
+ none
+ .issuable-context-selectbox
+ - if can?(current_user, :"admin_#{issuable.class.to_s.underscore}", @project)
+ = f.select(:milestone_id, milestone_options(issuable), { include_blank: 'Select milestone' }, {class: 'select2 select2-compact js-select2 js-milestone'})
+ = hidden_field_tag :issuable_context
+ = f.submit class: 'btn hide'
+
+ - if current_user
+ - subscribed = issuable.subscribed?(current_user)
+ %div.prepend-top-20.clearfix
+ .issuable-context-title
+ %label
+ Subscription:
+ %button.btn.btn-block.subscribe-button{:type => 'button'}
+ = icon('eye')
+ %span= subscribed ? 'Unsubscribe' : 'Subscribe'
+ - subscribtion_status = subscribed ? 'subscribed' : 'unsubscribed'
+ .subscription-status{data: {status: subscribtion_status}}
+ .description-block.unsubscribed{class: ( 'hidden' if subscribed )}
+ You're not receiving notifications from this thread.
+ .description-block.subscribed{class: ( 'hidden' unless subscribed )}
+ You're receiving notifications because you're subscribed to this thread.
+
+:coffeescript
+ new Subscription("#{toggle_subscription_path(issuable)}")
+ new IssuableContext()
diff --git a/app/views/shared/_issuable_filter.html.haml b/app/views/shared/issuable/_filter.html.haml
index a5187fa4ea7..0e8da8de723 100644
--- a/app/views/shared/_issuable_filter.html.haml
+++ b/app/views/shared/issuable/_filter.html.haml
@@ -12,10 +12,10 @@
= icon('check-circle')
#{state_filters_text_for(:merged, @project)}
- %li{class: ("active" if params[:state] == 'rejected')}
- = link_to page_filter_path(state: 'rejected') do
+ %li{class: ("active" if params[:state] == 'closed')}
+ = link_to page_filter_path(state: 'closed') do
= icon('ban')
- #{state_filters_text_for(:rejected, @project)}
+ #{state_filters_text_for(:closed, @project)}
- else
%li{class: ("active" if params[:state] == 'closed')}
= link_to page_filter_path(state: 'closed') do
@@ -29,11 +29,10 @@
.issues-details-filters
= form_tag page_filter_path(without: [:assignee_id, :author_id, :milestone_title, :label_name]), method: :get, class: 'filter-form' do
- - if controller.controller_name == 'issues'
+ - if controller.controller_name == 'issues' && can?(current_user, :admin_issue, @project)
.check-all-holder
= check_box_tag "check_all_issues", nil, false,
- class: "check_all_issues left",
- disabled: !can?(current_user, :modify_issue, @project)
+ class: "check_all_issues left"
.issues-other-filters
.filter-item.inline
= users_select_tag(:assignee_id, selected: params[:assignee_id],
@@ -44,11 +43,15 @@
placeholder: 'Author', class: 'trigger-submit', any_user: true, first_user: true)
.filter-item.inline.milestone-filter
- = select_tag('milestone_title', projects_milestones_options, class: "select2 trigger-submit", prompt: 'Milestone')
+ = select_tag('milestone_title', projects_milestones_options,
+ class: 'select2 trigger-submit', include_blank: true,
+ data: {placeholder: 'Milestone'})
- if @project
.filter-item.inline.labels-filter
- = select_tag('label_name', project_labels_options(@project), class: "select2 trigger-submit", prompt: 'Label')
+ = select_tag('label_name', project_labels_options(@project),
+ class: 'select2 trigger-submit', include_blank: true,
+ data: {placeholder: 'Label'})
.pull-right
= render 'shared/sort_dropdown'
@@ -64,6 +67,8 @@
= 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/projects/_issuable_form.html.haml b/app/views/shared/issuable/_form.html.haml
index 4d93c89c93a..e434e1b6b98 100644
--- a/app/views/projects/_issuable_form.html.haml
+++ b/app/views/shared/issuable/_form.html.haml
@@ -15,10 +15,10 @@
- if issuable.is_a?(MergeRequest)
%p.help-block
- if issuable.work_in_progress?
- Remove the <code>WIP</code> prefix from the title to allow this
+ Remove the <code>WIP</code> prefix from the title to allow this
<strong>Work In Progress</strong> merge request to be accepted when it's ready.
- else
- Start the title with <code>[WIP]</code> or <code>WIP:</code> to prevent a
+ Start the title with <code>[WIP]</code> or <code>WIP:</code> to prevent a
<strong>Work In Progress</strong> merge request from being accepted before it's ready.
.form-group.issuable-description
= f.label :description, 'Description', class: 'control-label'
@@ -37,65 +37,67 @@
.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, project: @target_project || @project)
- &nbsp;
- = 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
+ %hr
+- if can?(current_user, :admin_issue, @project)
+ .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, project: @target_project || @project)
+ &nbsp;
+ = 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.
+ &nbsp;
+ - if can? current_user, :admin_milestone, issuable.project
+ = link_to 'Create new milestone', new_namespace_project_milestone_path(issuable.project.namespace, issuable.project), target: :blank
+ .form-group
+ = f.label :label_ids, class: 'control-label' do
+ %i.fa.fa-tag
+ Labels
.col-sm-10
- - if milestone_options(issuable).present?
- = f.select(:milestone_id, milestone_options(issuable),
- { include_blank: 'Select milestone' }, { class: 'select2' })
+ - 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 open milestones available.
+ %span.light No labels yet.
&nbsp;
- - if can? current_user, :admin_milestone, issuable.project
- = link_to 'Create new milestone', new_namespace_project_milestone_path(issuable.project.namespace, issuable.project), target: :blank
-.form-group
- = 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.
- &nbsp;
- - if can? current_user, :admin_label, issuable.project
- = link_to 'Create new label', new_namespace_project_label_path(issuable.project.namespace, issuable.project), target: :blank
+ - 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
- if issuable.is_a?(MergeRequest)
%hr
- - unless @merge_request.persisted?
+ - if @merge_request.new_record?
.form-group
= f.label :source_branch, class: 'control-label' do
%i.fa.fa-code-fork
Source Branch
.col-sm-10
= f.select(:source_branch, [@merge_request.source_branch], { }, { class: 'source_branch select2 span2', disabled: true })
- %p.help-block
- = link_to 'Change source branch', mr_change_branches_path(@merge_request)
.form-group
= f.label :target_branch, class: 'control-label' do
%i.fa.fa-code-fork
Target Branch
.col-sm-10
- = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, { class: 'target_branch select2 span2' })
+ = f.select(:target_branch, @merge_request.target_branches, { include_blank: "Select branch" }, { class: 'target_branch select2 span2', disabled: @merge_request.new_record? })
+ - if @merge_request.new_record?
+ %p.help-block
+ = link_to 'Change branches', mr_change_branches_path(@merge_request)
.form-actions
- if !issuable.project.empty_repo? && (guide_url = contribution_guide_url(issuable.project)) && !issuable.persisted?
diff --git a/app/views/shared/_issuable_search_form.html.haml b/app/views/shared/issuable/_search_form.html.haml
index 58c3de64b77..58c3de64b77 100644
--- a/app/views/shared/_issuable_search_form.html.haml
+++ b/app/views/shared/issuable/_search_form.html.haml
diff --git a/app/views/shared/snippets/_form.html.haml b/app/views/shared/snippets/_form.html.haml
index fe25133abb0..913b6744844 100644
--- a/app/views/shared/snippets/_form.html.haml
+++ b/app/views/shared/snippets/_form.html.haml
@@ -18,7 +18,7 @@
.col-sm-10
.file-holder.snippet
.file-title
- = f.text_field :file_name, placeholder: "example.rb", class: 'form-control snippet-file-name', required: true
+ = f.text_field :file_name, placeholder: "Optionally name this file to add code highlighting, e.g. example.rb for Ruby.", class: 'form-control snippet-file-name'
.file-content.code
%pre#editor= @snippet.content
= f.hidden_field :content, class: 'snippet-file-content'
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index 70a95abde6f..089e8122918 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -36,7 +36,7 @@
= @snippet.file_name
.file-actions
.btn-group
- - if can?(current_user, :modify_personal_snippet, @snippet)
+ - if can?(current_user, :update_personal_snippet, @snippet)
= 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)
diff --git a/app/views/users/show.html.haml b/app/views/users/show.html.haml
index 1694818aef6..43d847831d6 100644
--- a/app/views/users/show.html.haml
+++ b/app/views/users/show.html.haml
@@ -9,7 +9,8 @@
.row
%section.col-md-8
.header-with-avatar
- = image_tag avatar_icon(@user.email, 90), class: "avatar avatar-tile s90", alt: ''
+ = link_to avatar_icon(@user.email, 400), target: '_blank' do
+ = image_tag avatar_icon(@user.email, 90), class: "avatar avatar-tile s90", alt: ''
%h3
= @user.name
- if @user == current_user
diff --git a/app/workers/irker_worker.rb b/app/workers/irker_worker.rb
index 84a54656df2..2d44d8d4dc6 100644
--- a/app/workers/irker_worker.rb
+++ b/app/workers/irker_worker.rb
@@ -19,7 +19,7 @@ class IrkerWorker
branch = "\x0305#{branch}\x0f"
end
- # Firsts messages are for branch creation/deletion
+ # First messages are for branch creation/deletion
send_branch_updates push_data, project, repo_name, committer, branch
# Next messages are for commits
@@ -34,7 +34,7 @@ class IrkerWorker
def init_perform(set, chans, colors)
@colors = colors
@channels = chans
- start_connection set['server_ip'], set['server_port']
+ start_connection set['server_host'], set['server_port']
end
def start_connection(irker_server, irker_port)
diff --git a/app/workers/project_cache_worker.rb b/app/workers/project_cache_worker.rb
new file mode 100644
index 00000000000..55cb6af232e
--- /dev/null
+++ b/app/workers/project_cache_worker.rb
@@ -0,0 +1,15 @@
+class ProjectCacheWorker
+ include Sidekiq::Worker
+
+ sidekiq_options queue: :default
+
+ def perform(project_id)
+ project = Project.find(project_id)
+ project.update_repository_size
+ project.update_commit_count
+
+ if project.repository.root_ref
+ project.repository.build_cache
+ end
+ end
+end
diff --git a/app/workers/repository_import_worker.rb b/app/workers/repository_import_worker.rb
index e6a50afedb1..94832872d13 100644
--- a/app/workers/repository_import_worker.rb
+++ b/app/workers/repository_import_worker.rb
@@ -28,7 +28,7 @@ class RepositoryImportWorker
project.import_finish
project.save
project.satellite.create unless project.satellite.exists?
- project.update_repository_size
+ ProjectCacheWorker.perform_async(project.id)
Gitlab::BitbucketImport::KeyDeleter.new(project).execute if project.import_type == 'bitbucket'
end
end
diff --git a/config/application.rb b/config/application.rb
index 7e899cc3b5b..a96e22211e6 100644
--- a/config/application.rb
+++ b/config/application.rb
@@ -96,6 +96,7 @@ module Gitlab
end
redis_config_hash[:namespace] = 'cache:gitlab'
+ redis_config_hash[:expires_in] = 2.weeks # Cache should not grow forever
config.cache_store = :redis_store, redis_config_hash
# This is needed for gitlab-shell
diff --git a/config/initializers/6_rack_profiler.rb b/config/initializers/6_rack_profiler.rb
index 5312fd8e89a..1d958904e8f 100644
--- a/config/initializers/6_rack_profiler.rb
+++ b/config/initializers/6_rack_profiler.rb
@@ -5,6 +5,6 @@ if Rails.env.development?
Rack::MiniProfilerRails.initialize!(Rails.application)
Rack::MiniProfiler.config.position = 'right'
- Rack::MiniProfiler.config.start_hidden = true
+ Rack::MiniProfiler.config.start_hidden = false
Rack::MiniProfiler.config.skip_paths << '/teaspoon'
end
diff --git a/config/initializers/doorkeeper.rb b/config/initializers/doorkeeper.rb
index d422acb31d6..6139ddbe6cd 100644
--- a/config/initializers/doorkeeper.rb
+++ b/config/initializers/doorkeeper.rb
@@ -6,7 +6,8 @@ Doorkeeper.configure do
# 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:
+ # Ensure user is redirected to redirect_uri after login
+ session[:user_return_to] = request.fullpath
current_user || redirect_to(new_user_session_url)
end
diff --git a/config/routes.rb b/config/routes.rb
index d60bc796fdb..2e16c3ecb39 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -149,10 +149,17 @@ Gitlab::Application.routes.draw do
namespace :admin do
resources :users, constraints: { id: /[a-zA-Z.\/0-9_\-]+/ } do
resources :keys, only: [:show, :destroy]
+ resources :identities, only: [:index, :edit, :update, :destroy]
+
member do
+ get :projects
+ get :keys
+ get :groups
put :team_update
put :block
put :unblock
+ put :unlock
+ patch :disable_two_factor
delete 'remove/:email_id', action: 'remove_email', as: 'remove_email'
end
end
@@ -202,7 +209,7 @@ Gitlab::Application.routes.draw do
#
resource :profile, only: [:show, :update] do
member do
- get :history
+ get :audit_log
get :applications
put :reset_private_token
@@ -308,6 +315,7 @@ Gitlab::Application.routes.draw do
post :toggle_star
post :markdown_preview
get :autocomplete_sources
+ get :activity
end
scope module: :projects do
@@ -473,7 +481,7 @@ Gitlab::Application.routes.draw do
end
end
- resources :milestones, except: [:destroy], constraints: { id: /\d+/ } do
+ resources :milestones, constraints: { id: /\d+/ } do
member do
put :sort_issues
put :sort_merge_requests
diff --git a/db/fixtures/development/04_project.rb b/db/fixtures/development/04_project.rb
index 87839770924..8f71198e47f 100644
--- a/db/fixtures/development/04_project.rb
+++ b/db/fixtures/development/04_project.rb
@@ -11,9 +11,42 @@ Sidekiq::Testing.inline! do
'https://github.com/twitter/flight.git',
'https://github.com/twitter/typeahead.js.git',
'https://github.com/h5bp/html5-boilerplate.git',
+ 'https://github.com/google/material-design-lite.git',
+ 'https://github.com/jlevy/the-art-of-command-line.git',
+ 'https://github.com/FreeCodeCamp/freecodecamp.git',
+ 'https://github.com/google/deepdream.git',
+ 'https://github.com/jtleek/datasharing.git',
+ 'https://github.com/WebAssembly/design.git',
+ 'https://github.com/airbnb/javascript.git',
+ 'https://github.com/tessalt/echo-chamber-js.git',
+ 'https://github.com/atom/atom.git',
+ 'https://github.com/ipselon/react-ui-builder.git',
+ 'https://github.com/mattermost/platform.git',
+ 'https://github.com/purifycss/purifycss.git',
+ 'https://github.com/facebook/nuclide.git',
+ 'https://github.com/wbkd/awesome-d3.git',
+ 'https://github.com/kilimchoi/engineering-blogs.git',
+ 'https://github.com/gilbarbara/logos.git',
+ 'https://github.com/gaearon/redux.git',
+ 'https://github.com/awslabs/s2n.git',
+ 'https://github.com/arkency/reactjs_koans.git',
+ 'https://github.com/twbs/bootstrap.git',
+ 'https://github.com/chjj/ttystudio.git',
+ 'https://github.com/DrBoolean/mostly-adequate-guide.git',
+ 'https://github.com/octocat/Spoon-Knife.git',
+ 'https://github.com/opencontainers/runc.git',
+ 'https://github.com/googlesamples/android-topeka.git'
]
- project_urls.each_with_index do |url, i|
+ # You can specify how many projects you need during seed execution
+ size = if ENV['SIZE'].present?
+ ENV['SIZE'].to_i
+ else
+ 8
+ end
+
+
+ project_urls.first(size).each_with_index do |url, i|
group_path, project_path = url.split('/')[-2..-1]
group = Group.find_by(path: group_path)
diff --git a/db/migrate/20141118150935_add_audit_event.rb b/db/migrate/20141118150935_add_audit_event.rb
new file mode 100644
index 00000000000..07383c6bbc7
--- /dev/null
+++ b/db/migrate/20141118150935_add_audit_event.rb
@@ -0,0 +1,22 @@
+class AddAuditEvent < ActiveRecord::Migration
+ def change
+ create_table :audit_events do |t|
+ t.integer :author_id, null: false
+ t.string :type, null: false
+
+ # "Namespace" where the change occurs
+ # eg. On a project, group or user
+ t.integer :entity_id, null: false
+ t.string :entity_type, null: false
+
+ # Details for the event
+ t.text :details
+
+ t.timestamps
+ end
+
+ add_index :audit_events, :author_id
+ add_index :audit_events, :type
+ add_index :audit_events, [:entity_id, :entity_type]
+ end
+end
diff --git a/db/migrate/20150620233230_add_default_otp_required_for_login_value.rb b/db/migrate/20150620233230_add_default_otp_required_for_login_value.rb
new file mode 100644
index 00000000000..8eed8678b2f
--- /dev/null
+++ b/db/migrate/20150620233230_add_default_otp_required_for_login_value.rb
@@ -0,0 +1,11 @@
+class AddDefaultOtpRequiredForLoginValue < ActiveRecord::Migration
+ def up
+ execute %q{UPDATE users SET otp_required_for_login = FALSE WHERE otp_required_for_login IS NULL}
+
+ change_column :users, :otp_required_for_login, :boolean, default: false, null: false
+ end
+
+ def down
+ change_column :users, :otp_required_for_login, :boolean, null: true
+ end
+end
diff --git a/db/migrate/20150713160110_add_project_view_to_users.rb b/db/migrate/20150713160110_add_project_view_to_users.rb
new file mode 100644
index 00000000000..fe3d206df89
--- /dev/null
+++ b/db/migrate/20150713160110_add_project_view_to_users.rb
@@ -0,0 +1,5 @@
+class AddProjectViewToUsers < ActiveRecord::Migration
+ def change
+ add_column :users, :project_view, :integer, default: 0
+ end
+end
diff --git a/db/migrate/20150717130904_add_commits_count_to_project.rb b/db/migrate/20150717130904_add_commits_count_to_project.rb
new file mode 100644
index 00000000000..9b46daa5933
--- /dev/null
+++ b/db/migrate/20150717130904_add_commits_count_to_project.rb
@@ -0,0 +1,5 @@
+class AddCommitsCountToProject < ActiveRecord::Migration
+ def change
+ add_column :projects, :commit_count, :integer, default: 0
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index f063a4868b1..a63c2d05821 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20150610065936) do
+ActiveRecord::Schema.define(version: 20150717130904) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -38,6 +38,20 @@ ActiveRecord::Schema.define(version: 20150610065936) do
t.integer "session_expire_delay", default: 10080, null: false
end
+ create_table "audit_events", force: true do |t|
+ t.integer "author_id", null: false
+ t.string "type", null: false
+ t.integer "entity_id", null: false
+ t.string "entity_type", null: false
+ t.text "details"
+ t.datetime "created_at"
+ t.datetime "updated_at"
+ end
+
+ add_index "audit_events", ["author_id"], name: "index_audit_events_on_author_id", using: :btree
+ add_index "audit_events", ["entity_id", "entity_type"], name: "index_audit_events_on_entity_id_and_entity_type", using: :btree
+ add_index "audit_events", ["type"], name: "index_audit_events_on_type", using: :btree
+
create_table "broadcast_messages", force: true do |t|
t.text "message", null: false
t.datetime "starts_at"
@@ -360,6 +374,7 @@ ActiveRecord::Schema.define(version: 20150610065936) do
t.integer "star_count", default: 0, null: false
t.string "import_type"
t.string "import_source"
+ t.integer "commit_count", default: 0
end
add_index "projects", ["created_at", "id"], name: "index_projects_on_created_at_and_id", using: :btree
@@ -499,10 +514,11 @@ ActiveRecord::Schema.define(version: 20150610065936) do
t.string "encrypted_otp_secret"
t.string "encrypted_otp_secret_iv"
t.string "encrypted_otp_secret_salt"
- t.boolean "otp_required_for_login"
+ t.boolean "otp_required_for_login", default: false, null: false
t.text "otp_backup_codes"
t.string "public_email", default: "", null: false
t.integer "dashboard", default: 0
+ t.integer "project_view", default: 0
end
add_index "users", ["admin"], name: "index_users_on_admin", using: :btree
diff --git a/doc/README.md b/doc/README.md
index 28459613252..0524fda3ed6 100644
--- a/doc/README.md
+++ b/doc/README.md
@@ -4,6 +4,7 @@
- [API](api/README.md) Automate GitLab via a simple and powerful API.
- [GitLab as OAuth2 authentication service provider](integration/oauth_provider.md). It allows you to login to other applications from GitLab.
+- [GitLab Basics](gitlab-basics/README.md) Find step by step how to start working on your commandline and on GitLab.
- [Importing to GitLab](workflow/importing/README.md).
- [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.
diff --git a/doc/api/README.md b/doc/api/README.md
index ca58c184543..f369c3fd978 100644
--- a/doc/api/README.md
+++ b/doc/api/README.md
@@ -4,7 +4,7 @@
- [Users](users.md)
- [Session](session.md)
-- [Projects](projects.md)
+- [Projects](projects.md) including setting Webhooks
- [Project Snippets](project_snippets.md)
- [Services](services.md)
- [Repositories](repositories.md)
@@ -20,6 +20,7 @@
- [System Hooks](system_hooks.md)
- [Groups](groups.md)
- [Namespaces](namespaces.md)
+- [Settings](settings.md)
## Clients
diff --git a/doc/api/merge_requests.md b/doc/api/merge_requests.md
index 7b0873a9111..bb551fc67f7 100644
--- a/doc/api/merge_requests.md
+++ b/doc/api/merge_requests.md
@@ -49,7 +49,8 @@ Parameters:
"state": "active",
"created_at": "2012-04-29T08:46:00Z"
},
- "description":"fixed login page css paddings"
+ "description":"fixed login page css paddings",
+ "work_in_progress": false
}
]
```
@@ -94,7 +95,8 @@ Parameters:
"state": "active",
"created_at": "2012-04-29T08:46:00Z"
},
- "description":"fixed login page css paddings"
+ "description":"fixed login page css paddings",
+ "work_in_progress": false
}
```
@@ -118,6 +120,7 @@ Parameters:
"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.",
+ "work_in_progress": false,
"state": "reopened",
"created_at": "2015-02-02T19:49:39.159Z",
"updated_at": "2015-02-02T20:08:49.959Z",
@@ -336,14 +339,6 @@ Parameters:
```json
{
- "author": {
- "id": 1,
- "username": "admin",
- "email": "admin@example.com",
- "name": "Administrator",
- "blocked": false,
- "created_at": "2012-04-29T08:46:00Z"
- },
"note": "text1"
}
```
diff --git a/doc/api/projects.md b/doc/api/projects.md
index 17c014019ea..10533c73a31 100644
--- a/doc/api/projects.md
+++ b/doc/api/projects.md
@@ -4,16 +4,16 @@
### Project visibility level
Project in GitLab has be either private, internal or public.
-You can determine it by `visibility_level` field in project.
+You can determine it by `visibility_level` field in project.
Constants for project visibility levels are next:
-* Private. `visibility_level` is `0`.
+* 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.
@@ -362,7 +362,7 @@ Parameters:
- `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
+On success, method returns 200 with the updated project. If parameters are
invalid, 400 is returned.
### Fork project
@@ -479,6 +479,9 @@ rely on the returned JSON structure.
## Hooks
+Also called Project Hooks and Webhooks.
+These are different for [System Hooks](system_hooks.md) that are system wide.
+
### List project hooks
Get a list of project hooks.
diff --git a/doc/api/settings.md b/doc/api/settings.md
new file mode 100644
index 00000000000..d1b93a09c02
--- /dev/null
+++ b/doc/api/settings.md
@@ -0,0 +1,88 @@
+# Application settings
+
+This API allows you to read and modify GitLab instance application settings.
+
+
+## Get current application settings:
+
+```
+GET /application/settings
+```
+
+```json
+{
+ "id": 1,
+ "default_projects_limit": 10,
+ "signup_enabled": true,
+ "signin_enabled": true,
+ "gravatar_enabled": true,
+ "sign_in_text": "",
+ "created_at": "2015-06-12T15:51:55.432Z",
+ "updated_at": "2015-06-30T13:22:42.210Z",
+ "home_page_url": "",
+ "default_branch_protection": 2,
+ "twitter_sharing_enabled": true,
+ "restricted_visibility_levels": [],
+ "max_attachment_size": 10,
+ "session_expire_delay": 10080,
+ "default_project_visibility": 0,
+ "default_snippet_visibility": 0,
+ "restricted_signup_domains": [],
+ "user_oauth_applications": true,
+ "after_sign_out_path": ""
+}
+```
+
+## Change application settings:
+
+
+
+```
+PUT /application/settings
+```
+
+Parameters:
+
+- `default_projects_limit` - project limit per user
+- `signup_enabled` - enable registration
+- `signin_enabled` - enable login via GitLab account
+- `gravatar_enabled` - enable gravatar
+- `sign_in_text` - text on login page
+- `home_page_url` - redirect to this URL when not logged in
+- `default_branch_protection` - determine if developers can push to master
+- `twitter_sharing_enabled` - allow users to share project creation in twitter
+- `restricted_visibility_levels` - restrict certain visibility levels
+- `max_attachment_size` - limit attachment size
+- `session_expire_delay` - session lifetime
+- `default_project_visibility` - what visibility level new project receives
+- `default_snippet_visibility` - what visibility level new snippet receives
+- `restricted_signup_domains` - force people to use only corporate emails for signup
+- `user_oauth_applications` - allow users to create oauth applicaitons
+- `after_sign_out_path` - where redirect user after logout
+
+All parameters are optional. You can send only one that you want to change.
+
+
+```json
+{
+ "id": 1,
+ "default_projects_limit": 10,
+ "signup_enabled": true,
+ "signin_enabled": true,
+ "gravatar_enabled": true,
+ "sign_in_text": "",
+ "created_at": "2015-06-12T15:51:55.432Z",
+ "updated_at": "2015-06-30T13:22:42.210Z",
+ "home_page_url": "",
+ "default_branch_protection": 2,
+ "twitter_sharing_enabled": true,
+ "restricted_visibility_levels": [],
+ "max_attachment_size": 10,
+ "session_expire_delay": 10080,
+ "default_project_visibility": 0,
+ "default_snippet_visibility": 0,
+ "restricted_signup_domains": [],
+ "user_oauth_applications": true,
+ "after_sign_out_path": ""
+}
+```
diff --git a/doc/api/users.md b/doc/api/users.md
index cd141daadc8..5dca77b5c7b 100644
--- a/doc/api/users.md
+++ b/doc/api/users.md
@@ -58,7 +58,8 @@ GET /users
"is_admin": false,
"avatar_url": "http://localhost:3000/uploads/user/avatar/1/cd8.jpeg",
"can_create_group": true,
- "current_sign_in_at": "2014-03-19T13:12:15Z"
+ "current_sign_in_at": "2014-03-19T13:12:15Z",
+ "two_factor_enabled": true
},
{
"id": 2,
@@ -81,7 +82,8 @@ GET /users
"can_create_group": true,
"can_create_project": true,
"projects_limit": 100,
- "current_sign_in_at": "2014-03-19T17:54:13Z"
+ "current_sign_in_at": "2014-03-19T17:54:13Z",
+ "two_factor_enabled": false
}
]
```
@@ -394,3 +396,31 @@ Parameters:
- `id` (required) - SSH key ID
Will return `200 OK` on success, or `404 Not found` if either user or key cannot be found.
+
+## Block user
+
+Blocks the specified user. Available only for admin.
+
+```
+PUT /users/:uid/block
+```
+
+Parameters:
+
+- `uid` (required) - id of specified user
+
+Will return `200 OK` on success, or `404 User Not Found` is user cannot be found.
+
+## Unblock user
+
+Unblocks the specified user. Available only for admin.
+
+```
+PUT /users/:uid/unblock
+```
+
+Parameters:
+
+- `uid` (required) - id of specified user
+
+Will return `200 OK` on success, or `404 User Not Found` is user cannot be found.
diff --git a/doc/gitlab-basics/README.md b/doc/gitlab-basics/README.md
new file mode 100644
index 00000000000..63fa7a90a26
--- /dev/null
+++ b/doc/gitlab-basics/README.md
@@ -0,0 +1,17 @@
+# GitLab basics
+
+Step-by-step guides on the basics of working with Git and GitLab.
+
+* [Start using Git on the commandline](start-using-git.md)
+
+* [Create and add your SSH Keys](create-your-ssh-keys.md)
+
+* [Command Line basic commands](command-line-commands.md)
+
+* [Basic Git commands](basic-git-commands.md)
+
+* [Create a project](create-project.md)
+
+* [Create a group](create-group.md)
+
+* [Create a branch](create-branch.md)
diff --git a/doc/gitlab-basics/basic-git-commands.md b/doc/gitlab-basics/basic-git-commands.md
new file mode 100644
index 00000000000..2b5767dd2d3
--- /dev/null
+++ b/doc/gitlab-basics/basic-git-commands.md
@@ -0,0 +1,59 @@
+# Basic Git commands
+
+### Go to the master branch to pull the latest changes from there
+```
+git checkout master
+```
+
+### Download the latest changes in the project
+This is for you to work on an up-to-date copy (it is important to do every time you work on a project), while you setup tracking branches.
+```
+git pull REMOTE NAME-OF-BRANCH -u
+```
+(REMOTE: origin) (NAME-OF-BRANCH: could be "master" or an existing branch)
+
+### Create a branch
+Spaces won't be recognized, so you need to use a hyphen or underscore.
+```
+git checkout -b NAME-OF-BRANCH
+```
+
+### Work on a branch that has already been created
+```
+git checkout NAME-OF-BRANCH
+```
+
+### View the changes you've made
+It's important to be aware of what's happening and what's the status of your changes.
+```
+git status
+```
+
+### Add changes to commit
+You'll see your changes in red when you type "git status".
+```
+git add CHANGES IN RED
+git commit -m "DESCRIBE THE INTENTION OF THE COMMIT"
+```
+
+### Send changes to gitlab.com
+```
+git push REMOTE NAME-OF-BRANCH
+```
+
+### Delete all changes in the Git repository, but leave unstaged things
+```
+git checkout .
+```
+
+### Delete all changes in the Git repository, including untracked files
+```
+git clean -f
+```
+
+### Merge created branch with master branch
+You need to be in the created branch.
+```
+git checkout NAME-OF-BRANCH
+git merge master
+```
diff --git a/doc/gitlab-basics/basicsimages/add_new_merge_request.png b/doc/gitlab-basics/basicsimages/add_new_merge_request.png
new file mode 100644
index 00000000000..9d93b217a59
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/add_new_merge_request.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/add_sshkey.png b/doc/gitlab-basics/basicsimages/add_sshkey.png
new file mode 100644
index 00000000000..2dede97aa40
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/add_sshkey.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/branch_info.png b/doc/gitlab-basics/basicsimages/branch_info.png
new file mode 100644
index 00000000000..c5e38b552a5
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/branch_info.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/branch_name.png b/doc/gitlab-basics/basicsimages/branch_name.png
new file mode 100644
index 00000000000..06e77f5eea9
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/branch_name.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/branches.png b/doc/gitlab-basics/basicsimages/branches.png
new file mode 100644
index 00000000000..c18fa83b968
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/branches.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/click-on-new-group.png b/doc/gitlab-basics/basicsimages/click-on-new-group.png
new file mode 100644
index 00000000000..94b6d5756d3
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/click-on-new-group.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/commit_changes.png b/doc/gitlab-basics/basicsimages/commit_changes.png
new file mode 100644
index 00000000000..81588336f37
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/commit_changes.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/commit_message.png b/doc/gitlab-basics/basicsimages/commit_message.png
new file mode 100644
index 00000000000..0df2c32653c
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/commit_message.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/commits.png b/doc/gitlab-basics/basicsimages/commits.png
new file mode 100644
index 00000000000..7e606539077
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/commits.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/compare_braches.png b/doc/gitlab-basics/basicsimages/compare_braches.png
new file mode 100644
index 00000000000..7eebaed9075
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/compare_braches.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/create_file.png b/doc/gitlab-basics/basicsimages/create_file.png
new file mode 100644
index 00000000000..688e355cca2
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/create_file.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/create_group.png b/doc/gitlab-basics/basicsimages/create_group.png
new file mode 100644
index 00000000000..57da898abdc
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/create_group.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/edit_file.png b/doc/gitlab-basics/basicsimages/edit_file.png
new file mode 100644
index 00000000000..afa68760108
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/edit_file.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/file_located.png b/doc/gitlab-basics/basicsimages/file_located.png
new file mode 100644
index 00000000000..1def489d16b
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/file_located.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/file_name.png b/doc/gitlab-basics/basicsimages/file_name.png
new file mode 100644
index 00000000000..9ac2f1c355f
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/file_name.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/find_file.png b/doc/gitlab-basics/basicsimages/find_file.png
new file mode 100644
index 00000000000..98639149a39
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/find_file.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/find_group.png b/doc/gitlab-basics/basicsimages/find_group.png
new file mode 100644
index 00000000000..5ac33c7e953
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/find_group.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/fork.png b/doc/gitlab-basics/basicsimages/fork.png
new file mode 100644
index 00000000000..b1f94938613
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/fork.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/group_info.png b/doc/gitlab-basics/basicsimages/group_info.png
new file mode 100644
index 00000000000..e78d84e4d80
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/group_info.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/groups.png b/doc/gitlab-basics/basicsimages/groups.png
new file mode 100644
index 00000000000..b8104343afa
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/groups.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/https.png b/doc/gitlab-basics/basicsimages/https.png
new file mode 100644
index 00000000000..2a31b4cf751
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/https.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/image_file.png b/doc/gitlab-basics/basicsimages/image_file.png
new file mode 100644
index 00000000000..1061d9c5082
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/image_file.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/issue_title.png b/doc/gitlab-basics/basicsimages/issue_title.png
new file mode 100644
index 00000000000..7b69c705392
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/issue_title.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/issues.png b/doc/gitlab-basics/basicsimages/issues.png
new file mode 100644
index 00000000000..9354d05319e
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/issues.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/key.png b/doc/gitlab-basics/basicsimages/key.png
new file mode 100644
index 00000000000..321805cda98
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/key.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/merge_requests.png b/doc/gitlab-basics/basicsimages/merge_requests.png
new file mode 100644
index 00000000000..7601d40de47
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/merge_requests.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/new_issue.png b/doc/gitlab-basics/basicsimages/new_issue.png
new file mode 100644
index 00000000000..94e7503dd8b
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/new_issue.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/new_merge_request.png b/doc/gitlab-basics/basicsimages/new_merge_request.png
new file mode 100644
index 00000000000..9120d2b1ab1
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/new_merge_request.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/new_project.png b/doc/gitlab-basics/basicsimages/new_project.png
new file mode 100644
index 00000000000..ac255270a66
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/new_project.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/newbranch.png b/doc/gitlab-basics/basicsimages/newbranch.png
new file mode 100644
index 00000000000..da1a6b604ea
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/newbranch.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/paste_sshkey.png b/doc/gitlab-basics/basicsimages/paste_sshkey.png
new file mode 100644
index 00000000000..9880ddfead1
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/paste_sshkey.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/profile_settings.png b/doc/gitlab-basics/basicsimages/profile_settings.png
new file mode 100644
index 00000000000..5f2e7a7e10c
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/profile_settings.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/project_info.png b/doc/gitlab-basics/basicsimages/project_info.png
new file mode 100644
index 00000000000..6c06ff351fa
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/project_info.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/public_file_link.png b/doc/gitlab-basics/basicsimages/public_file_link.png
new file mode 100644
index 00000000000..1a60a3d880a
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/public_file_link.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/select-group.png b/doc/gitlab-basics/basicsimages/select-group.png
new file mode 100644
index 00000000000..d02c2255ff2
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/select-group.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/select-group2.png b/doc/gitlab-basics/basicsimages/select-group2.png
new file mode 100644
index 00000000000..fd40bce499b
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/select-group2.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/select_branch.png b/doc/gitlab-basics/basicsimages/select_branch.png
new file mode 100644
index 00000000000..3475b2df576
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/select_branch.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/select_project.png b/doc/gitlab-basics/basicsimages/select_project.png
new file mode 100644
index 00000000000..6d5aa439124
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/select_project.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/settings.png b/doc/gitlab-basics/basicsimages/settings.png
new file mode 100644
index 00000000000..9bf9c5a0d39
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/settings.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/shh_keys.png b/doc/gitlab-basics/basicsimages/shh_keys.png
new file mode 100644
index 00000000000..d7ef4dafe77
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/shh_keys.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/submit_new_issue.png b/doc/gitlab-basics/basicsimages/submit_new_issue.png
new file mode 100644
index 00000000000..18944417085
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/submit_new_issue.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/title_description_mr.png b/doc/gitlab-basics/basicsimages/title_description_mr.png
new file mode 100644
index 00000000000..e08eb628414
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/title_description_mr.png
Binary files differ
diff --git a/doc/gitlab-basics/basicsimages/white_space.png b/doc/gitlab-basics/basicsimages/white_space.png
new file mode 100644
index 00000000000..6363a09360e
--- /dev/null
+++ b/doc/gitlab-basics/basicsimages/white_space.png
Binary files differ
diff --git a/doc/gitlab-basics/command-line-commands.md b/doc/gitlab-basics/command-line-commands.md
new file mode 100644
index 00000000000..a8223a9b161
--- /dev/null
+++ b/doc/gitlab-basics/command-line-commands.md
@@ -0,0 +1,74 @@
+# Command Line basic commands
+
+## Start working on your project
+
+In Git, when you copy a project you say you "clone" it. To work on a git project locally (from your own computer), you will need to clone it. To do this, sign in to [GitLab.com](https://gitlab.com).
+
+When you are on your Dashboard, click on the project that you'd like to clone, which you'll find at the right side of your screen.
+
+![Select a project](basicsimages/select_project.png)
+
+To work in the project, you can copy a link to the Git repository through a SSH or a HTTPS protocol. SSH is easier to use after it's been [setup](create-your-ssh-keys.md). When you're in the project, click on the HTTPS or SSH button at the right side of your screen. Then copy the link (you'll have to paste it on your shell in the next step).
+
+![Copy the HTTPS or SSH](basicsimages/https.png)
+
+## On the command line
+
+### Clone your project
+Go to your computer's shell and type the following command:
+```
+git clone PASTE HTTPS OR SSH HERE
+```
+
+A clone of the project will be created in your computer.
+
+### Go into a project, directory or file to work in it
+```
+cd NAME-OF-PROJECT-OR-FILE
+```
+
+### Go back one directory or file
+```
+cd ../
+```
+
+### View what’s in the directory that you are in
+```
+ls
+```
+
+### Create a directory
+```
+mkdir NAME-OF-YOUR-DIRECTORY
+```
+
+### Create a README.md or file in directory
+```
+touch README.md
+nano README.md
+#### ADD YOUR INFORMATION
+#### Press: control + X
+#### Type: Y
+#### Press: enter
+```
+
+### Remove a file
+```
+rm NAME-OF-FILE
+```
+
+### Remove a directory and all of its contents
+```
+rm -rf NAME-OF-DIRECTORY
+```
+
+### View history in the command line
+```
+history
+```
+
+### Carry out commands for which the account you are using lacks authority
+You will be asked for an administrator’s password.
+```
+sudo
+```
diff --git a/doc/gitlab-basics/create-branch.md b/doc/gitlab-basics/create-branch.md
new file mode 100644
index 00000000000..a8afbfe53d7
--- /dev/null
+++ b/doc/gitlab-basics/create-branch.md
@@ -0,0 +1,38 @@
+# How to create a branch
+
+A branch is an independent line of development.
+New commits are recorded in the history for the current branch, which results in taking the source from someone’s repository (the place where the history of your work is stored) at certain point in time, and apply your own changes to it in the history of the project.
+
+To add changes to your GitLab project, you should create a branch. You can do it in your [shell](basic-git-commands.md) or in GitLab.
+
+To create a new branch in GitLab, sign in and then select a project on the right side of your screen:
+
+![Select a project](basicsimages/select_project.png)
+
+Click on "commits" on the menu on the left side of your screen:
+
+![Commits](basicsimages/commits.png)
+
+Click on the "branches" tab:
+
+![Branches](basicsimages/branches.png)
+
+Click on the "new branch" button on the right side of the screen:
+
+![New branch](basicsimages/newbranch.png)
+
+Fill out the information required:
+
+1. Add a name for your new branch (you can't add spaces, so you can use hyphens or underscores)
+
+1. On the "create from" space, add the the name of the branch you want to branch off from
+
+1. Click on the button "create branch"
+
+![Branch info](basicsimages/branch_info.png)
+
+### Note:
+
+You will be able to find and select the name of your branch in the white box next to a project's name:
+
+![Branch name](basicsimages/branch_name.png)
diff --git a/doc/gitlab-basics/create-group.md b/doc/gitlab-basics/create-group.md
new file mode 100644
index 00000000000..8e168395ff7
--- /dev/null
+++ b/doc/gitlab-basics/create-group.md
@@ -0,0 +1,43 @@
+# How to create a group in GitLab
+
+## Create a group
+
+Your projects in [GitLab.com](https://gitlab.com) can be organized in 2 different ways:
+under your own namespace for single projects, such as ´your-name/project-1'; or under groups.
+If you organize your projects under a group, it works like a folder. You can manage your group members' permissions and access to the projects.
+
+To create a group, follow the instructions below:
+
+Sign in to [GitLab.com](https://gitlab.com).
+
+When you are on your Dashboard, click on "Groups" on the left menu of your screen:
+
+![Go to groups](basicsimages/select-group2.png)
+
+Click on "New group" on the top right side of your screen:
+
+![New group](basicsimages/click-on-new-group.png)
+
+Fill out the information required:
+
+1. Add a group path or group name (you can't add spaces, so you can use hyphens or underscores)
+
+1. Add details or a group description
+
+1. You can choose a group avatar if you'd like
+
+1. Click on "create group"
+
+![Group information](basicsimages/group_info.png)
+
+## Add a project to a group
+
+There are 2 different ways to add a new project to a group:
+
+* Select a group and then click on "New project" on the right side of your screen. Then you can [create a project](create-project.md)
+
+![New project](basicsimages/new_project.png)
+
+* When you are [creating a project](create-project.md), click on "create a group" on the bottom right side of your screen
+
+![Create a group](basicsimages/create_group.png)
diff --git a/doc/gitlab-basics/create-project.md b/doc/gitlab-basics/create-project.md
new file mode 100644
index 00000000000..90d40cb6c51
--- /dev/null
+++ b/doc/gitlab-basics/create-project.md
@@ -0,0 +1,21 @@
+# How to create a project in GitLab
+
+To create a new project, sign in to [GitLab.com](https://gitlab.com).
+
+Go to your Dashboard and click on "new project" on the right side of your screen.
+
+![Create a project](basicsimages/new_project.png)
+
+Fill out the required information:
+
+1. Project path or the name of your project (you can't add spaces, so you can use hyphens or underscores)
+
+1. Your project's description
+
+1. Select a [visibility level](https://gitlab.com/help/public_access/public_access)
+
+1. You can also [import your existing projects](http://doc.gitlab.com/ce/workflow/importing/README.html)
+
+1. Click on "create project"
+
+!![Project information](basicsimages/project_info.png)
diff --git a/doc/gitlab-basics/create-your-ssh-keys.md b/doc/gitlab-basics/create-your-ssh-keys.md
new file mode 100644
index 00000000000..dcd3e6ffb31
--- /dev/null
+++ b/doc/gitlab-basics/create-your-ssh-keys.md
@@ -0,0 +1,37 @@
+# How to create your SSH Keys
+
+You need to connect your computer to your GitLab account through SSH Keys. They are unique for every computer that you link your GitLab account with.
+
+## Generate your SSH Key
+
+Create an account on GitLab. Sign up and check your email for your confirmation link.
+
+After you confirm, go to [GitLab.com](https://about.gitlab.com/) and sign in to your account.
+
+## Add your SSH Key
+
+At the top right corner, click on "profile settings":
+
+![profile settings](basicsimages/profile_settings.png)
+
+On the left side menu click on "SSH Keys":
+
+![SSH Keys](basicsimages/shh_keys.png)
+
+Then click on the green button "Add SSH Key":
+
+![Add SSH Key](basicsimages/add_sshkey.png)
+
+There, you should paste the SSH Key that your command line will generate for you. Below you'll find the steps to generate it:
+
+![Paste SSH Key](basicsimages/paste_sshkey.png)
+
+## To generate an SSH Key on your command line
+
+Go to your [command line](start-using-git.md) and follow the [instructions](../ssh/README.md) to generate it.
+
+Copy the SSH Key that your command line created and paste it on the "Key" box on the GitLab page. The title will be added automatically.
+
+![Paste SSH Key](basicsimages/key.png)
+
+Now, you'll be able to use Git over SSH, instead of Git over HTTP.
diff --git a/doc/gitlab-basics/start-using-git.md b/doc/gitlab-basics/start-using-git.md
new file mode 100644
index 00000000000..5b1c6c1cd46
--- /dev/null
+++ b/doc/gitlab-basics/start-using-git.md
@@ -0,0 +1,61 @@
+# Start using Git on the command line
+
+If you want to start using a Git and GitLab, make sure that you have created an account on [GitLab.com](https://about.gitlab.com/).
+
+## Open a shell
+
+Depending on your operating system, find the shell of your preference. Here are some suggestions.
+
+- [Terminal](http://blog.teamtreehouse.com/introduction-to-the-mac-os-x-command-line) on Mac OSX
+
+- [GitBash](https://msysgit.github.io) on Windows
+
+- [Linux Terminal](http://www.howtogeek.com/140679/beginner-geek-how-to-start-using-the-linux-terminal/) on Linux
+
+## Check if Git has already been installed
+
+Git is usually preinstalled on Mac and Linux.
+
+Type the following command and then press enter:
+```
+git --version
+```
+
+You should receive a message that will tell you which Git version you have in your computer. If you don’t receive a "Git version" message, it means that you need to [download Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git).
+
+If Git doesn't automatically download, there's an option on the website to [download manually](https://git-scm.com/downloads). Then follow the steps on the installation window.
+
+After you finished installing, open a new shell and type "git --version" again to verify that it was correctly installed.
+
+## Add your Git username and set your email
+
+It is important because every Git commit that you create will use this information.
+
+On your shell, type the following command to add your username:
+```
+git config --global user.name ADD YOUR USERNAME
+```
+
+Then verify that you have the correct username:
+```
+git config --global user.name
+```
+
+To set your email address, type the following command:
+```
+git config --global user.email ADD YOUR EMAIL
+```
+
+To verify that you entered your email correctly, type:
+```
+git config --global user.email
+```
+
+You'll need to do this only once because you are using the "--global" option. It tells Git to always use this information for anything you do on that system. If you want to override this with a different username or email address for specific projects, you can run the command without the "--global" option when you’re in that project.
+
+## Check your information
+
+To view the information that you entered, type:
+```
+git config --global --list
+```
diff --git a/doc/install/installation.md b/doc/install/installation.md
index ff0361c5e52..8b918cba133 100644
--- a/doc/install/installation.md
+++ b/doc/install/installation.md
@@ -368,6 +368,9 @@ Make sure to edit the config file to match your setup:
# Change YOUR_SERVER_FQDN to the fully-qualified
# domain name of your host serving GitLab.
+ # If using Ubuntu default nginx install:
+ # either remove the default_server from the listen line
+ # or else rm -f /etc/sites-enabled/default
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 HTTPS configuration details.
diff --git a/doc/install/requirements.md b/doc/install/requirements.md
index 7a3216dd2d2..1efc1f7bddf 100644
--- a/doc/install/requirements.md
+++ b/doc/install/requirements.md
@@ -57,6 +57,7 @@ If you have enough RAM memory and a recent CPU the speed of GitLab is mainly lim
- 16 cores supports up to 10,000 users
- 32 cores supports up to 20,000 users
- 64 cores supports up to 40,000 users
+- More users? Run it on [multiple application servers](https://about.gitlab.com/high-availability/)
### Memory
@@ -64,15 +65,17 @@ You need at least 2GB of addressable memory (RAM + swap) to install and use GitL
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
-- 8GB RAM supports up to 5,000 users
-- 16GB RAM supports up to 10,000 users
-- 32GB RAM supports up to 20,000 users
-- 64GB RAM supports up to 40,000 users
-
-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.
+- 1GB RAM + 1GB swap supports up to 100 users but it will be slow
+- **2GB RAM** is the **recommended** memory size and supports up to 100 users
+- 4GB RAM supports up to 1,000 users
+- 8GB RAM supports up to 2,000 users
+- 16GB RAM supports up to 4,000 users
+- 32GB RAM supports up to 8,000 users
+- 64GB RAM supports up to 16,000 users
+- 128GB RAM supports up to 32,000 users
+- More users? Run it on [multiple application servers](https://about.gitlab.com/high-availability/)
+
+Notice: The 25 workers of Sidekiq will show up as separate processes in your process overview (such as top or htop) but they share the same RAM allocation since Sidekiq is a multithreaded application. Please see the section below about Unicorn workers for information about many you need of those.
## Unicorn Workers
@@ -106,4 +109,8 @@ On a very active server (10,000 active users) the Sidekiq process can use 1GB+ o
- 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+ \ No newline at end of file
+- IE 10+
+
+### Common UI problems with IE
+
+If you experience UI issues with Internet Explorer, please make sure that you have the `Compatibility View` mode disabled. \ No newline at end of file
diff --git a/doc/integration/README.md b/doc/integration/README.md
index 286bd34a0bd..6d856951d4e 100644
--- a/doc/integration/README.md
+++ b/doc/integration/README.md
@@ -7,6 +7,7 @@ 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, GitLab, and Google via OAuth.
+- [SAML](saml.md) Configure GitLab as a SAML 2.0 Service Provider
- [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
diff --git a/doc/integration/saml.md b/doc/integration/saml.md
index a8cc5c8f74a..4aa6dbe758a 100644
--- a/doc/integration/saml.md
+++ b/doc/integration/saml.md
@@ -75,3 +75,8 @@ At a minimum the IdP *must* provide a claim containing the user's email address,
On the sign in page there should now be a SAML button below the regular sign in form. Click the icon to begin the authentication process. If everything goes well the user will be returned to GitLab and will be signed in.
+## Troubleshooting
+
+If you see a "500 error" in GitLab when you are redirected back from the SAML sign in page, this likely indicates that GitLab could not get the email address for the SAML user.
+
+Make sure the IdP provides a claim containing the user's email address, using claim name 'email' or 'mail'. The email will be used to automatically generate the GitLab username.
diff --git a/doc/markdown/markdown.md b/doc/markdown/markdown.md
index 9c7f723c06d..322111ae9e1 100644
--- a/doc/markdown/markdown.md
+++ b/doc/markdown/markdown.md
@@ -343,6 +343,36 @@ Strikethrough uses two tildes. ~~Scratch this.~~
- Or minuses
+ Or pluses
+If a list item contains multiple paragraphs,
+each subsequent paragraph should be indented with four spaces.
+
+```no-highlight
+1. First ordered list item
+
+ Second paragraph of first item.
+2. Another item
+```
+
+1. First ordered list item
+
+ Second paragraph of first item.
+2. Another item
+
+If the second paragraph isn't indented with four spaces,
+the second list item will be incorrectly labeled as `1`.
+
+```no-highlight
+1. First ordered list item
+
+ Second paragraph of first item.
+2. Another item
+```
+
+1. First ordered list item
+
+ Second paragraph of first item.
+2. Another item
+
## Links
There are two ways to create links, inline-style and reference-style.
diff --git a/doc/permissions/permissions.md b/doc/permissions/permissions.md
index 8cfa7f9c876..70b7e17795d 100644
--- a/doc/permissions/permissions.md
+++ b/doc/permissions/permissions.md
@@ -15,6 +15,8 @@ If a user is a GitLab administrator they receive all permissions.
| Pull project code | | ✓ | ✓ | ✓ | ✓ |
| Download project | | ✓ | ✓ | ✓ | ✓ |
| Create code snippets | | ✓ | ✓ | ✓ | ✓ |
+| Manage issue tracker | | ✓ | ✓ | ✓ | ✓ |
+| Manage labels | | ✓ | ✓ | ✓ | ✓ |
| Create new merge request | | | ✓ | ✓ | ✓ |
| Create new branches | | | ✓ | ✓ | ✓ |
| Push to non-protected branches | | | ✓ | ✓ | ✓ |
@@ -22,8 +24,6 @@ If a user is a GitLab administrator they receive all permissions.
| Remove non-protected branches | | | ✓ | ✓ | ✓ |
| Add tags | | | ✓ | ✓ | ✓ |
| Write a wiki | | | ✓ | ✓ | ✓ |
-| Manage issue tracker | | | ✓ | ✓ | ✓ |
-| Manage labels | | | ✓ | ✓ | ✓ |
| Create new milestones | | | | ✓ | ✓ |
| Add new team members | | | | ✓ | ✓ |
| Push to protected branches | | | | ✓ | ✓ |
diff --git a/doc/profile/preferences.md b/doc/profile/preferences.md
index ce5f1936782..f17bbe8f2aa 100644
--- a/doc/profile/preferences.md
+++ b/doc/profile/preferences.md
@@ -30,3 +30,9 @@ will be. Setting it to **Starred Projects** will make that Dashboard view the
default when signing in or clicking the application logo in the upper left.
The default is **Your Projects**.
+
+### Default Project view
+
+It allows user to choose what content he or she want to see on project page.
+
+The default is **Readme**.
diff --git a/doc/project_services/irker.md b/doc/project_services/irker.md
index 9875bebf66b..25c0c3ad2a6 100644
--- a/doc/project_services/irker.md
+++ b/doc/project_services/irker.md
@@ -9,38 +9,43 @@ See the project homepage for further info: https://gitlab.com/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.
+repository on https://gitlab.com/esr/irker:
-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.
+```
+git clone https://gitlab.com/esr/irker.git
+```
-## Optional setup
+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.
-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 runs on the same machine, you are done. If not, you will
+need to follow the firsts steps of the next section.
-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.
+## 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 "Irker".
+1. Select the "Active" checkbox.
+1. Enter the server host address where `irkerd` runs (defaults to `localhost`)
+in the `Server host` field on the Web page
+1. Enter the server port of `irkerd` (e.g. defaults to 6659) in the
+`Server port` field on the Web page.
+1. Optional: if `Default IRC URI` 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.
+1. Specify the recipients (e.g. #channel1, user1, etc.)
+1. Save or optionally click "Test Settings".
## 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
+`,isnick` after 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/raketasks/backup_restore.md b/doc/raketasks/backup_restore.md
index ae2d465e0c1..39a13b14fba 100644
--- a/doc/raketasks/backup_restore.md
+++ b/doc/raketasks/backup_restore.md
@@ -19,7 +19,7 @@ sudo gitlab-rake gitlab:backup:create
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,
+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.
```
@@ -300,6 +300,25 @@ Example: LVM snapshots + rsync
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.
-### Note
-This documentation is for GitLab CE.
-We backup GitLab.com and make sure your data is secure, but you can't use these methods to export / backup your data yourself from GitLab.com. \ No newline at end of file
+## Troubleshooting
+
+### Restoring database backup using omnibus packages outputs warnings
+If you are using backup restore procedures you might encounter the following warnings:
+
+```
+psql:/var/opt/gitlab/backups/db/database.sql:22: ERROR: must be owner of extension plpgsql
+psql:/var/opt/gitlab/backups/db/database.sql:2931: WARNING: no privileges could be revoked for "public" (two occurences)
+psql:/var/opt/gitlab/backups/db/database.sql:2933: WARNING: no privileges were granted for "public" (two occurences)
+
+```
+
+Be advised that, backup is successfully restored in spite of these warnings.
+
+The rake task runs this as the `gitlab` user which does not have the superuser access to the database. When restore is initiated it will also run as `gitlab` user but it will also try to alter the objects it does not have access to.
+Those objects have no influence on the database backup/restore but they give this annoying warning.
+
+For more information see similar questions on postgresql issue tracker[here](http://www.postgresql.org/message-id/201110220712.30886.adrian.klaver@gmail.com) and [here](http://www.postgresql.org/message-id/2039.1177339749@sss.pgh.pa.us) as well as [stack overflow](http://stackoverflow.com/questions/4368789/error-must-be-owner-of-language-plpgsql).
+
+## Note
+This documentation is for GitLab CE.
+We backup GitLab.com and make sure your data is secure, but you can't use these methods to export / backup your data yourself from GitLab.com.
diff --git a/doc/raketasks/maintenance.md b/doc/raketasks/maintenance.md
index 2aca91d5371..69171cd1765 100644
--- a/doc/raketasks/maintenance.md
+++ b/doc/raketasks/maintenance.md
@@ -165,13 +165,18 @@ sudo -u git -H bundle exec rake cache:clear RAILS_ENV=production
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
-```
+Note that this only applies to source installations and does NOT apply to
+omnibus packages.
For installations from source:
```
cd /home/git/gitlab
sudo -u git -H bundle exec rake assets:precompile RAILS_ENV=production
```
+
+For omnibus versions, the unoptimized assets (JavaScript, CSS) are frozen at
+the release of upstream GitLab. The omnibus version includes optimized versions
+of those assets. Unless you are modifying the JavaScript / CSS code on your
+production machine after installing the package, there should be no reason to redo
+rake assets:precompile on the production machine. If you suspect that assets
+have been corrupted, you should reinstall the omnibus package.
diff --git a/doc/release/monthly.md b/doc/release/monthly.md
index 7cb0e3d84cf..ca9696e957e 100644
--- a/doc/release/monthly.md
+++ b/doc/release/monthly.md
@@ -6,6 +6,7 @@ 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.
+The release manager should be the only person pushing/merging commits to the x-y-stable branches.
## Release Manager
@@ -61,15 +62,16 @@ Xth: (3 working days before the 22nd)
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 everyone is mentioned on the blog post using `@all` (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)
+- [ ] Merge CE stable into EE stable
+- [ ] Create (hopefully final) CE, EE, CI release candidates (#LINK)
+- [ ] Create Omnibus tags and build packages for the latest release candidates
+- [ ] Update GitLab.com with the latest RC (#LINK)
+- [ ] Update ci.gitLab.com with the latest RC (#LINK)
22nd before 12AM CET:
@@ -77,7 +79,11 @@ Release before 12AM CET / 3AM PST, to make sure the majority of our users
get the new version on the 22nd and there is sufficient time in the European
workday to quickly fix any issues.
-- [ ] Release CE, EE and CI (#LINK)
+- [ ] Merge CE stable into EE stable (#LINK)
+- [ ] Create the 'x.y.0' tag with the [release tools](https://dev.gitlab.org/gitlab/release-tools) (#LINK)
+- [ ] BEFORE 11AM CET Create and push omnibus tags for x.y.0 (will auto-release the packages) (#LINK)
+- [ ] BEFORE 12AM CET Publish the release blog post (#LINK)
+- [ ] Tweet about the release (blog post) (#LINK)
- [ ] Schedule a second tweet of the release announcement at 6PM CET / 9AM PST
```
diff --git a/doc/ssh/README.md b/doc/ssh/README.md
index 0acf92fbf54..5f44f9351dd 100644
--- a/doc/ssh/README.md
+++ b/doc/ssh/README.md
@@ -30,7 +30,7 @@ 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.
+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:
@@ -77,3 +77,31 @@ information.
### Eclipse
How to add your ssh key to Eclipse: http://wiki.eclipse.org/EGit/User_Guide#Eclipse_SSH_Configuration
+
+## Tip: Non-default OpenSSH key file names or locations
+
+If, for whatever reason, you decide to specify a non-default location and filename for your Gitlab SSH key pair, you must configure your SSH client to find your Gitlab SSH private key for connections to your Gitlab server (perhaps gitlab.com). For OpenSSH clients, this is handled in the `~/.ssh/config` file with a stanza similar to the following:
+
+```
+#
+# Main gitlab.com server
+#
+Host gitlab.com
+RSAAuthentication yes
+IdentityFile ~/my-ssh-key-directory/my-gitlab-private-key-filename
+User mygitlabusername
+```
+
+Another example
+```
+#
+# Our company's internal Gitlab server
+#
+Host my-gitlab.company.com
+RSAAuthentication yes
+IdentityFile ~/my-ssh-key-directory/company-com-private-key-filename
+```
+
+Note in the gitlab.com example above a username was specified to override the default chosen by OpenSSH (your local username). This is only required if your local and remote usernames differ.
+
+Due to the wide variety of SSH clients and their very large number of configuration options, further explanation of these topics is beyond the scope of this document.
diff --git a/doc/update/6.x-or-7.x-to-7.12.md b/doc/update/6.x-or-7.x-to-7.13.md
index 5705fb360db..3e3ceb1194c 100644
--- a/doc/update/6.x-or-7.x-to-7.12.md
+++ b/doc/update/6.x-or-7.x-to-7.13.md
@@ -1,7 +1,7 @@
-# From 6.x or 7.x to 7.12
-*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.x-or-7.x-to-7.12.md) for the most up to date instructions.*
+# From 6.x or 7.x to 7.13
+*Make sure you view this [upgrade guide from the `master` branch](../../../master/doc/update/6.x-or-7.x-to-7.13.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.12.
+This allows you to upgrade any version of GitLab from 6.0 and up (including 7.0 and up) to 7.13.
## Global issue numbers
@@ -71,7 +71,7 @@ sudo -u git -H git checkout -- db/schema.rb # local changes will be restored aut
For GitLab Community Edition:
```bash
-sudo -u git -H git checkout 7-12-stable
+sudo -u git -H git checkout 7-13-stable
```
OR
@@ -79,7 +79,7 @@ OR
For GitLab Enterprise Edition:
```bash
-sudo -u git -H git checkout 7-12-stable-ee
+sudo -u git -H git checkout 7-13-stable-ee
```
## 4. Install additional packages
@@ -162,11 +162,11 @@ sudo cp lib/support/init.d/gitlab /etc/init.d/gitlab
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-12-stable:config/gitlab.yml.example
+git diff 6-0-stable:config/gitlab.yml.example 7-13-stable:config/gitlab.yml.example
```
-* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-12-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-12-stable/config/unicorn.rb.example but with your settings.
+* Make `/home/git/gitlab/config/gitlab.yml` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-13-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-13-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
@@ -182,14 +182,14 @@ 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-12-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-12-stable/lib/support/nginx/gitlab-ssl but with your settings.
+* HTTP setups: Make `/etc/nginx/sites-available/gitlab` the same as https://gitlab.com/gitlab-org/gitlab-ce/blob/7-13-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-13-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.
### Check the version of /usr/local/bin/git
If you installed Git from source into /usr/local/bin/git then please [check
-your version](7.11-to-7.12.md).
+your version](7.12-to-7.13.md).
## 9. Start application
diff --git a/doc/update/7.12-to-7.13.md b/doc/update/7.12-to-7.13.md
new file mode 100644
index 00000000000..57ebe3261b6
--- /dev/null
+++ b/doc/update/7.12-to-7.13.md
@@ -0,0 +1,129 @@
+# From 7.12 to 7.13
+
+### 0. Double-check your Git version
+
+**This notice applies only to /usr/local/bin/git**
+
+If you compiled Git from source on your GitLab server then please double-check
+that you are using a version that protects against CVE-2014-9390. For six
+months after this vulnerability became known the GitLab installation guide
+still contained instructions that would install the outdated, 'vulnerable' Git
+version 2.1.2.
+
+Run the following command to get your current Git version.
+
+```
+/usr/local/bin/git --version
+```
+
+If you see 'No such file or directory' then you did not install Git according
+to the outdated instructions from the GitLab installation guide and you can go
+to the next step 'Stop server' below.
+
+If you see a version string then it should be v1.8.5.6, v1.9.5, v2.0.5, v2.1.4,
+v2.2.1 or newer. You can use the [instructions in the GitLab source
+installation
+guide](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/install/installation.md#1-packages-dependencies)
+to install a newer version of Git.
+
+### 1. Stop server
+
+ sudo service gitlab stop
+
+### 2. Backup
+
+```bash
+cd /home/git/gitlab
+sudo -u git -H bundle exec rake gitlab:backup:create RAILS_ENV=production
+```
+
+### 3. Get latest code
+
+```bash
+sudo -u git -H git fetch --all
+sudo -u git -H git checkout -- db/schema.rb # local changes will be restored automatically
+```
+
+For GitLab Community Edition:
+
+```bash
+sudo -u git -H git checkout 7-13-stable
+```
+
+OR
+
+For GitLab Enterprise Edition:
+
+```bash
+sudo -u git -H git checkout 7-13-stable-ee
+```
+
+### 4. Update gitlab-shell
+
+```bash
+cd /home/git/gitlab-shell
+sudo -u git -H git fetch
+sudo -u git -H git checkout v2.6.3
+```
+
+### 5. 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
+```
+
+### 6. 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-12-stable:config/gitlab.yml.example origin/7-13-stable:config/gitlab.yml.example
+``````
+
+### 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, the upgrade is complete!
+
+## Things went south? Revert to previous version (7.12)
+
+### 1. Revert the code to the previous version
+Follow the [upgrade guide from 7.11 to 7.12](7.11-to-7.12.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/mysql_to_postgresql.md b/doc/update/mysql_to_postgresql.md
index 2c43cf59c1f..26605c7c3a3 100644
--- a/doc/update/mysql_to_postgresql.md
+++ b/doc/update/mysql_to_postgresql.md
@@ -16,6 +16,7 @@ git clone https://github.com/gitlabhq/mysql-postgresql-converter.git -b gitlab
cd mysql-postgresql-converter
mysqldump --compatible=postgresql --default-character-set=utf8 -r gitlabhq_production.mysql -u root gitlabhq_production -p
python db_converter.py gitlabhq_production.mysql gitlabhq_production.psql
+ed -s gitlabhq_production.psql < move_drop_indexes.ed
# Import the database dump as the application database user
sudo -u git psql -f gitlabhq_production.psql -d gitlabhq_production
@@ -56,13 +57,17 @@ sudo -u git -H git clone https://github.com/gitlabhq/mysql-postgresql-converter.
# Convert gitlabhq_production.mysql
sudo -u git -H mkdir db
sudo -u git -H python mysql-postgresql-converter/db_converter.py gitlabhq_production.mysql db/database.sql
+sudo -u git -H ed -s db/database.sql < mysql-postgresql-converter/move_drop_indexes.ed
+
+# Compress database backup
+sudo -u git -H gzip db/database.sql
# Replace the MySQL dump in TIMESTAMP_gitlab_backup.tar.
# Warning: if you forget to replace TIMESTAMP below, tar will create a new file
# 'TIMESTAMP_gitlab_backup.tar' without giving an error.
-sudo -u git -H tar rf TIMESTAMP_gitlab_backup.tar db/database.sql
+sudo -u git -H tar rf TIMESTAMP_gitlab_backup.tar db/database.sql.gz
# Done! TIMESTAMP_gitlab_backup.tar can now be restored into a Postgres GitLab
# installation. Remember to recreate the indexes after the import.
diff --git a/doc/workflow/README.md b/doc/workflow/README.md
index f1959d30139..1f39d02bdf3 100644
--- a/doc/workflow/README.md
+++ b/doc/workflow/README.md
@@ -7,7 +7,7 @@
- [Groups](groups.md)
- [Keyboard shortcuts](shortcuts.md)
- [Labels](labels.md)
-- [Notifications](notifications.md)
+- [Notification emails](notifications.md)
- [Project Features](project_features.md)
- [Project forking workflow](forking_workflow.md)
- [Protected branches](protected_branches.md)
diff --git a/doc/workflow/importing/import_projects_from_github.md b/doc/workflow/importing/import_projects_from_github.md
index aad2c63817d..3efa92cb868 100644
--- a/doc/workflow/importing/import_projects_from_github.md
+++ b/doc/workflow/importing/import_projects_from_github.md
@@ -3,6 +3,8 @@
It takes just a couple of steps to import your existing GitHub projects to GitLab. 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)
+If you want to import from a GitHub Enterprise instance, you need to use GitLab Enterprise; please see the [EE docs for the GitHub integration](http://doc.gitlab.com/ee/integration/github.html).
+
* Sign in to GitLab.com and go to your dashboard.
* To get to the importer page, you need to go to the "New project" page.
@@ -15,4 +17,4 @@ GitHub support is enabled on your GitLab instance. You can read more about GitHu
* 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.
### Note
-When you import your projects from GitHub, it is not possible to keep your labels and milestones and issue numbers won't match. We are working on improving this in the near future. \ No newline at end of file
+When you import your projects from GitHub, it is not possible to keep your labels and milestones and issue numbers won't match. We are working on improving this in the near future.
diff --git a/doc/workflow/notifications.md b/doc/workflow/notifications.md
index 17215de677e..2b5f06dd1fa 100644
--- a/doc/workflow/notifications.md
+++ b/doc/workflow/notifications.md
@@ -1,6 +1,6 @@
-# GitLab Notifications
+# GitLab Notification Emails
-GitLab has notifications system in place to notify a user of events important for the workflow.
+GitLab has a notification system in place to notify a user of events that are important for the workflow.
## Notification settings
@@ -67,5 +67,3 @@ Below is the table of events users can be notified of:
| 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_styleguide.md b/doc_styleguide.md
index db30a737f14..656bb1d17ff 100644
--- a/doc_styleguide.md
+++ b/doc_styleguide.md
@@ -4,20 +4,21 @@ This styleguide recommends best practices to improve documentation and to keep i
## Text
-* Make sure that the documentation is added in the correct directory and that there's a link to it somewhere useful.
-
-* Add only one H1 or title in each document, by adding '#' at the begining of it (when using markdown). For subtitles, use '##', '###' and so on.
-
-* Do not duplicate information.
-
-* Be brief and clear.
-
-* Whenever it applies, add documents in alphabetical order.
-
-## When adding images to a document
-
-* Create a directory to store the images with the specific name of the document where the images belong. It could be in the same directory where the .md document that you're working on is located.
-
-* Images should have a specific, non-generic name that will differentiate them.
-
-* Keep all file names in lower case. \ No newline at end of file
+- Split up long lines, this makes it much easier to review and edit. Only
+double line breaks are shown as a full line break in markdown. 80 characters
+is a good line length.
+- For subtitles, make sure to start with the largest and go down, meaning:
+`#` for the title, `##` for subtitles and `###` for subtitles of the subtitles, etc.
+- Make sure that the documentation is added in the correct directory and that there's a link to it somewhere useful.
+- Add only one H1 or title in each document, by adding '#' at the begining of it (when using markdown).
+For subtitles, use '##', '###' and so on.
+- Do not duplicate information.
+- Be brief and clear.
+- Whenever it applies, add documents in alphabetical order.
+
+## Images
+
+- Create a directory to store the images with the specific name of the document where the images belong.
+It could be in the same directory where the .md document that you're working on is located.
+- Images should have a specific, non-generic name that will differentiate them.
+- Keep all file names in lower case. \ No newline at end of file
diff --git a/docker/single/Dockerfile b/docker/Dockerfile
index a6cbf131237..05521af6963 100644
--- a/docker/single/Dockerfile
+++ b/docker/Dockerfile
@@ -7,7 +7,9 @@ RUN apt-get update -q \
ca-certificates \
openssh-server \
wget \
- apt-transport-https
+ apt-transport-https \
+ vim \
+ nano
# Download & Install GitLab
# If you run GitLab Enterprise Edition point it to a location where you have downloaded it.
@@ -23,12 +25,23 @@ RUN mkdir -p /opt/gitlab/sv/sshd/supervise \
&& ln -s /opt/gitlab/sv/sshd /opt/gitlab/service \
&& mkdir -p /var/run/sshd
+# Prepare default configuration
+RUN ( \
+ echo "" && \
+ echo "# Docker options" && \
+ echo "# Prevent Postgres from trying to allocate 25% of total memory" && \
+ echo "postgresql['shared_buffers'] = '1MB'" ) >> /etc/gitlab/gitlab.rb && \
+ mkdir -p /assets/ && \
+ cp /etc/gitlab/gitlab.rb /assets/gitlab.rb
+
# Expose web & ssh
-EXPOSE 80 22
+EXPOSE 443 80 22
+
+# Define data volumes
+VOLUME ["/etc/gitlab", "/var/opt/gitlab", "/var/log/gitlab"]
# Copy assets
COPY assets/wrapper /usr/local/bin/
-COPY assets/gitlab.rb /etc/gitlab/
# Wrapper to handle signal, trigger runit and reconfigure GitLab
CMD ["/usr/local/bin/wrapper"]
diff --git a/docker/README.md b/docker/README.md
index fb3bde5016d..e4d56cdb336 100644
--- a/docker/README.md
+++ b/docker/README.md
@@ -1,9 +1,6 @@
# GitLab Docker images
-## 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.
-Learn more on [https://about.gitlab.com](https://about.gitlab.com)
+The GitLab docker image is [available on Docker Hub](https://registry.hub.docker.com/u/gitlab/gitlab-ce/).
## After starting a container
@@ -11,152 +8,162 @@ After starting a container you can go to [http://localhost:8080/](http://localho
It might take a while before the docker container is responding to queries.
-You can check the status with something like `sudo docker logs -f 7c10172d7705`.
+You can check the status with something like `sudo docker logs -f gitlab`.
You can login to the web interface with username `root` and password `5iveL!fe`.
Next time, you can just use docker start and stop to run the container.
-## How to build the docker images
+## Run the image
-This guide will also let you know how to build docker images yourself.
-Please run all the commands from the GitLab repo root directory.
-People using boot2docker should run all the commands without sudo.
+Run the image:
+```bash
+sudo docker run --detach \
+ --publish 8443:443 --publish 8080:80 --publish 2222:22 \
+ --name gitlab \
+ --restart always \
+ --volume /srv/gitlab/config:/etc/gitlab \
+ --volume /srv/gitlab/logs:/var/log/gitlab \
+ --volume /srv/gitlab/data:/var/opt/gitlab \
+ gitlab/gitlab-ce:latest
+```
-## Choosing between the single and the app and data images
+This will download and start GitLab CE container and publish ports needed to access SSH, HTTP and HTTPS.
+All GitLab data will be stored as subdirectories of `/srv/gitlab/`.
+The container will automatically `restart` after system reboot.
-Normally docker uses a single image for one applications.
-But GitLab stores repositories and uploads in the filesystem.
-This means that upgrades of a single image are hard.
-That is why we recommend using separate app and data images.
-We'll first describe how to use a single image.
-After that we'll describe how to use the app and data images.
+After this you can login to the web interface as explained above in 'After starting a container'.
-## Single image
+## Where is the data stored?
-Get a published image from Dockerhub:
+The GitLab container uses host mounted volumes to store persistent data:
+- `/srv/gitlab/data` mounted as `/var/opt/gitlab` in the container is used for storing *application data*
+- `/srv/gitlab/logs` mounted as `/var/log/gitlab` in the container is used for storing *logs*
+- `/srv/gitlab/config` mounted as `/etc/gitlab` in the container is used for storing *configuration*
-```bash
-sudo docker pull sytse/gitlab-ce:7.10.1
-```
+You can fine tune these directories to meet your requirements.
-Run the image:
+### 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 bash in a new the context of running container, you will be able to browse all directories and use your favorite text editor:
```bash
-sudo docker run --detach --publish 8080:80 --publish 2222:22 sytse/gitlab-ce:7.10.1
+sudo docker exec -it gitlab /bin/bash
```
-After this you can login to the web interface as explained above in 'After starting a container'.
-
-Build the image:
-
+You can also edit just `/etc/gitlab/gitlab.rb`:
```bash
-sudo docker build --tag sytse/gitlab-ce:7.10.1 docker/single/
+sudo docker exec -it gitlab vi /etc/gitlab/gitlab.rb
```
-Publish the image to Dockerhub:
+**You should set the `external_url` to point to a valid URL.**
-```bash
-sudo docker push sytse/gitlab-ce
-```
+**You may also be interesting in [Enabling HTTPS](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/nginx.md#enable-https).**
+
+**To receive e-mails from GitLab you have to configure the [SMTP settings](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/doc/settings/smtp.md),
+because Docker image doesn't have a SMTP server.**
-Diagnosing commands:
+**Note** that GitLab will reconfigure itself **at each container start.** You will need to restart the container to reconfigure your GitLab:
```bash
-sudo docker run -i -t sytse/gitlab-ce:7.10.1
-sudo docker run -ti -e TERM=linux --name gitlab-ce-troubleshoot --publish 8080:80 --publish 2222:22 sytse/gitlab-ce:7.10.1 bash /usr/local/bin/wrapper
+sudo docker restart gitlab
```
-## App and data images
+For more options for configuring the container please check [Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration).
-### Get published images from Dockerhub
+## Diagnose potential problems
+Read container logs:
```bash
-sudo docker pull sytse/gitlab-data
-sudo docker pull sytse/gitlab-app:7.10.1
+sudo docker logs gitlab
```
-### Run the images
-
+Enter running container:
```bash
-sudo docker run --name gitlab-data sytse/gitlab-data /bin/true
-sudo docker run --detach --name gitlab-app --publish 8080:80 --publish 2222:22 --volumes-from gitlab-data sytse/gitlab-app:7.10.1
+sudo docker exec -it gitlab /bin/bash
```
-After this you can login to the web interface as explained above in 'After starting a container'.
-
-### Build images
+From within container you can administrer GitLab container as you would normally administer Omnibus installation: https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md.
-Build your own based on the Omnibus packages with the following commands.
+### Upgrade GitLab to newer version
+To upgrade GitLab to new version you have to do:
+1. pull new image,
```bash
-sudo docker build --tag gitlab-data docker/data/
-sudo docker build --tag gitlab-app:7.10.1 docker/app/
+sudo docker stop gitlab
```
-After this run the images:
-
+1. stop running container,
```bash
-sudo docker run --name gitlab-data gitlab-data /bin/true
-sudo docker run --detach --name gitlab-app --publish 8080:80 --publish 2222:22 --volumes-from gitlab-data gitlab-app:7.10.1
+sudo docker rm gitlab
```
-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
-
-### 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:
+1. remove existing container,
+```bash
+sudo docker pull gitlab/gitlab-ce:latest
+```
+1. create the container once again with previously specified options.
```bash
-sudo docker run -ti -e TERM=linux --rm --volumes-from gitlab-data ubuntu
-vi /etc/gitlab/gitlab.rb
+sudo docker run --detach \
+ --publish 8443:443 --publish 8080:80 --publish 2222:22 \
+ --name gitlab \
+ --restart always \
+ --volume /srv/gitlab/config:/etc/gitlab \
+ --volume /srv/gitlab/logs:/var/log/gitlab \
+ --volume /srv/gitlab/data:/var/opt/gitlab \
+ gitlab/gitlab-ce:latest
```
-**Note** that GitLab will reconfigure itself **at each container start.** You will need to restart the container to reconfigure your GitLab.
+On the first run GitLab will reconfigure and update itself.
-You can find all available options in [Omnibus GitLab documentation](https://gitlab.com/gitlab-org/omnibus-gitlab/blob/master/README.md#configuration).
+### Run GitLab CE on public IP address
-### Upgrade GitLab with app and data images
+You can make Docker to use your IP address and forward all traffic to the GitLab CE container.
+You can do that by modifying the `--publish` ([Binding container ports to the host](https://docs.docker.com/articles/networking/#binding-ports)):
-To upgrade GitLab to new versions, stop running container, create new docker image and container from that image.
+> --publish=[] : Publish a container᾿s port or a range of ports to the host format: ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort
-It Assumes that you're upgrading from 7.8.1 to 7.10.1 and you're in the updated GitLab repo root directory:
+To expose GitLab CE on IP 1.1.1.1:
```bash
-sudo docker stop gitlab-app
-sudo docker rm gitlab-app
-sudo docker build --tag gitlab-app:7.10.1 docker/app/
-sudo docker run --detach --name gitlab-app --publish 8080:80 --publish 2222:22 --volumes-from gitlab-data gitlab-app:7.10.1
+sudo docker run --detach \
+ --publish 1.1.1.1:443:443 --publish 1.1.1.1:80:80 --publish 1.1.1.1:22:22 \
+ --name gitlab \
+ --restart always \
+ --volume /srv/gitlab/config:/etc/gitlab \
+ --volume /srv/gitlab/logs:/var/log/gitlab \
+ --volume /srv/gitlab/data:/var/opt/gitlab \
+ gitlab/gitlab-ce:latest
```
-On the first run GitLab will reconfigure and update itself. If everything runs OK don't forget to cleanup the app image:
+You can then access GitLab instance at http://1.1.1.1/ and https://1.1.1.1/.
+
+### Build the image
+
+This guide will also let you know how to build docker image yourself.
+Please run the command from the GitLab repo root directory.
+People using boot2docker should run all the commands without sudo.
```bash
-sudo docker rmi gitlab-app:7.8.1
+sudo docker build --tag gitlab/gitlab-ce:latest docker/
```
-### Publish images to Dockerhub
+### Publish the image to Dockerhub
- Ensure the containers are running
- Login to Dockerhub with `sudo docker login`
-- Run the following (replace '7.10.1' with the version you're using and 'Sytse Sijbrandij' with your name):
```bash
-sudo docker commit -m "Initial commit" -a "Sytse Sijbrandij" gitlab-app sytse/gitlab-app:7.10.1
-sudo docker push sytse/gitlab-app:7.10.1
-sudo docker commit -m "Initial commit" -a "Sytse Sijbrandij" gitlab-data sytse/gitlab-data
-sudo docker push sytse/gitlab-data
+sudo docker login
+sudo docker push gitlab/gitlab-ce:latest
```
## Troubleshooting
-Please see the [troubleshooting](troubleshooting.md) file in this directory. \ No newline at end of file
+Please see the [troubleshooting](troubleshooting.md) file in this directory.
+
+Note: We use `fig.yml` to have compatibility with fig and because docker-compose also supports it.
+
+Our docker image runs chef at every start to generate GitLab configuration.
diff --git a/docker/app/Dockerfile b/docker/app/Dockerfile
deleted file mode 100644
index fe3f7f0bcd2..00000000000
--- a/docker/app/Dockerfile
+++ /dev/null
@@ -1,32 +0,0 @@
-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 \
- apt-transport-https
-
-# Download & Install GitLab
-# If you run GitLab Enterprise Edition point it to a location where you have downloaded it.
-RUN echo "deb https://packages.gitlab.com/gitlab/gitlab-ce/ubuntu/ `lsb_release -cs` main" > /etc/apt/sources.list.d/gitlab_gitlab-ce.list
-RUN wget -q -O - https://packages.gitlab.com/gpg.key | apt-key add -
-RUN apt-get update && apt-get install -yq --no-install-recommends gitlab-ce
-
-# 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"] \ No newline at end of file
diff --git a/docker/app/assets/wrapper b/docker/app/assets/wrapper
deleted file mode 100755
index 9e6e7a05903..00000000000
--- a/docker/app/assets/wrapper
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/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/single/assets/wrapper b/docker/assets/wrapper
index 966b2cab4a1..8bc8370fbc9 100755
--- a/docker/single/assets/wrapper
+++ b/docker/assets/wrapper
@@ -13,4 +13,9 @@ function entrypoint() {
gitlab-ctl tail # tail all logs
}
+if [[ ! -e /etc/gitlab/gitlab.rb ]]; then
+ cp /assets/gitlab.rb /etc/gitlab/gitlab.rb
+ chmod 0600 /etc/gitlab/gitlab.rb
+fi
+
entrypoint
diff --git a/docker/data/Dockerfile b/docker/data/Dockerfile
deleted file mode 100644
index ea0175c4aa2..00000000000
--- a/docker/data/Dockerfile
+++ /dev/null
@@ -1,8 +0,0 @@
-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
deleted file mode 100644
index 7fddf309c01..00000000000
--- a/docker/data/assets/gitlab.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# 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/fig.yml b/docker/fig.yml
new file mode 100644
index 00000000000..989551cbfe2
--- /dev/null
+++ b/docker/fig.yml
@@ -0,0 +1,2 @@
+app:
+ build: .
diff --git a/docker/marathon.json b/docker/marathon.json
new file mode 100644
index 00000000000..9b2091a8c22
--- /dev/null
+++ b/docker/marathon.json
@@ -0,0 +1,31 @@
+{
+ "id": "/gitlab",
+ "ports": [0,0],
+ "cpus": 2,
+ "mem": 2048.0,
+ "disk": 10240.0,
+ "container": {
+ "type": "DOCKER",
+ "docker": {
+ "network": "HOST",
+ "image": "gitlab/gitlab-ce:latest"
+ },
+ "volumes": [
+ {
+ "containerPath": "/etc/gitlab",
+ "hostPath": "/var/data/etc/gitlab",
+ "mode": "RW"
+ },
+ {
+ "containerPath": "/var/opt/gitlab",
+ "hostPath": "/var/data/opt/gitlab",
+ "mode": "RW"
+ },
+ {
+ "containerPath": "/var/log/gitlab",
+ "hostPath": "/var/data/log/gitlab",
+ "mode": "RW"
+ }
+ ]
+ }
+} \ No newline at end of file
diff --git a/docker/single/assets/gitlab.rb b/docker/single/assets/gitlab.rb
deleted file mode 100644
index ef84e7832d6..00000000000
--- a/docker/single/assets/gitlab.rb
+++ /dev/null
@@ -1,37 +0,0 @@
-# External URL should be your Docker instance.
-# By default, GitLab will use the Docker container hostname.
-# 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/single/marathon.json b/docker/single/marathon.json
deleted file mode 100644
index d23c2b84e0e..00000000000
--- a/docker/single/marathon.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
- "id": "/gitlab",
- "ports": [0,0],
- "cpus": 2,
- "mem": 2048.0,
- "disk": 10240.0,
- "container": {
- "type": "DOCKER",
- "docker": {
- "network": "HOST",
- "image": "sytse/gitlab-ce:7.10.1"
- }
- }
-} \ No newline at end of file
diff --git a/docker/troubleshooting.md b/docker/troubleshooting.md
index 5827f2185db..63482547daa 100644
--- a/docker/troubleshooting.md
+++ b/docker/troubleshooting.md
@@ -9,24 +9,19 @@ postgresql['log_directory'] = '/var/log/gitlab/postgresql'
# Commands
```bash
-sudo docker build --tag gitlab_image docker/
+sudo docker build --tag gitlab/gitlab-ce:latest docker/
-sudo docker rm -f gitlab_app
-sudo docker rm -f gitlab_data
+sudo docker rm -f gitlab
-sudo docker run --name gitlab_data gitlab_image /bin/true
+sudo docker exec -it gitlab vim /etc/gitlab/gitlab.rb
-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 exec gitlab tail -f /var/log/gitlab/reconfigure.log
-sudo docker run --detach --name gitlab_app --publish 8080:80 --publish 2222:22 --volumes-from gitlab_data gitlab_image
+sudo docker exec gitlab tail -f /var/log/gitlab/postgresql/current
-sudo docker run -t --rm --volumes-from gitlab_data ubuntu tail -f /var/log/gitlab/reconfigure.log
+sudo docker exec gitlab cat /var/opt/gitlab/postgresql/data/postgresql.conf | grep shared_buffers
-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
+sudo docker exec gitlab cat /etc/gitlab/gitlab.rb
```
# Interactively
@@ -37,7 +32,16 @@ sudo docker run -t --rm --volumes-from gitlab_data ubuntu cat /etc/gitlab/gitlab
# - 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
+sudo docker run --ti \
+ -e TERM=linux
+ --publish 80443:443 --publish 8080:80 --publish 2222:22 \
+ --name gitlab \
+ --restart always \
+ --volume /srv/gitlab/config:/etc/gitlab \
+ --volume /srv/gitlab/logs:/var/log/gitlab \
+ --volume /srv/gitlab/data:/var/opt/gitlab \
+ gitlab/gitlab-ce:latest \
+ bash
# Configure GitLab to redirect PostgreSQL logs
echo "postgresql['log_directory'] = '/var/log/gitlab/postgresql'" >> /etc/gitlab/gitlab.rb
@@ -64,10 +68,17 @@ free -m
# Cleanup
-Remove ALL docker containers and images (also non GitLab ones):
+Remove ALL docker containers and images (also non GitLab ones).
+**Be careful, because the `-v` also removes volumes attached to the images.**
-```
-docker rm $(docker ps -a -q)
+```bash
+# Remove all containers with attached volumes
+docker rm -v $(docker ps -a -q)
+
+# Remove all images
docker rmi $(docker images -q)
+
+# Remove GitLab persistent data
+rm -rf /srv/gitlab
```
diff --git a/features/admin/users.feature b/features/admin/users.feature
index 1a8720dd77e..6755645778a 100644
--- a/features/admin/users.feature
+++ b/features/admin/users.feature
@@ -28,7 +28,7 @@ Feature: Admin Users
When I submit modified user
Then I see user attributes changed
-@javascript
+ @javascript
Scenario: Remove users secondary email
Given I visit admin users page
And I view the user with secondary email
@@ -40,8 +40,26 @@ Feature: Admin Users
Given user "Pete" with ssh keys
And I visit admin users page
And click on user "Pete"
+ And click on ssh keys tab
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
+
+ Scenario: Show user identities
+ Given user "Pete" with twitter account
+ And I visit "Pete" identities page in admin
+ Then I should see twitter details
+
+ Scenario: Update user identities
+ Given user "Pete" with twitter account
+ And I visit "Pete" identities page in admin
+ And I modify twitter identity
+ Then I should see twitter details updated
+
+ Scenario: Remove user identities
+ Given user "Pete" with twitter account
+ And I visit "Pete" identities page in admin
+ And I remove twitter identity
+ Then I should not see twitter details
diff --git a/features/groups.feature b/features/groups.feature
index 415e43d6ae7..299e846edb0 100644
--- a/features/groups.feature
+++ b/features/groups.feature
@@ -4,6 +4,10 @@ Feature: Groups
And "John Doe" is owner of group "Owned"
And "John Doe" is guest of group "Guest"
+ Scenario: I should have back to group button
+ When I visit group "Owned" page
+ Then I should see back to dashboard button
+
@javascript
Scenario: I should see group "Owned" dashboard list
When I visit group "Owned" page
diff --git a/features/profile/active_tab.feature b/features/profile/active_tab.feature
index 1fa4ac88ddc..788b7895d72 100644
--- a/features/profile/active_tab.feature
+++ b/features/profile/active_tab.feature
@@ -23,7 +23,7 @@ Feature: Profile Active Tab
Then the active main tab should be Preferences
And no other main tabs should be active
- Scenario: On Profile History
- Given I visit profile history page
- Then the active main tab should be History
+ Scenario: On Profile Audit Log
+ Given I visit Audit Log page
+ Then the active main tab should be Audit Log
And no other main tabs should be active
diff --git a/features/profile/profile.feature b/features/profile/profile.feature
index 0dd0afde8b1..7a1345f2b37 100644
--- a/features/profile/profile.feature
+++ b/features/profile/profile.feature
@@ -63,7 +63,7 @@ Feature: Profile
Scenario: I visit history tab
Given I have activity
- When I visit profile history page
+ When I visit Audit Log page
Then I should see my activity
Scenario: I visit my user page
diff --git a/features/project/commits/comments.feature b/features/project/commits/comments.feature
index c41075d7ad4..320f008abb6 100644
--- a/features/project/commits/comments.feature
+++ b/features/project/commits/comments.feature
@@ -39,6 +39,7 @@ Feature: Project Commits Comments
@javascript
Scenario: I can delete a comment
Given I leave a comment like "XML attached"
+ Then I should see a comment saying "XML attached"
And I delete a comment
Then I should not see a comment saying "XML attached"
diff --git a/features/project/forked_merge_requests.feature b/features/project/forked_merge_requests.feature
index ad1160e3343..10bd6fec803 100644
--- a/features/project/forked_merge_requests.feature
+++ b/features/project/forked_merge_requests.feature
@@ -26,7 +26,6 @@ Feature: Project Forked Merge Requests
#And I save the merge request
#Then I should see the edited merge request
- @javascript
Scenario: I cannot submit an invalid merge request
Given I visit project "Forked Shop" merge requests page
And I click link "New Merge Request"
diff --git a/features/project/issues/issues.feature b/features/project/issues/issues.feature
index bf84e2f8e87..28cc43ef710 100644
--- a/features/project/issues/issues.feature
+++ b/features/project/issues/issues.feature
@@ -184,3 +184,16 @@ Feature: Project Issues
Then I should see that I am subscribed
When I click button "Unsubscribe"
Then I should see that I am unsubscribed
+
+ Scenario: I submit new unassigned issue as guest
+ Given I logout
+ Given public project "Community"
+ When I visit project "Community" page
+ And I visit project "Community" issues page
+ And I click link "New Issue"
+ And I should not see assignee field
+ And I should not see milestone field
+ And I should not see labels field
+ And I submit new issue "500 error on profile"
+ Then I should see issue "500 error on profile"
+
diff --git a/features/project/issues/milestones.feature b/features/project/issues/milestones.feature
index 9ac65b1257c..bfbaaec5a35 100644
--- a/features/project/issues/milestones.feature
+++ b/features/project/issues/milestones.feature
@@ -17,6 +17,10 @@ Feature: Project Issues Milestones
And I submit new milestone "v2.3"
Then I should see milestone "v2.3"
+ Scenario: I delete new milestone
+ Given I click link to remove milestone "v2.2"
+ And I should see no milestones
+
@javascript
Scenario: Listing closed issues
Given the milestone has open and closed issues
diff --git a/features/project/merge_requests.feature b/features/project/merge_requests.feature
index eb091c291e9..947f668e432 100644
--- a/features/project/merge_requests.feature
+++ b/features/project/merge_requests.feature
@@ -11,7 +11,7 @@ Feature: Project Merge Requests
And I should not see "Feature NS-03" in merge requests
Scenario: I should see rejected merge requests
- Given I click link "Rejected"
+ Given I click link "Closed"
Then I should see "Feature NS-03" in merge requests
And I should not see "Bug NS-04" in merge requests
@@ -41,6 +41,18 @@ Feature: Project Merge Requests
And I submit new merge request "Wiki Feature"
Then I should see merge request "Wiki Feature"
+ Scenario: I download a diff on a public merge request
+ Given public project "Community"
+ And "John Doe" owns public project "Community"
+ And project "Community" has "Bug CO-01" open merge request with diffs inside
+ Given I logout directly
+ And I visit merge request page "Bug CO-01"
+ And I click on "Email Patches"
+ Then I should see a patch diff
+ And I visit merge request page "Bug CO-01"
+ And I click on "Plain Diff"
+ Then I should see a patch diff
+
@javascript
Scenario: I comment on a merge request
Given I visit merge request page "Bug NS-04"
@@ -51,7 +63,7 @@ Feature: Project Merge Requests
Scenario: I comment on a merge request diff
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 click on the Changes tab
And I leave a comment like "Line is wrong" on diff
And I switch to the merge request's comments tab
Then I should see a discussion has started on diff
@@ -102,7 +114,7 @@ Feature: Project Merge Requests
Scenario: I hide comments on a merge request diff with comments in a single file
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 click on the Changes 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 here" in the second file
@@ -111,7 +123,7 @@ Feature: Project Merge Requests
Scenario: I show comments on a merge request diff with comments in a single file
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 click on the Changes tab
And I leave a comment like "Line is wrong" on line 39 of the second file
Then I should see a comment like "Line is wrong" in the second file
@@ -119,7 +131,7 @@ Feature: Project Merge Requests
Scenario: I hide comments on a merge request 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 click on the Changes 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 link "Hide inline discussion" of the second file
@@ -130,7 +142,7 @@ Feature: Project Merge Requests
Scenario: I show comments on a merge request 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 click on the Changes 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 link "Hide inline discussion" of the second file
@@ -142,7 +154,7 @@ Feature: Project Merge Requests
Scenario: I unfold diff
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 click on the Changes tab
And I unfold diff
Then I should see additional file lines
@@ -150,7 +162,7 @@ Feature: Project Merge Requests
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 click on the Changes 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
@@ -160,7 +172,7 @@ Feature: Project Merge Requests
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
+ And I click on the Changes tab
Then I should see the proper Inline and Side-by-side links
# Description preview
diff --git a/features/project/project.feature b/features/project/project.feature
index 56ae5c78d01..089ffcba14a 100644
--- a/features/project/project.feature
+++ b/features/project/project.feature
@@ -18,9 +18,22 @@ Feature: Project
Then I should see the default project avatar
And I should not see the "Remove avatar" button
+ Scenario: I should have back to group button
+ And project "Shop" belongs to group
+ And I visit project "Shop" page
+ Then I should see back to group button
+
+ Scenario: I should have back to group button
+ And I visit project "Shop" page
+ Then I should see back to dashboard button
+
+ Scenario: I should have readme on page
+ And I visit project "Shop" page
+ Then I should see project "Shop" README
+
@javascript
Scenario: I should see project activity
- When I visit project "Shop" page
+ When I visit project "Shop" activity page
Then I should see project "Shop" activity feed
Scenario: I visit edit project
@@ -38,24 +51,12 @@ Feature: Project
And change project path settings
Then I should see project with new path settings
- Scenario: I should see project readme and version
- When I visit project "Shop" page
- And I should see project "Shop" version
-
Scenario: I should change project default branch
When I visit edit project "Shop" page
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
-
Scenario: I tag a project
When I visit edit project "Shop" page
Then I should see project settings
diff --git a/features/project/shortcuts.feature b/features/project/shortcuts.feature
index cfb68bf1f50..0f71c32380b 100644
--- a/features/project/shortcuts.feature
+++ b/features/project/shortcuts.feature
@@ -3,7 +3,7 @@ Feature: Project Shortcuts
Background:
Given I sign in as a user
And I own a project
- And I visit my project's home page
+ And I visit my project's commits page
@javascript
Scenario: Navigate to files tab
@@ -12,6 +12,7 @@ Feature: Project Shortcuts
@javascript
Scenario: Navigate to commits tab
+ Given I visit my project's files page
Given I press "g" and "c"
Then the active main tab should be Commits
@@ -46,7 +47,11 @@ Feature: Project Shortcuts
Then the active main tab should be Wiki
@javascript
- Scenario: Navigate to project feed
- Given I visit my project's files page
+ Scenario: Navigate to project home
Given I press "g" and "p"
Then the active main tab should be Home
+
+ @javascript
+ Scenario: Navigate to project feed
+ Given I press "g" and "e"
+ Then the active main tab should be Activity
diff --git a/features/project/source/browse_files.feature b/features/project/source/browse_files.feature
index 90b966dd645..af68cb96ed9 100644
--- a/features/project/source/browse_files.feature
+++ b/features/project/source/browse_files.feature
@@ -45,7 +45,7 @@ Feature: Project Source Browse Files
Then I am redirected to the new file on new branch
And I should see its new content
- @javascript @tricky
+ @javascript
Scenario: I can create file in empty repo
Given I own an empty project
And I visit my empty project page
diff --git a/features/project/source/multiselect_blob.feature b/features/project/source/multiselect_blob.feature
deleted file mode 100644
index 63b7cb77a93..00000000000
--- a/features/project/source/multiselect_blob.feature
+++ /dev/null
@@ -1,85 +0,0 @@
-Feature: Project Source Multiselect Blob
- Background:
- Given I sign in as a user
- And I own project "Shop"
- And I visit ".gitignore" file in repo
-
- @javascript
- Scenario: I click line 1 in file
- When I click line 1 in file
- Then I should see "L1" as URI fragment
- And I should see line 1 highlighted
-
- @javascript
- Scenario: I shift-click line 1 in file
- When I shift-click line 1 in file
- Then I should see "L1" as URI fragment
- And I should see line 1 highlighted
-
- @javascript
- Scenario: I click line 1 then click line 2 in file
- When I click line 1 in file
- Then I should see "L1" as URI fragment
- And I should see line 1 highlighted
- Then I click line 2 in file
- Then I should see "L2" as URI fragment
- And I should see line 2 highlighted
-
- @javascript
- Scenario: I click various line numbers to test multiselect
- Then I click line 1 in file
- Then I should see "L1" as URI fragment
- And I should see line 1 highlighted
- Then I shift-click line 2 in file
- Then I should see "L1-2" as URI fragment
- And I should see lines 1-2 highlighted
- Then I shift-click line 3 in file
- Then I should see "L1-3" as URI fragment
- And I should see lines 1-3 highlighted
- Then I click line 3 in file
- Then I should see "L3" as URI fragment
- And I should see line 3 highlighted
- Then I shift-click line 1 in file
- Then I should see "L1-3" as URI fragment
- And I should see lines 1-3 highlighted
- Then I shift-click line 5 in file
- Then I should see "L1-5" as URI fragment
- And I should see lines 1-5 highlighted
- Then I shift-click line 4 in file
- Then I should see "L1-4" as URI fragment
- And I should see lines 1-4 highlighted
- Then I click line 5 in file
- Then I should see "L5" as URI fragment
- And I should see line 5 highlighted
- Then I shift-click line 3 in file
- Then I should see "L3-5" as URI fragment
- And I should see lines 3-5 highlighted
- Then I shift-click line 1 in file
- Then I should see "L1-3" as URI fragment
- And I should see lines 1-3 highlighted
- Then I shift-click line 1 in file
- Then I should see "L1" as URI fragment
- And I should see line 1 highlighted
-
- @javascript
- Scenario: I multiselect lines 1-5 and then go back and forward in history
- When I click line 1 in file
- And I shift-click line 3 in file
- And I shift-click line 2 in file
- And I shift-click line 5 in file
- Then I should see "L1-5" as URI fragment
- And I should see lines 1-5 highlighted
- Then I go back in history
- Then I should see "L1-2" as URI fragment
- And I should see lines 1-2 highlighted
- Then I go back in history
- Then I should see "L1-3" as URI fragment
- And I should see lines 1-3 highlighted
- Then I go back in history
- Then I should see "L1" as URI fragment
- And I should see line 1 highlighted
- Then I go forward in history
- And I go forward in history
- And I go forward in history
- Then I should see "L1-5" as URI fragment
- And I should see lines 1-5 highlighted
diff --git a/features/project/wiki.feature b/features/project/wiki.feature
index 7a70f348754..2ebfa3c1660 100644
--- a/features/project/wiki.feature
+++ b/features/project/wiki.feature
@@ -69,7 +69,7 @@ Feature: Project Wiki
And I click on the "Pages" button
Then I should see non-escaped link in the pages list
- @javascript @focus
+ @javascript
Scenario: Creating an invalid new page
Given I create a New page with an invalid name
Then I should see an error message
diff --git a/features/snippets/snippets.feature b/features/snippets/snippets.feature
index 6e8019c326f..4f617b6bed8 100644
--- a/features/snippets/snippets.feature
+++ b/features/snippets/snippets.feature
@@ -25,4 +25,15 @@ Feature: Snippets
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 \ No newline at end of file
+ Then I should not see "Personal snippet one" in snippets
+
+ Scenario: I create new internal snippet
+ Given I logout directly
+ And I sign in as an admin
+ Then I visit new snippet page
+ And I submit new internal snippet
+ Then I visit snippet page "Internal personal snippet one"
+ And I logout directly
+ Then I sign in as a user
+ Given I visit new snippet page
+ Then I visit snippet page "Internal personal snippet one"
diff --git a/features/steps/admin/broadcast_messages.rb b/features/steps/admin/broadcast_messages.rb
index 2ecb6f0191a..f6daf852977 100644
--- a/features/steps/admin/broadcast_messages.rb
+++ b/features/steps/admin/broadcast_messages.rb
@@ -36,6 +36,6 @@ class Spinach::Features::AdminBroadcastMessages < Spinach::FeatureSteps
step 'I should see a customized broadcast message' do
expect(page).to have_content 'Application update from 4:00 CST to 5:00 CST'
- expect(page).to have_selector %(div[style="background-color:#f2dede;color:#b94a48"])
+ expect(page).to have_selector %(div[style="background-color: #f2dede; color: #b94a48"])
end
end
diff --git a/features/steps/admin/settings.rb b/features/steps/admin/settings.rb
index 1c0b7a4b712..147a4bd7486 100644
--- a/features/steps/admin/settings.rb
+++ b/features/steps/admin/settings.rb
@@ -11,9 +11,9 @@ class Spinach::Features::AdminSettings < Spinach::FeatureSteps
end
step 'I should see application settings saved' do
- expect(current_application_settings.gravatar_enabled).to be_false
- expect(current_application_settings.home_page_url).to eq 'https://about.gitlab.com/'
- expect(page).to have_content 'Application settings saved successfully'
+ expect(current_application_settings.gravatar_enabled).to be_falsey
+ expect(current_application_settings.home_page_url).to eq "https://about.gitlab.com/"
+ expect(page).to have_content "Application settings saved successfully"
end
step 'I click on "Service Templates"' do
diff --git a/features/steps/admin/users.rb b/features/steps/admin/users.rb
index 34a3ed9f615..6c4b91586d6 100644
--- a/features/steps/admin/users.rb
+++ b/features/steps/admin/users.rb
@@ -114,4 +114,45 @@ class Spinach::Features::AdminUsers < Spinach::FeatureSteps
step 'I should see the key removed' do
expect(page).not_to have_content 'ssh-rsa Key2'
end
+
+ step 'user "Pete" with twitter account' do
+ @user = create(:user, name: 'Pete')
+ @user.identities.create!(extern_uid: '123456', provider: 'twitter')
+ end
+
+ step 'I visit "Pete" identities page in admin' do
+ allow(Gitlab::OAuth::Provider).to receive(:names).and_return(%w(twitter twitter_updated))
+ visit admin_user_identities_path(@user)
+ end
+
+ step 'I should see twitter details' do
+ expect(page).to have_content 'Pete'
+ expect(page).to have_content 'twitter'
+ end
+
+ step 'I modify twitter identity' do
+ find('.table').find(:link, 'Edit').click
+ fill_in 'identity_extern_uid', with: '654321'
+ select 'twitter_updated', from: 'identity_provider'
+ click_button 'Save changes'
+ end
+
+ step 'I should see twitter details updated' do
+ expect(page).to have_content 'Pete'
+ expect(page).to have_content 'twitter_updated'
+ expect(page).to have_content '654321'
+ end
+
+ step 'I remove twitter identity' do
+ click_link 'Delete'
+ end
+
+ step 'I should not see twitter details' do
+ expect(page).to have_content 'Pete'
+ expect(page).to_not have_content 'twitter'
+ end
+
+ step 'click on ssh keys tab' do
+ click_link 'SSH keys'
+ end
end
diff --git a/features/steps/dashboard/dashboard.rb b/features/steps/dashboard/dashboard.rb
index 945bf35ff27..cb3a80cac29 100644
--- a/features/steps/dashboard/dashboard.rb
+++ b/features/steps/dashboard/dashboard.rb
@@ -28,7 +28,7 @@ class Spinach::Features::Dashboard < Spinach::FeatureSteps
end
step 'user with name "John Doe" joined project "Shop"' do
- user = create(:user, {name: "John Doe"})
+ user = create(:user, { name: "John Doe" })
project.team << [user, :master]
Event.create(
project: project,
diff --git a/features/steps/dashboard/event_filters.rb b/features/steps/dashboard/event_filters.rb
index 834afa439a0..726b37cfde5 100644
--- a/features/steps/dashboard/event_filters.rb
+++ b/features/steps/dashboard/event_filters.rb
@@ -52,7 +52,7 @@ class Spinach::Features::EventFilters < Spinach::FeatureSteps
end
step 'this project has new member event' do
- user = create(:user, {name: "John Doe"})
+ user = create(:user, { name: "John Doe" })
Event.create(
project: @project,
author_id: user.id,
diff --git a/features/steps/dashboard/new_project.rb b/features/steps/dashboard/new_project.rb
index b4ade65ee53..d4440c1fb4d 100644
--- a/features/steps/dashboard/new_project.rb
+++ b/features/steps/dashboard/new_project.rb
@@ -10,7 +10,7 @@ class Spinach::Features::NewProject < Spinach::FeatureSteps
end
step 'I see "New project" page' do
- expect(page).to have_content("Project path")
+ expect(page).to have_content('Project path')
end
step 'I click on "Import project from GitHub"' do
diff --git a/features/steps/explore/projects.rb b/features/steps/explore/projects.rb
index 49c2f6a1253..8b498e7b4a6 100644
--- a/features/steps/explore/projects.rb
+++ b/features/steps/explore/projects.rb
@@ -145,4 +145,3 @@ class Spinach::Features::ExploreProjects < Spinach::FeatureSteps
@public_merge_request ||= MergeRequest.find_by!(title: 'Bug fix for public project')
end
end
-
diff --git a/features/steps/groups.rb b/features/steps/groups.rb
index c6c855a7c22..46e1f4d0990 100644
--- a/features/steps/groups.rb
+++ b/features/steps/groups.rb
@@ -5,6 +5,10 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
include SharedUser
include Select2Helper
+ step 'I should see back to dashboard button' do
+ expect(page).to have_content 'Back to Dashboard'
+ end
+
step 'gitlab user "Mike"' do
create(:user, name: "Mike")
end
@@ -128,14 +132,14 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
end
step 'I change group "Owned" avatar' do
- attach_file(:group_avatar, File.join(Rails.root, 'public', 'gitlab_logo.png'))
+ attach_file(:group_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif'))
click_button "Save group"
Group.find_by(name: "Owned").reload
end
step 'I should see new group "Owned" avatar' do
expect(Group.find_by(name: "Owned").avatar).to be_instance_of AvatarUploader
- expect(Group.find_by(name: "Owned").avatar.url).to eq "/uploads/group/avatar/#{ Group.find_by(name:"Owned").id }/gitlab_logo.png"
+ expect(Group.find_by(name: "Owned").avatar.url).to eq "/uploads/group/avatar/#{ Group.find_by(name:"Owned").id }/banana_sample.gif"
end
step 'I should see the "Remove avatar" button' do
@@ -143,7 +147,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
end
step 'I have group "Owned" avatar' do
- attach_file(:group_avatar, File.join(Rails.root, 'public', 'gitlab_logo.png'))
+ attach_file(:group_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif'))
click_button "Save group"
Group.find_by(name: "Owned").reload
end
@@ -154,7 +158,7 @@ class Spinach::Features::Groups < Spinach::FeatureSteps
end
step 'I should not see group "Owned" avatar' do
- expect(Group.find_by(name: "Owned").avatar?).to be_false
+ expect(Group.find_by(name: "Owned").avatar?).to eq false
end
step 'I should not see the "Remove avatar" button' do
diff --git a/features/steps/profile/active_tab.rb b/features/steps/profile/active_tab.rb
index 79e3b55f6e1..4724a326277 100644
--- a/features/steps/profile/active_tab.rb
+++ b/features/steps/profile/active_tab.rb
@@ -19,7 +19,7 @@ class Spinach::Features::ProfileActiveTab < Spinach::FeatureSteps
ensure_active_main_tab('Preferences')
end
- step 'the active main tab should be History' do
- ensure_active_main_tab('History')
+ step 'the active main tab should be Audit Log' do
+ ensure_active_main_tab('Audit Log')
end
end
diff --git a/features/steps/profile/profile.rb b/features/steps/profile/profile.rb
index 10bd307320e..2b6b8b167f6 100644
--- a/features/steps/profile/profile.rb
+++ b/features/steps/profile/profile.rb
@@ -27,14 +27,14 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step 'I change my avatar' do
- attach_file(:user_avatar, File.join(Rails.root, 'public', 'gitlab_logo.png'))
+ attach_file(:user_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif'))
click_button "Save changes"
@user.reload
end
step 'I should see new avatar' do
expect(@user.avatar).to be_instance_of AvatarUploader
- expect(@user.avatar.url).to eq "/uploads/user/avatar/#{ @user.id }/gitlab_logo.png"
+ expect(@user.avatar.url).to eq "/uploads/user/avatar/#{ @user.id }/banana_sample.gif"
end
step 'I should see the "Remove avatar" button' do
@@ -42,7 +42,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step 'I have an avatar' do
- attach_file(:user_avatar, File.join(Rails.root, 'public', 'gitlab_logo.png'))
+ attach_file(:user_avatar, File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif'))
click_button "Save changes"
@user.reload
end
@@ -53,7 +53,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step 'I should see my gravatar' do
- expect(@user.avatar?).to be_false
+ expect(@user.avatar?).to eq false
end
step 'I should not see the "Remove avatar" button' do
@@ -87,11 +87,15 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step "I should see a missing password error message" do
- expect(page).to have_content "You must provide a valid current password"
+ page.within ".flash-container" do
+ expect(page).to have_content "You must provide a valid current password"
+ end
end
step "I should see a password error message" do
- expect(page).to have_content "Password confirmation doesn't match"
+ page.within '.alert' do
+ expect(page).to have_content "Password confirmation doesn't match"
+ end
end
step 'I reset my token' do
@@ -111,7 +115,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
end
step 'I should see my activity' do
- expect(page).to have_content "#{current_user.name} closed issue"
+ expect(page).to have_content "Signed in with standard authentication"
end
step 'my password is expired' do
@@ -120,7 +124,7 @@ class Spinach::Features::Profile < Spinach::FeatureSteps
step "I am not an ldap user" do
current_user.identities.delete
- expect(current_user.ldap_user?).to be_false
+ expect(current_user.ldap_user?).to eq false
end
step 'I redirected to expired password page' do
diff --git a/features/steps/project/active_tab.rb b/features/steps/project/active_tab.rb
index fabbc1d3d81..9e96fa5ba49 100644
--- a/features/steps/project/active_tab.rb
+++ b/features/steps/project/active_tab.rb
@@ -20,8 +20,8 @@ class Spinach::Features::ProjectActiveTab < Spinach::FeatureSteps
end
step 'I click the "Edit" tab' do
- page.within '.project-settings-nav' do
- click_link('Project')
+ page.within '.sidebar-subnav' do
+ click_link('Project Settings')
end
end
diff --git a/features/steps/project/forked_merge_requests.rb b/features/steps/project/forked_merge_requests.rb
index 78812c52026..58c16d59d05 100644
--- a/features/steps/project/forked_merge_requests.rb
+++ b/features/steps/project/forked_merge_requests.rb
@@ -42,6 +42,7 @@ class Spinach::Features::ProjectForkedMergeRequests < Spinach::FeatureSteps
click_button "Compare branches"
+ expect(page).to have_content "New merge request"
fill_in "merge_request_title", with: "Merge Request On Forked Project"
end
diff --git a/features/steps/project/issues/issues.rb b/features/steps/project/issues/issues.rb
index 6873c043e19..239392eab96 100644
--- a/features/steps/project/issues/issues.rb
+++ b/features/steps/project/issues/issues.rb
@@ -19,12 +19,11 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
step 'I should see that I am subscribed' do
- expect(find(".subscribe-button span").text).to eq "Unsubscribe"
+ expect(find('.subscribe-button span')).to have_content 'Unsubscribe'
end
step 'I should see that I am unsubscribed' do
- sleep 0.2
- expect(find(".subscribe-button span").text).to eq "Subscribe"
+ expect(find('.subscribe-button span')).to have_content 'Subscribe'
end
step 'I click link "Closed"' do
@@ -195,6 +194,11 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
end
+ When "I visit project \"Community\" issues page" do
+ project = Project.find_by(name: 'Community')
+ visit namespace_project_issues_path(project.namespace, project)
+ 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)
@@ -262,6 +266,24 @@ class Spinach::Features::ProjectIssues < Spinach::FeatureSteps
end
end
+ step 'I should not see labels field' do
+ page.within '.issue-form' do
+ expect(page).not_to have_content("Labels")
+ end
+ end
+
+ step 'I should not see milestone field' do
+ page.within '.issue-form' do
+ expect(page).not_to have_content("Milestone")
+ end
+ end
+
+ step 'I should not see assignee field' do
+ page.within '.issue-form' do
+ expect(page).not_to have_content("Assign to")
+ end
+ end
+
def filter_issue(text)
fill_in 'issue_search', with: text
end
diff --git a/features/steps/project/issues/milestones.rb b/features/steps/project/issues/milestones.rb
index 708c5243947..61e62c2adbd 100644
--- a/features/steps/project/issues/milestones.rb
+++ b/features/steps/project/issues/milestones.rb
@@ -56,4 +56,12 @@ class Spinach::Features::ProjectIssuesMilestones < Spinach::FeatureSteps
step 'I should see 3 issues' do
expect(page).to have_selector('#tab-issues li.issue-row', count: 4)
end
+
+ step 'I click link to remove milestone "v2.2"' do
+ click_link 'Remove'
+ end
+
+ step 'I should see no milestones' do
+ expect(page).to have_content('No milestones to show')
+ end
end
diff --git a/features/steps/project/merge_requests.rb b/features/steps/project/merge_requests.rb
index 62c64e60f6d..a1a26abd8ca 100644
--- a/features/steps/project/merge_requests.rb
+++ b/features/steps/project/merge_requests.rb
@@ -6,6 +6,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
include SharedPaths
include SharedMarkdown
include SharedDiffNote
+ include SharedUser
step 'I click link "New Merge Request"' do
click_link "New Merge Request"
@@ -19,8 +20,8 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
click_link "All"
end
- step 'I click link "Rejected"' do
- click_link "Rejected"
+ step 'I click link "Closed"' do
+ click_link "Closed"
end
step 'I should see merge request "Wiki Feature"' do
@@ -31,8 +32,8 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I should see closed merge request "Bug NS-04"' do
merge_request = MergeRequest.find_by!(title: "Bug NS-04")
- expect(merge_request.closed?).to be_true
- expect(page).to have_content "Rejected by"
+ expect(merge_request).to be_closed
+ expect(page).to have_content "Closed by"
end
step 'I should see merge request "Bug NS-04"' do
@@ -57,11 +58,11 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I should see that I am subscribed' do
- expect(find(".subscribe-button span").text).to eq "Unsubscribe"
+ expect(find('.subscribe-button span')).to have_content 'Unsubscribe'
end
step 'I should see that I am unsubscribed' do
- expect(find(".subscribe-button span")).to have_content("Subscribe")
+ expect(find('.subscribe-button span')).to have_content 'Subscribe'
end
step 'I click button "Unsubscribe"' do
@@ -108,25 +109,26 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
author: project.users.first)
end
- step 'I switch to the diff tab' do
- visit diffs_namespace_project_merge_request_path(project.namespace, project, merge_request)
+ step 'project "Community" has "Bug CO-01" open merge request with diffs inside' do
+ project = Project.find_by(name: "Community")
+ create(:merge_request_with_diffs,
+ title: "Bug CO-01",
+ source_project: project,
+ target_project: project,
+ author: project.users.first)
end
- step 'I click on the Changes tab via Javascript' do
+ step 'I click on the Changes tab' do
page.within '.merge-request-tabs' do
click_link 'Changes'
end
- sleep 2
+ # Waits for load
+ expect(page).to have_css('.tab-content #diffs.active')
end
step 'I should see the proper Inline and Side-by-side links' do
- buttons = page.all('#commit-diff-viewtype')
- expect(buttons.count).to eq(2)
-
- buttons.each do |b|
- expect(expect(b['href'])).not_to have_content('json')
- end
+ expect(page).to have_css('#commit-diff-viewtype', count: 2)
end
step 'I switch to the merge request\'s comments tab' do
@@ -164,20 +166,26 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I should see a discussion has started on diff' do
- expect(page).to have_content "#{current_user.name} started a discussion"
- expect(page).to have_content sample_commit.line_code_path
- expect(page).to have_content "Line is wrong"
+ page.within(".notes .discussion") do
+ page.should have_content "#{current_user.name} started a discussion"
+ page.should have_content sample_commit.line_code_path
+ page.should have_content "Line is wrong"
+ end
end
step 'I should see a discussion has started on commit diff' do
- expect(page).to have_content "#{current_user.name} started a discussion on commit"
- expect(page).to have_content sample_commit.line_code_path
- expect(page).to have_content "Line is wrong"
+ page.within(".notes .discussion") do
+ page.should have_content "#{current_user.name} started a discussion on commit"
+ page.should have_content sample_commit.line_code_path
+ page.should have_content "Line is wrong"
+ end
end
step 'I should see a discussion has started on commit' do
- expect(page).to have_content "#{current_user.name} started a discussion on commit"
- expect(page).to have_content "One comment to rule them all"
+ page.within(".notes .discussion") do
+ page.should have_content "#{current_user.name} started a discussion on commit"
+ page.should have_content "One comment to rule them all"
+ end
end
step 'merge request is mergeable' do
@@ -206,7 +214,7 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
step 'I should see merged request' do
page.within '.issue-box' do
- expect(page).to have_content "Accepted"
+ expect(page).to have_content "Merged"
end
end
@@ -285,6 +293,8 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
step 'I unfold diff' do
+ expect(page).to have_css('.js-unfold')
+
first('.js-unfold').click
end
@@ -320,6 +330,18 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
expect(page).to have_content 'Target branch changed from master to feature'
end
+ step 'I click on "Email Patches"' do
+ click_link "Email Patches"
+ end
+
+ step 'I click on "Plain Diff"' do
+ click_link "Plain Diff"
+ end
+
+ step 'I should see a patch diff' do
+ expect(page).to have_content('diff --git')
+ end
+
def merge_request
@merge_request ||= MergeRequest.find_by!(title: "Bug NS-05")
end
@@ -329,12 +351,13 @@ class Spinach::Features::ProjectMergeRequests < Spinach::FeatureSteps
end
def leave_comment(message)
- page.within(".js-discussion-note-form") do
+ page.within(".js-discussion-note-form", visible: true) do
fill_in "note_note", with: message
click_button "Add Comment"
end
-
- expect(page).to have_content message
+ page.within(".notes_holder", visible: true) do
+ expect(page).to have_content message
+ end
end
def init_diff_note_first_file
diff --git a/features/steps/project/project.rb b/features/steps/project/project.rb
index ee4c7cd0f06..0404fd5e594 100644
--- a/features/steps/project/project.rb
+++ b/features/steps/project/project.rb
@@ -28,7 +28,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps
step 'I change the project avatar' do
attach_file(
:project_avatar,
- File.join(Rails.root, 'public', 'gitlab_logo.png')
+ File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')
)
click_button 'Save changes'
@project.reload
@@ -37,7 +37,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps
step 'I should see new project avatar' do
expect(@project.avatar).to be_instance_of AvatarUploader
url = @project.avatar.url
- expect(url).to eq "/uploads/project/avatar/#{ @project.id }/gitlab_logo.png"
+ expect(url).to eq "/uploads/project/avatar/#{ @project.id }/banana_sample.gif"
end
step 'I should see the "Remove avatar" button' do
@@ -47,7 +47,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps
step 'I have an project avatar' do
attach_file(
:project_avatar,
- File.join(Rails.root, 'public', 'gitlab_logo.png')
+ File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')
)
click_button 'Save changes'
@project.reload
@@ -59,7 +59,7 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end
step 'I should see the default project avatar' do
- expect(@project.avatar?).to be_false
+ expect(@project.avatar?).to eq false
end
step 'I should not see the "Remove avatar" button' do
@@ -86,13 +86,15 @@ class Spinach::Features::Project < Spinach::FeatureSteps
end
step 'I should see project "Forum" README' do
- expect(page).to have_link 'README.md'
- expect(page).to have_content 'Sample repo for testing gitlab features'
+ page.within('#README') do
+ expect(page).to have_content 'Sample repo for testing gitlab features'
+ end
end
step 'I should see project "Shop" README' do
- expect(page).to have_link 'README.md'
- expect(page).to have_content 'testme'
+ page.within('#README') do
+ expect(page).to have_content 'testme'
+ end
end
step 'I add project tags' do
@@ -114,4 +116,18 @@ class Spinach::Features::Project < Spinach::FeatureSteps
step 'I should not see "Snippets" button' do
expect(page).not_to have_link 'Snippets'
end
+
+ step 'project "Shop" belongs to group' do
+ group = create(:group)
+ @project.namespace = group
+ @project.save!
+ end
+
+ step 'I should see back to dashboard button' do
+ expect(page).to have_content 'Back to Dashboard'
+ end
+
+ step 'I should see back to group button' do
+ expect(page).to have_content 'Back to Group'
+ end
end
diff --git a/features/steps/project/project_shortcuts.rb b/features/steps/project/project_shortcuts.rb
index a10e7bf78ee..49e9c5520bb 100644
--- a/features/steps/project/project_shortcuts.rb
+++ b/features/steps/project/project_shortcuts.rb
@@ -33,4 +33,9 @@ class Spinach::Features::ProjectShortcuts < Spinach::FeatureSteps
find('body').native.send_key('g')
find('body').native.send_key('w')
end
+
+ step 'I press "g" and "e"' do
+ find('body').native.send_key('g')
+ find('body').native.send_key('e')
+ end
end
diff --git a/features/steps/project/snippets.rb b/features/steps/project/snippets.rb
index eedb4e1b74a..db8ad08bb9e 100644
--- a/features/steps/project/snippets.rb
+++ b/features/steps/project/snippets.rb
@@ -52,8 +52,8 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
end
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"
+ fill_in "project_snippet_title", with: "Snippet three"
+ fill_in "project_snippet_file_name", with: "my_snippet.rb"
page.within('.file-editor') do
find(:xpath, "//input[@id='project_snippet_content']").set 'Content of snippet three'
end
@@ -66,7 +66,7 @@ class Spinach::Features::ProjectSnippets < Spinach::FeatureSteps
end
step 'I submit new title "Snippet new title"' do
- fill_in "project_snippet_title", :with => "Snippet new title"
+ fill_in "project_snippet_title", with: "Snippet new title"
click_button "Save"
end
diff --git a/features/steps/project/source/browse_files.rb b/features/steps/project/source/browse_files.rb
index 398c9bf5756..95879b9544d 100644
--- a/features/steps/project/source/browse_files.rb
+++ b/features/steps/project/source/browse_files.rb
@@ -187,7 +187,7 @@ class Spinach::Features::ProjectSourceBrowseFiles < Spinach::FeatureSteps
end
step 'I click on "add a file" link' do
- click_link 'add a file'
+ click_link 'adding README'
# Remove pre-receive hook so we can push without auth
FileUtils.rm_f(File.join(@project.repository.path, 'hooks', 'pre-receive'))
diff --git a/features/steps/project/source/multiselect_blob.rb b/features/steps/project/source/multiselect_blob.rb
deleted file mode 100644
index 8e14623b892..00000000000
--- a/features/steps/project/source/multiselect_blob.rb
+++ /dev/null
@@ -1,58 +0,0 @@
-class Spinach::Features::ProjectSourceMultiselectBlob < Spinach::FeatureSteps
- include SharedAuthentication
- include SharedProject
- include SharedPaths
-
- class << self
- def click_line_steps(*line_numbers)
- line_numbers.each do |line_number|
- step "I click line #{line_number} in file" do
- find("#L#{line_number}").click
- end
-
- step "I shift-click line #{line_number} in file" do
- script = "$('#L#{line_number}').trigger($.Event('click', { shiftKey: true }));"
- execute_script(script)
- end
- end
- end
-
- def check_state_steps(*ranges)
- ranges.each do |range|
- fragment = range.kind_of?(Array) ? "L#{range.first}-#{range.last}" : "L#{range}"
- pluralization = range.kind_of?(Array) ? "s" : ""
-
- step "I should see \"#{fragment}\" as URI fragment" do
- expect(URI.parse(current_url).fragment).to eq fragment
- end
-
- step "I should see line#{pluralization} #{fragment[1..-1]} highlighted" do
- ids = Array(range).map { |n| "LC#{n}" }
- extra = false
-
- highlighted = page.all("#tree-content-holder .highlight .line.hll")
- highlighted.each do |element|
- extra ||= ids.delete(element[:id]).nil?
- end
-
- expect(extra).to be_false and ids.should be_empty
- end
- end
- end
- end
-
- click_line_steps *Array(1..5)
- 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
- go_back
- end
-
- step 'I go forward in history' do
- go_forward
- end
-
- step 'I click on ".gitignore" file in repo' do
- click_link ".gitignore"
- end
-end
diff --git a/features/steps/project/star.rb b/features/steps/project/star.rb
index 8b50bfcef04..bd2e0619cdd 100644
--- a/features/steps/project/star.rb
+++ b/features/steps/project/star.rb
@@ -5,7 +5,7 @@ class Spinach::Features::ProjectStar < Spinach::FeatureSteps
include SharedUser
step "The project has no stars" do
- expect(page).not_to have_content '.star-buttons'
+ expect(page).not_to have_content '.toggle-star'
end
step "The project has 0 stars" do
diff --git a/features/steps/search.rb b/features/steps/search.rb
index fec5d9f0e4e..87893aa0205 100644
--- a/features/steps/search.rb
+++ b/features/steps/search.rb
@@ -52,7 +52,9 @@ class Spinach::Features::Search < Spinach::FeatureSteps
end
step 'I should see code results for project "Shop"' do
- expect(page).to have_content 'Update capybara, rspec-rails, poltergeist to recent versions'
+ page.within('.results') do
+ page.should have_content 'Update capybara, rspec-rails, poltergeist to recent versions'
+ end
end
step 'I search for "Contibuting"' do
@@ -71,7 +73,9 @@ class Spinach::Features::Search < Spinach::FeatureSteps
end
step 'I should see "Foo" link in the search results' do
- expect(find(:css, '.search-results')).to have_link 'Foo'
+ page.within('.results') do
+ find(:css, '.search-results').should have_link 'Foo'
+ end
end
step 'I should not see "Bar" link in the search results' do
@@ -79,7 +83,9 @@ class Spinach::Features::Search < Spinach::FeatureSteps
end
step 'I should see "test_wiki" link in the search results' do
- expect(find(:css, '.search-results')).to have_link 'test_wiki.md'
+ page.within('.results') do
+ find(:css, '.search-results').should have_link 'test_wiki.md'
+ end
end
step 'project has Wiki content' do
diff --git a/features/steps/shared/admin.rb b/features/steps/shared/admin.rb
index b6072995677..fbaa408226e 100644
--- a/features/steps/shared/admin.rb
+++ b/features/steps/shared/admin.rb
@@ -9,4 +9,3 @@ module SharedAdmin
2.times { create(:user) }
end
end
-
diff --git a/features/steps/shared/authentication.rb b/features/steps/shared/authentication.rb
index 3c0f2a9406a..735e0ef6108 100644
--- a/features/steps/shared/authentication.rb
+++ b/features/steps/shared/authentication.rb
@@ -28,6 +28,10 @@ module SharedAuthentication
logout
end
+ step "I logout directly" do
+ logout_direct
+ end
+
def current_user
@user || User.first
end
diff --git a/features/steps/shared/diff_note.rb b/features/steps/shared/diff_note.rb
index c4f89ca31c9..27a95aeb19a 100644
--- a/features/steps/shared/diff_note.rb
+++ b/features/steps/shared/diff_note.rb
@@ -20,11 +20,14 @@ module SharedDiffNote
end
step 'I leave a diff comment like "Typo, please fix"' do
- click_diff_line(sample_commit.line_code)
- page.within("#{diff_file_selector} form[rel$='#{sample_commit.line_code}']") do
- fill_in "note[note]", with: "Typo, please fix"
- find(".js-comment-button").trigger("click")
- sleep 0.05
+ page.within(diff_file_selector) do
+ click_diff_line(sample_commit.line_code)
+
+ page.within("form[rel$='#{sample_commit.line_code}']") do
+ fill_in "note[note]", with: "Typo, please fix"
+ find(".js-comment-button").trigger("click")
+ sleep 0.05
+ end
end
end
@@ -45,28 +48,37 @@ module SharedDiffNote
end
step 'I preview a diff comment text like "Should fix it :smile:"' do
- click_diff_line(sample_commit.line_code)
- page.within("#{diff_file_selector} form[rel$='#{sample_commit.line_code}']") do
- fill_in "note[note]", with: "Should fix it :smile:"
- find('.js-md-preview-button').click
+ page.within(diff_file_selector) do
+ click_diff_line(sample_commit.line_code)
+
+ page.within("form[rel$='#{sample_commit.line_code}']") do
+ fill_in "note[note]", with: "Should fix it :smile:"
+ find('.js-md-preview-button').click
+ end
end
end
step 'I preview another diff comment text like "DRY this up"' do
- click_diff_line(sample_commit.del_line_code)
+ page.within(diff_file_selector) do
+ click_diff_line(sample_commit.del_line_code)
- page.within("#{diff_file_selector} form[rel$='#{sample_commit.del_line_code}']") do
- fill_in "note[note]", with: "DRY this up"
- find('.js-md-preview-button').click
+ page.within("form[rel$='#{sample_commit.del_line_code}']") do
+ fill_in "note[note]", with: "DRY this up"
+ find('.js-md-preview-button').click
+ end
end
end
step 'I open a diff comment form' do
- click_diff_line(sample_commit.line_code)
+ page.within(diff_file_selector) do
+ click_diff_line(sample_commit.line_code)
+ end
end
step 'I open another diff comment form' do
- click_diff_line(sample_commit.del_line_code)
+ page.within(diff_file_selector) do
+ click_diff_line(sample_commit.del_line_code)
+ end
end
step 'I write a diff comment like ":-1: I don\'t like this"' do
@@ -194,7 +206,7 @@ module SharedDiffNote
end
def diff_file_selector
- ".diff-file:nth-of-type(1)"
+ '.diff-file:nth-of-type(1)'
end
def click_diff_line(code)
diff --git a/features/steps/shared/issuable.rb b/features/steps/shared/issuable.rb
index 41db2612f26..e6d1b8b8efc 100644
--- a/features/steps/shared/issuable.rb
+++ b/features/steps/shared/issuable.rb
@@ -2,7 +2,7 @@ module SharedIssuable
include Spinach::DSL
def edit_issuable
- find(:css, '.issuable-edit').click
+ find(:css, '.issuable-edit').click
end
step 'I click link "Edit" for the merge request' do
diff --git a/features/steps/shared/note.rb b/features/steps/shared/note.rb
index b2675546a14..f6aabfefeff 100644
--- a/features/steps/shared/note.rb
+++ b/features/steps/shared/note.rb
@@ -2,8 +2,10 @@ module SharedNote
include Spinach::DSL
step 'I delete a comment' do
- find('.note').hover
- find(".js-note-delete").click
+ page.within('.notes') do
+ find('.note').hover
+ find(".js-note-delete").click
+ end
end
step 'I haven\'t written any comment text' do
@@ -16,7 +18,6 @@ module SharedNote
page.within(".js-main-target-form") do
fill_in "note[note]", with: "XML attached"
click_button "Add Comment"
- sleep 0.05
end
end
@@ -123,13 +124,14 @@ module SharedNote
end
step 'I edit the last comment with a +1' do
- find(".note").hover
- find('.js-note-edit').click
+ page.within(".notes") do
+ find(".note").hover
+ find('.js-note-edit').click
+ end
page.within(".current-note-edit-form") do
fill_in 'note[note]', with: '+1 Awesome!'
click_button 'Save Comment'
- sleep 0.05
end
end
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index 3bd0d60281c..88a98a37807 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -20,43 +20,43 @@ module SharedPaths
# ----------------------------------------
step 'I visit group "Owned" page' do
- visit group_path(Group.find_by(name:"Owned"))
+ visit group_path(Group.find_by(name: "Owned"))
end
step 'I visit group "Owned" issues page' do
- visit issues_group_path(Group.find_by(name:"Owned"))
+ visit issues_group_path(Group.find_by(name: "Owned"))
end
step 'I visit group "Owned" merge requests page' do
- visit merge_requests_group_path(Group.find_by(name:"Owned"))
+ visit merge_requests_group_path(Group.find_by(name: "Owned"))
end
step 'I visit group "Owned" members page' do
- visit group_group_members_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
- visit edit_group_path(Group.find_by(name:"Owned"))
+ visit edit_group_path(Group.find_by(name: "Owned"))
end
step 'I visit group "Guest" page' do
- visit group_path(Group.find_by(name:"Guest"))
+ visit group_path(Group.find_by(name: "Guest"))
end
step 'I visit group "Guest" issues page' do
- visit issues_group_path(Group.find_by(name:"Guest"))
+ visit issues_group_path(Group.find_by(name: "Guest"))
end
step 'I visit group "Guest" merge requests page' do
- visit merge_requests_group_path(Group.find_by(name:"Guest"))
+ visit merge_requests_group_path(Group.find_by(name: "Guest"))
end
step 'I visit group "Guest" members page' do
- visit group_group_members_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
- visit edit_group_path(Group.find_by(name:"Guest"))
+ visit edit_group_path(Group.find_by(name: "Guest"))
end
# ----------------------------------------
@@ -127,8 +127,8 @@ module SharedPaths
visit profile_preferences_path
end
- step 'I visit profile history page' do
- visit history_profile_path
+ step 'I visit Audit Log page' do
+ visit audit_log_profile_path
end
# ----------------------------------------
@@ -201,11 +201,11 @@ module SharedPaths
end
step "I visit my project's commits page" do
- visit namespace_project_commits_path(@project.namespace, @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 namespace_project_commits_path(@project.namespace, @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
@@ -251,6 +251,10 @@ module SharedPaths
visit namespace_project_path(project.namespace, project)
end
+ step 'I visit project "Shop" activity page' do
+ visit activity_namespace_project_path(project.namespace, project)
+ end
+
step 'I visit project "Forked Shop" merge requests page' do
visit namespace_project_merge_requests_path(@forked_project.namespace, @forked_project)
end
@@ -272,11 +276,11 @@ module SharedPaths
end
step 'I visit project commits page' do
- visit namespace_project_commits_path(@project.namespace, @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 namespace_project_commits_path(@project.namespace, @project, 'stable', {limit: 5})
+ visit namespace_project_commits_path(@project.namespace, @project, 'stable', { limit: 5 })
end
step 'I visit project source page' do
@@ -357,6 +361,11 @@ module SharedPaths
visit namespace_project_merge_request_path(mr.target_project.namespace, mr.target_project, mr)
end
+ step 'I visit merge request page "Bug CO-01"' do
+ mr = MergeRequest.find_by(title: "Bug CO-01")
+ 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 namespace_project_merge_requests_path(project.namespace, project)
end
@@ -418,13 +427,13 @@ module SharedPaths
visit explore_projects_path
end
- step 'I visit the explore trending projects' do
- visit trending_explore_projects_path
- end
+ step 'I visit the explore trending projects' do
+ visit trending_explore_projects_path
+ end
- step 'I visit the explore starred projects' do
- visit starred_explore_projects_path
- end
+ step 'I visit the explore starred projects' do
+ visit starred_explore_projects_path
+ end
step 'I visit the public groups area' do
visit explore_groups_path
diff --git a/features/steps/shared/project_tab.rb b/features/steps/shared/project_tab.rb
index 3b94b7d8621..c67e5e4a06a 100644
--- a/features/steps/shared/project_tab.rb
+++ b/features/steps/shared/project_tab.rb
@@ -49,4 +49,8 @@ module SharedProjectTab
expect(page).to have_content('Back to project')
end
end
+
+ step 'the active main tab should be Activity' do
+ ensure_active_main_tab('Activity')
+ end
end
diff --git a/features/steps/shared/user.rb b/features/steps/shared/user.rb
index 209d77c7acf..fc1e8d6e889 100644
--- a/features/steps/shared/user.rb
+++ b/features/steps/shared/user.rb
@@ -2,16 +2,16 @@ module SharedUser
include Spinach::DSL
step 'User "John Doe" exists' do
- user_exists("John Doe", {username: "john_doe"})
+ user_exists("John Doe", { username: "john_doe" })
end
step 'User "Mary Jane" exists' do
- user_exists("Mary Jane", {username: "mary_jane"})
+ user_exists("Mary Jane", { username: "mary_jane" })
end
protected
def user_exists(name, options = {})
- User.find_by(name: name) || create(:user, {name: name, admin: false}.merge(options))
+ User.find_by(name: name) || create(:user, { name: name, admin: false }.merge(options))
end
end
diff --git a/features/steps/snippets/snippets.rb b/features/steps/snippets/snippets.rb
index 09fdd1b5a13..6ff48e0c6b8 100644
--- a/features/steps/snippets/snippets.rb
+++ b/features/steps/snippets/snippets.rb
@@ -23,21 +23,33 @@ class Spinach::Features::Snippets < Spinach::FeatureSteps
end
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"
+ fill_in "personal_snippet_title", with: "Personal snippet three"
+ fill_in "personal_snippet_file_name", with: "my_snippet.rb"
page.within('.file-editor') do
find(:xpath, "//input[@id='personal_snippet_content']").set 'Content of snippet three'
end
click_button "Create snippet"
end
+ step 'I submit new internal snippet' do
+ fill_in "personal_snippet_title", with: "Internal personal snippet one"
+ fill_in "personal_snippet_file_name", with: "my_snippet.rb"
+ choose 'personal_snippet_visibility_level_10'
+
+ page.within('.file-editor') do
+ find(:xpath, "//input[@id='personal_snippet_content']").set 'Content of internal snippet'
+ end
+
+ click_button "Create snippet"
+ end
+
step 'I should see snippet "Personal snippet three"' do
expect(page).to have_content "Personal snippet three"
expect(page).to have_content "Content of snippet three"
end
step 'I submit new title "Personal snippet new title"' do
- fill_in "personal_snippet_title", :with => "Personal snippet new title"
+ fill_in "personal_snippet_title", with: "Personal snippet new title"
click_button "Save"
end
@@ -58,7 +70,15 @@ class Spinach::Features::Snippets < Spinach::FeatureSteps
visit snippet_path(snippet)
end
+ step 'I visit snippet page "Internal personal snippet one"' do
+ visit snippet_path(internal_snippet)
+ end
+
def snippet
@snippet ||= PersonalSnippet.find_by!(title: "Personal snippet one")
end
+
+ def internal_snippet
+ @snippet ||= PersonalSnippet.find_by!(title: "Internal personal snippet one")
+ end
end
diff --git a/features/support/env.rb b/features/support/env.rb
index d4a878ea4ce..672251af084 100644
--- a/features/support/env.rb
+++ b/features/support/env.rb
@@ -25,6 +25,7 @@ WebMock.allow_net_connect!
Spinach.hooks.before_run do
include RSpec::Mocks::ExampleMethods
+ RSpec::Mocks.setup
TestEnv.init(mailer: false)
include FactoryGirl::Syntax::Methods
diff --git a/lib/api/api.rb b/lib/api/api.rb
index d2a35c78fc1..eebd44ea5b6 100644
--- a/lib/api/api.rb
+++ b/lib/api/api.rb
@@ -49,5 +49,6 @@ module API
mount Namespaces
mount Branches
mount Labels
+ mount Settings
end
end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index b23eff3661c..ecf1412dee5 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -24,6 +24,7 @@ module API
expose :identities, using: Entities::Identity
expose :can_create_group?, as: :can_create_group
expose :can_create_project?, as: :can_create_project
+ expose :two_factor_enabled
end
class UserLogin < UserFull
@@ -170,6 +171,7 @@ module API
expose :source_project_id, :target_project_id
expose :label_names, as: :labels
expose :description
+ expose :work_in_progress?, as: :work_in_progress
expose :milestone, using: Entities::Milestone
end
@@ -276,5 +278,27 @@ module API
class BroadcastMessage < Grape::Entity
expose :message, :starts_at, :ends_at, :color, :font
end
+
+ class ApplicationSetting < Grape::Entity
+ expose :id
+ expose :default_projects_limit
+ expose :signup_enabled
+ expose :signin_enabled
+ expose :gravatar_enabled
+ expose :sign_in_text
+ expose :created_at
+ expose :updated_at
+ expose :home_page_url
+ expose :default_branch_protection
+ expose :twitter_sharing_enabled
+ expose :restricted_visibility_levels
+ expose :max_attachment_size
+ expose :session_expire_delay
+ expose :default_project_visibility
+ expose :default_snippet_visibility
+ expose :restricted_signup_domains
+ expose :user_oauth_applications
+ expose :after_sign_out_path
+ end
end
end
diff --git a/lib/api/groups.rb b/lib/api/groups.rb
index e88b6e31775..024aeec2e14 100644
--- a/lib/api/groups.rb
+++ b/lib/api/groups.rb
@@ -74,9 +74,9 @@ module API
# POST /groups/:id/projects/:project_id
post ":id/projects/:project_id" do
authenticated_as_admin!
- group = Group.find(params[:id])
+ group = Group.find_by(id: params[:id])
project = Project.find(params[:project_id])
- result = ::Projects::TransferService.new(project, current_user, namespace_id: group.id).execute
+ result = ::Projects::TransferService.new(project, current_user).execute(group)
if result
present group
diff --git a/lib/api/issues.rb b/lib/api/issues.rb
index c8db93eb778..6e7a7672070 100644
--- a/lib/api/issues.rb
+++ b/lib/api/issues.rb
@@ -144,7 +144,7 @@ module API
# PUT /projects/:id/issues/:issue_id
put ":id/issues/:issue_id" do
issue = user_project.issues.find(params[:issue_id])
- authorize! :modify_issue, issue
+ authorize! :update_issue, issue
attrs = attributes_for_keys [:title, :description, :assignee_id, :milestone_id, :state_event]
# Validate label names in advance
@@ -157,7 +157,7 @@ module API
if issue.valid?
# Find or create labels and attach to issue. Labels are valid because
# we already checked its name, so there can't be an error here
- unless params[:labels].nil?
+ if params[:labels] && can?(current_user, :admin_issue, user_project)
issue.remove_labels
# Create and add labels to the new created issue
issue.add_labels_by_names(params[:labels].split(','))
diff --git a/lib/api/merge_requests.rb b/lib/api/merge_requests.rb
index d835dce2ded..aa43e1dffd9 100644
--- a/lib/api/merge_requests.rb
+++ b/lib/api/merge_requests.rb
@@ -109,7 +109,7 @@ module API
# POST /projects/:id/merge_requests
#
post ":id/merge_requests" do
- authorize! :write_merge_request, user_project
+ authorize! :create_merge_request, user_project
required_attributes! [:source_branch, :target_branch, :title]
attrs = attributes_for_keys [:source_branch, :target_branch, :assignee_id, :title, :target_project_id, :description]
@@ -149,7 +149,7 @@ module API
put ":id/merge_request/:merge_request_id" do
attrs = attributes_for_keys [:target_branch, :assignee_id, :title, :state_event, :description]
merge_request = user_project.merge_requests.find(params[:merge_request_id])
- authorize! :modify_merge_request, merge_request
+ authorize! :update_merge_request, merge_request
# Ensure source_branch is not specified
if params[:source_branch].present?
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index 54f2555903f..22ce3c6a066 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -46,7 +46,7 @@ module API
# Example Request:
# POST /projects/:id/snippets
post ":id/snippets" do
- authorize! :write_project_snippet, user_project
+ authorize! :create_project_snippet, user_project
required_attributes! [:title, :file_name, :code, :visibility_level]
attrs = attributes_for_keys [:title, :file_name, :visibility_level]
@@ -74,7 +74,7 @@ module API
# 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
+ authorize! :update_project_snippet, @snippet
attrs = attributes_for_keys [:title, :file_name, :visibility_level]
attrs[:content] = params[:code] if params[:code].present?
@@ -98,7 +98,7 @@ module API
delete ":id/snippets/:snippet_id" do
begin
@snippet = user_project.snippets.find(params[:snippet_id])
- authorize! :modify_project_snippet, @snippet
+ authorize! :update_project_snippet, @snippet
@snippet.destroy
rescue
not_found!('Snippet')
diff --git a/lib/api/settings.rb b/lib/api/settings.rb
new file mode 100644
index 00000000000..c885fcd7ea3
--- /dev/null
+++ b/lib/api/settings.rb
@@ -0,0 +1,35 @@
+module API
+ class Settings < Grape::API
+ before { authenticated_as_admin! }
+
+ helpers do
+ def current_settings
+ @current_setting ||=
+ (ApplicationSetting.current || ApplicationSetting.create_from_defaults)
+ end
+ end
+
+ # Get current applicaiton settings
+ #
+ # Example Request:
+ # GET /application/settings
+ get "application/settings" do
+ present current_settings, with: Entities::ApplicationSetting
+ end
+
+ # Modify applicaiton settings
+ #
+ # Example Request:
+ # PUT /application/settings
+ put "application/settings" do
+ attributes = current_settings.attributes.keys - ["id"]
+ attrs = attributes_for_keys(attributes)
+
+ if current_settings.update_attributes(attrs)
+ present current_settings, with: Entities::ApplicationSetting
+ else
+ render_validation_error!(current_settings)
+ end
+ end
+ end
+end
diff --git a/lib/api/users.rb b/lib/api/users.rb
index 7d4c68c7412..c468371d3d4 100644
--- a/lib/api/users.rb
+++ b/lib/api/users.rb
@@ -194,7 +194,37 @@ module API
user = User.find_by(id: params[:id])
if user
- DeleteUserService.new.execute(user)
+ DeleteUserService.new(current_user).execute(user)
+ else
+ not_found!('User')
+ end
+ end
+
+ # Block user. Available only for admin
+ #
+ # Example Request:
+ # PUT /users/:id/block
+ put ':id/block' do
+ authenticated_as_admin!
+ user = User.find_by(id: params[:id])
+
+ if user
+ user.block
+ else
+ not_found!('User')
+ end
+ end
+
+ # Unblock user. Available only for admin
+ #
+ # Example Request:
+ # PUT /users/:id/unblock
+ put ':id/unblock' do
+ authenticated_as_admin!
+ user = User.find_by(id: params[:id])
+
+ if user
+ user.activate
else
not_found!('User')
end
diff --git a/lib/backup/database.rb b/lib/backup/database.rb
index 9ab6aca276d..b8aa6b9ff2f 100644
--- a/lib/backup/database.rb
+++ b/lib/backup/database.rb
@@ -18,23 +18,30 @@ module Backup
when "postgresql" then
$progress.print "Dumping PostgreSQL database #{config['database']} ... "
pg_env
- system('pg_dump', config['database'], out: db_file_name)
+ # Pass '--clean' to include 'DROP TABLE' statements in the DB dump.
+ system('pg_dump', '--clean', config['database'], out: db_file_name)
end
report_success(success)
abort 'Backup failed' unless success
+
+ $progress.print 'Compressing database ... '
+ success = system('gzip', db_file_name)
+ report_success(success)
+ abort 'Backup failed: compress error' unless success
end
def restore
+ $progress.print 'Decompressing database ... '
+ success = system('gzip', '-d', db_file_name_gz)
+ report_success(success)
+ abort 'Restore failed: decompress error' unless success
+
success = case config["adapter"]
when /^mysql/ then
$progress.print "Restoring MySQL database #{config['database']} ... "
system('mysql', *mysql_args, config['database'], in: db_file_name)
when "postgresql" then
$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
@@ -48,6 +55,10 @@ module Backup
File.join(db_dir, 'database.sql')
end
+ def db_file_name_gz
+ File.join(db_dir, 'database.sql.gz')
+ end
+
def mysql_args
args = {
'host' => '--host',
diff --git a/lib/backup/uploads.rb b/lib/backup/uploads.rb
index e50e1ff4f13..bf43610acf6 100644
--- a/lib/backup/uploads.rb
+++ b/lib/backup/uploads.rb
@@ -23,7 +23,7 @@ module Backup
def backup_existing_uploads_dir
timestamped_uploads_path = File.join(app_uploads_dir, '..', "uploads.#{Time.now.to_i}")
if File.exists?(app_uploads_dir)
- FileUtils.mv(app_uploads_dir, timestamped_uploads_path)
+ FileUtils.mv(app_uploads_dir, File.expand_path(timestamped_uploads_path))
end
end
end
diff --git a/lib/extracts_path.rb b/lib/extracts_path.rb
index 6e4ed01e079..3f420553d42 100644
--- a/lib/extracts_path.rb
+++ b/lib/extracts_path.rb
@@ -55,12 +55,16 @@ module ExtractsPath
valid_refs = @project.repository.ref_names
valid_refs.select! { |v| id.start_with?("#{v}/") }
- if valid_refs.length != 1
+ if valid_refs.length == 0
# No exact ref match, so just try our best
pair = id.match(/([^\/]+)(.*)/).captures
else
+ # There is a distinct possibility that multiple refs prefix the ID.
+ # Use the longest match to maximize the chance that we have the
+ # right ref.
+ best_match = valid_refs.max_by(&:length)
# Partition the string into the ref and the path, ignoring the empty first value
- pair = id.partition(valid_refs.first)[1..-1]
+ pair = id.partition(best_match)[1..-1]
end
end
diff --git a/lib/gitlab/backend/shell_env.rb b/lib/gitlab/backend/shell_env.rb
index 044afb27f3f..17ec029eed4 100644
--- a/lib/gitlab/backend/shell_env.rb
+++ b/lib/gitlab/backend/shell_env.rb
@@ -6,7 +6,9 @@ module Gitlab
def set_env(user)
# Set GL_ID env variable
- ENV['GL_ID'] = "user-#{user.id}"
+ if user
+ ENV['GL_ID'] = "user-#{user.id}"
+ end
end
def reset_env
diff --git a/lib/gitlab/git_access_wiki.rb b/lib/gitlab/git_access_wiki.rb
index 8ba97184e69..8672cbc0ec4 100644
--- a/lib/gitlab/git_access_wiki.rb
+++ b/lib/gitlab/git_access_wiki.rb
@@ -1,7 +1,7 @@
module Gitlab
class GitAccessWiki < GitAccess
def change_access_check(change)
- if user.can?(:write_wiki, project)
+ if user.can?(:create_wiki, project)
build_status_object(true)
else
build_status_object(false, "You are not allowed to write to this project's wiki.")
diff --git a/lib/gitlab/github_import/importer.rb b/lib/gitlab/github_import/importer.rb
index 23832b3233c..98039a76dcd 100644
--- a/lib/gitlab/github_import/importer.rb
+++ b/lib/gitlab/github_import/importer.rb
@@ -11,7 +11,9 @@ module Gitlab
def execute
#Issues && Comments
- client.list_issues(project.import_source, state: :all).each do |issue|
+ client.list_issues(project.import_source, state: :all,
+ sort: :created,
+ direction: :asc).each do |issue|
if issue.pull_request.nil?
body = @formatter.author_line(issue.user.login, issue.body)
diff --git a/lib/gitlab/markdown.rb b/lib/gitlab/markdown.rb
index fa9c0975bb8..889decc9b48 100644
--- a/lib/gitlab/markdown.rb
+++ b/lib/gitlab/markdown.rb
@@ -54,7 +54,7 @@ module Gitlab
current_user: current_user
)
- pipeline = HTML::Pipeline.new(filters)
+ @pipeline ||= HTML::Pipeline.new(filters)
context = {
# SanitizationFilter
@@ -79,7 +79,7 @@ module Gitlab
project_wiki: @project_wiki
}
- result = pipeline.call(text, context)
+ result = @pipeline.call(text, context)
save_options = 0
if options[:xhtml]
diff --git a/lib/gitlab/markup_helper.rb b/lib/gitlab/markup_helper.rb
index f99be969d3e..b1991e2e285 100644
--- a/lib/gitlab/markup_helper.rb
+++ b/lib/gitlab/markup_helper.rb
@@ -33,6 +33,16 @@ module Gitlab
filename.downcase.end_with?(*%w(.adoc .ad .asciidoc))
end
+ # Public: Determines if the given filename is plain text.
+ #
+ # filename - Filename string to check
+ #
+ # Returns boolean
+ def plain?(filename)
+ filename.downcase.end_with?('.txt') ||
+ filename.downcase == 'readme'
+ end
+
def previewable?(filename)
markup?(filename)
end
diff --git a/lib/gitlab/o_auth/provider.rb b/lib/gitlab/o_auth/provider.rb
new file mode 100644
index 00000000000..f986499a27c
--- /dev/null
+++ b/lib/gitlab/o_auth/provider.rb
@@ -0,0 +1,19 @@
+module Gitlab
+ module OAuth
+ class Provider
+ def self.names
+ providers = []
+
+ Gitlab.config.ldap.servers.values.each do |server|
+ providers << server['provider_name']
+ end
+
+ Gitlab.config.omniauth.providers.each do |provider|
+ providers << provider['name']
+ end
+
+ providers
+ end
+ end
+ end
+end
diff --git a/lib/gitlab/satellite/action.rb b/lib/gitlab/satellite/action.rb
index 4890ccf21e6..489070f1a3f 100644
--- a/lib/gitlab/satellite/action.rb
+++ b/lib/gitlab/satellite/action.rb
@@ -39,8 +39,10 @@ module Gitlab
def prepare_satellite!(repo)
project.satellite.clear_and_update!
- repo.config['user.name'] = user.name
- repo.config['user.email'] = user.email
+ if user
+ repo.config['user.name'] = user.name
+ repo.config['user.email'] = user.email
+ end
end
def default_options(options = {})
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
index 582fc759efd..335dc44be19 100644
--- a/lib/gitlab/visibility_level.rb
+++ b/lib/gitlab/visibility_level.rb
@@ -47,6 +47,10 @@ module Gitlab
def valid_level?(level)
options.has_value?(level)
end
+
+ def allowed_fork_levels(origin_level)
+ [PRIVATE, INTERNAL, PUBLIC].select{ |level| level <= origin_level }
+ end
end
def private?
diff --git a/lib/repository_cache.rb b/lib/repository_cache.rb
index fa016a170cd..8ddc3511293 100644
--- a/lib/repository_cache.rb
+++ b/lib/repository_cache.rb
@@ -18,4 +18,12 @@ class RepositoryCache
def fetch(key, &block)
backend.fetch(cache_key(key), &block)
end
+
+ def exist?(key)
+ backend.exist?(cache_key(key))
+ end
+
+ def read(key)
+ backend.read(cache_key(key))
+ end
end
diff --git a/lib/support/nginx/gitlab b/lib/support/nginx/gitlab
index 4688a527eba..edb987875df 100644
--- a/lib/support/nginx/gitlab
+++ b/lib/support/nginx/gitlab
@@ -40,6 +40,10 @@ upstream gitlab {
## Normal HTTP host
server {
+ ## Either remove "default_server" from the listen line below,
+ ## or delete the /etc/nginx/sites-enabled/default file. This will cause gitlab
+ ## to be served if you visit any address that your server responds to, eg.
+ ## the ip address of the server (http://x.x.x.x/)n 0.0.0.0: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
diff --git a/lib/support/nginx/gitlab-ssl b/lib/support/nginx/gitlab-ssl
index 5c94ec63432..766559b49f6 100644
--- a/lib/support/nginx/gitlab-ssl
+++ b/lib/support/nginx/gitlab-ssl
@@ -44,6 +44,10 @@ upstream gitlab {
## Redirects all HTTP traffic to the HTTPS host
server {
+ ## Either remove "default_server" from the listen line below,
+ ## or delete the /etc/nginx/sites-enabled/default file. This will cause gitlab
+ ## to be served if you visit any address that your server responds to, eg.
+ ## the ip address of the server (http://x.x.x.x/)
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
diff --git a/lib/tasks/cache.rake b/lib/tasks/cache.rake
index 753a5a11070..1728dda72cf 100644
--- a/lib/tasks/cache.rake
+++ b/lib/tasks/cache.rake
@@ -1,5 +1,5 @@
namespace :cache do
- desc "GITLAB | Clear redis cache"
+ desc "GitLab | Clear redis cache"
task :clear => :environment do
# Hack into Rails.cache until https://github.com/redis-store/redis-store/pull/225
# is accepted (I hope) and we can update the redis-store gem.
diff --git a/lib/tasks/dev.rake b/lib/tasks/dev.rake
index b22c631c8ba..6f27972c4e4 100644
--- a/lib/tasks/dev.rake
+++ b/lib/tasks/dev.rake
@@ -1,14 +1,14 @@
task dev: ["dev:setup"]
namespace :dev do
- desc "GITLAB | Setup developer environment (db, fixtures)"
+ desc "GitLab | Setup developer environment (db, fixtures)"
task :setup => :environment do
ENV['force'] = 'yes'
Rake::Task["gitlab:setup"].invoke
Rake::Task["gitlab:shell:setup"].invoke
end
- desc 'GITLAB | Start/restart foreman and watch for changes'
+ desc 'GitLab | Start/restart foreman and watch for changes'
task :foreman => :environment do
sh 'rerun --dir app,config,lib -- foreman start'
end
diff --git a/lib/tasks/gitlab/backup.rake b/lib/tasks/gitlab/backup.rake
index 84445b3bf2f..4c73f90bbf2 100644
--- a/lib/tasks/gitlab/backup.rake
+++ b/lib/tasks/gitlab/backup.rake
@@ -3,7 +3,7 @@ require 'active_record/fixtures'
namespace :gitlab do
namespace :backup do
# Create backup of GitLab system
- desc "GITLAB | Create a backup of the GitLab system"
+ desc "GitLab | Create a backup of the GitLab system"
task create: :environment do
warn_user_is_not_gitlab
configure_cron_mode
@@ -19,7 +19,7 @@ namespace :gitlab do
end
# Restore backup of GitLab system
- desc "GITLAB | Restore a previously created backup"
+ desc "GitLab | Restore a previously created backup"
task restore: :environment do
warn_user_is_not_gitlab
configure_cron_mode
diff --git a/lib/tasks/gitlab/bulk_add_permission.rake b/lib/tasks/gitlab/bulk_add_permission.rake
index 3d8c171dfa3..5dbf7d61e06 100644
--- a/lib/tasks/gitlab/bulk_add_permission.rake
+++ b/lib/tasks/gitlab/bulk_add_permission.rake
@@ -1,6 +1,6 @@
namespace :gitlab do
namespace :import do
- desc "GITLAB | Add all users to all projects (admin users are added as masters)"
+ desc "GitLab | Add all users to all projects (admin users are added as masters)"
task all_users_to_all_projects: :environment do |t, args|
user_ids = User.where(admin: false).pluck(:id)
admin_ids = User.where(admin: true).pluck(:id)
@@ -13,7 +13,7 @@ namespace :gitlab do
ProjectMember.add_users_into_projects(projects_ids, admin_ids, ProjectMember::MASTER)
end
- desc "GITLAB | Add a specific user to all projects (as a developer)"
+ desc "GitLab | Add a specific user to all projects (as a developer)"
task :user_to_projects, [:email] => :environment do |t, args|
user = User.find_by(email: args.email)
project_ids = Project.pluck(:id)
@@ -21,7 +21,7 @@ namespace :gitlab do
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)"
+ desc "GitLab | Add all users to all groups (admin users are added as owners)"
task all_users_to_all_groups: :environment do |t, args|
user_ids = User.where(admin: false).pluck(:id)
admin_ids = User.where(admin: true).pluck(:id)
@@ -35,7 +35,7 @@ namespace :gitlab do
end
end
- desc "GITLAB | Add a specific user to all groups (as a developer)"
+ desc "GitLab | Add a specific user to all groups (as a developer)"
task :user_to_groups, [:email] => :environment do |t, args|
user = User.find_by_email args.email
groups = Group.all
diff --git a/lib/tasks/gitlab/check.rake b/lib/tasks/gitlab/check.rake
index 75bd41f2838..aed84226a2f 100644
--- a/lib/tasks/gitlab/check.rake
+++ b/lib/tasks/gitlab/check.rake
@@ -1,5 +1,5 @@
namespace :gitlab do
- desc "GITLAB | Check the configuration of GitLab and its environment"
+ desc "GitLab | Check the configuration of GitLab and its environment"
task check: %w{gitlab:gitlab_shell:check
gitlab:sidekiq:check
gitlab:ldap:check
@@ -8,7 +8,7 @@ namespace :gitlab do
namespace :app do
- desc "GITLAB | Check the configuration of the GitLab Rails app"
+ desc "GitLab | Check the configuration of the GitLab Rails app"
task check: :environment do
warn_user_is_not_gitlab
start_checking "GitLab"
@@ -329,7 +329,7 @@ namespace :gitlab do
end
namespace :gitlab_shell do
- desc "GITLAB | Check the configuration of GitLab Shell"
+ desc "GitLab | Check the configuration of GitLab Shell"
task check: :environment do
warn_user_is_not_gitlab
start_checking "GitLab Shell"
@@ -574,7 +574,7 @@ namespace :gitlab do
namespace :sidekiq do
- desc "GITLAB | Check the configuration of Sidekiq"
+ desc "GitLab | Check the configuration of Sidekiq"
task check: :environment do
warn_user_is_not_gitlab
start_checking "Sidekiq"
@@ -667,7 +667,7 @@ namespace :gitlab do
end
namespace :repo do
- desc "GITLAB | Check the integrity of the repositories managed by GitLab"
+ 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, '*')
diff --git a/lib/tasks/gitlab/cleanup.rake b/lib/tasks/gitlab/cleanup.rake
index d49cb6778f1..6b1e3716147 100644
--- a/lib/tasks/gitlab/cleanup.rake
+++ b/lib/tasks/gitlab/cleanup.rake
@@ -1,6 +1,6 @@
namespace :gitlab do
namespace :cleanup do
- desc "GITLAB | Cleanup | Clean namespaces"
+ desc "GitLab | Cleanup | Clean namespaces"
task dirs: :environment do
warn_user_is_not_gitlab
remove_flag = ENV['REMOVE']
@@ -43,7 +43,7 @@ namespace :gitlab do
end
end
- desc "GITLAB | Cleanup | Clean repositories"
+ desc "GitLab | Cleanup | Clean repositories"
task repos: :environment do
warn_user_is_not_gitlab
remove_flag = ENV['REMOVE']
@@ -85,7 +85,7 @@ namespace :gitlab do
end
end
- desc "GITLAB | Cleanup | Block users that have been removed in LDAP"
+ desc "GitLab | Cleanup | Block users that have been removed in LDAP"
task block_removed_ldap_users: :environment do
warn_user_is_not_gitlab
block_flag = ENV['BLOCK']
diff --git a/lib/tasks/gitlab/db/drop_all_postgres_sequences.rake b/lib/tasks/gitlab/db/drop_all_postgres_sequences.rake
deleted file mode 100644
index e9cf0a9b5e8..00000000000
--- a/lib/tasks/gitlab/db/drop_all_postgres_sequences.rake
+++ /dev/null
@@ -1,10 +0,0 @@
-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/db/drop_all_tables.rake b/lib/tasks/gitlab/db/drop_all_tables.rake
deleted file mode 100644
index a66030ab93a..00000000000
--- a/lib/tasks/gitlab/db/drop_all_tables.rake
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace :gitlab do
- namespace :db do
- task drop_all_tables: :environment do
- connection = ActiveRecord::Base.connection
- connection.tables.each do |table|
- connection.drop_table(table)
- end
- end
- end
-end
diff --git a/lib/tasks/gitlab/enable_automerge.rake b/lib/tasks/gitlab/enable_automerge.rake
index aa9869daf2f..3dade9d75b8 100644
--- a/lib/tasks/gitlab/enable_automerge.rake
+++ b/lib/tasks/gitlab/enable_automerge.rake
@@ -1,6 +1,6 @@
namespace :gitlab do
namespace :satellites do
- desc "GITLAB | Create satellite repos"
+ desc "GitLab | Create satellite repos"
task create: :environment do
create_satellites
end
diff --git a/lib/tasks/gitlab/generate_docs.rake b/lib/tasks/gitlab/generate_docs.rake
index 332cd61f84c..f6448c38e10 100644
--- a/lib/tasks/gitlab/generate_docs.rake
+++ b/lib/tasks/gitlab/generate_docs.rake
@@ -1,5 +1,5 @@
namespace :gitlab do
- desc "GITLAB | Generate sdocs for project"
+ desc "GitLab | Generate sdocs for project"
task generate_docs: :environment do
system(*%W(bundle exec sdoc -o doc/code app lib))
end
diff --git a/lib/tasks/gitlab/import.rake b/lib/tasks/gitlab/import.rake
index 7c98ad3144f..5f83e5e8e7f 100644
--- a/lib/tasks/gitlab/import.rake
+++ b/lib/tasks/gitlab/import.rake
@@ -9,7 +9,7 @@ namespace :gitlab do
# * The project owner will set to the first administator of the system
# * Existing projects will be skipped
#
- desc "GITLAB | Import bare repositories from gitlab_shell -> repos_path into GitLab project instance"
+ desc "GitLab | Import bare repositories from gitlab_shell -> repos_path into GitLab project instance"
task repos: :environment do
git_base_path = Gitlab.config.gitlab_shell.repos_path
diff --git a/lib/tasks/gitlab/info.rake b/lib/tasks/gitlab/info.rake
index 72452e1d8ea..bf221f06d3b 100644
--- a/lib/tasks/gitlab/info.rake
+++ b/lib/tasks/gitlab/info.rake
@@ -1,6 +1,6 @@
namespace :gitlab do
namespace :env do
- desc "GITLAB | Show information about GitLab and its environment"
+ desc "GitLab | Show information about GitLab and its environment"
task info: :environment do
# check if there is an RVM environment
diff --git a/lib/tasks/gitlab/setup.rake b/lib/tasks/gitlab/setup.rake
index 8b4ccdfc3fe..0ac4b0fa8a3 100644
--- a/lib/tasks/gitlab/setup.rake
+++ b/lib/tasks/gitlab/setup.rake
@@ -1,5 +1,5 @@
namespace :gitlab do
- desc "GITLAB | Setup production application"
+ desc "GitLab | Setup production application"
task setup: :environment do
setup_db
end
diff --git a/lib/tasks/gitlab/shell.rake b/lib/tasks/gitlab/shell.rake
index afdaba11cb0..3c0cc763d17 100644
--- a/lib/tasks/gitlab/shell.rake
+++ b/lib/tasks/gitlab/shell.rake
@@ -1,6 +1,6 @@
namespace :gitlab do
namespace :shell do
- desc "GITLAB | Install or upgrade gitlab-shell"
+ desc "GitLab | Install or upgrade gitlab-shell"
task :install, [:tag, :repo] => :environment do |t, args|
warn_user_is_not_gitlab
@@ -75,12 +75,12 @@ namespace :gitlab do
end
end
- desc "GITLAB | Setup gitlab-shell"
+ desc "GitLab | Setup gitlab-shell"
task setup: :environment do
setup
end
- desc "GITLAB | Build missing projects"
+ desc "GitLab | Build missing projects"
task build_missing_projects: :environment do
Project.find_each(batch_size: 1000) do |project|
path_to_repo = project.repository.path_to_repo
diff --git a/lib/tasks/gitlab/test.rake b/lib/tasks/gitlab/test.rake
index b4c0ae3ff79..4d4e746503a 100644
--- a/lib/tasks/gitlab/test.rake
+++ b/lib/tasks/gitlab/test.rake
@@ -1,12 +1,12 @@
namespace :gitlab do
- desc "GITLAB | Run all tests"
+ 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)
+ %W(rake teaspoon)
]
cmds.each do |cmd|
diff --git a/lib/tasks/gitlab/web_hook.rake b/lib/tasks/gitlab/web_hook.rake
index 412bcad1229..76e443e55ee 100644
--- a/lib/tasks/gitlab/web_hook.rake
+++ b/lib/tasks/gitlab/web_hook.rake
@@ -1,6 +1,6 @@
namespace :gitlab do
namespace :web_hook do
- desc "GITLAB | Adds a web hook to the projects"
+ desc "GitLab | Adds a web hook to the projects"
task :add => :environment do
web_hook_url = ENV['URL']
namespace_path = ENV['NAMESPACE']
@@ -20,7 +20,7 @@ namespace :gitlab do
end
end
- desc "GITLAB | Remove a web hook from the projects"
+ desc "GitLab | Remove a web hook from the projects"
task :rm => :environment do
web_hook_url = ENV['URL']
namespace_path = ENV['NAMESPACE']
@@ -33,7 +33,7 @@ namespace :gitlab do
puts "#{count} web hooks were removed."
end
- desc "GITLAB | List web hooks"
+ desc "GitLab | List web hooks"
task :list => :environment do
namespace_path = ENV['NAMESPACE']
diff --git a/lib/tasks/jasmine.rake b/lib/tasks/jasmine.rake
deleted file mode 100644
index ac307a9e929..00000000000
--- a/lib/tasks/jasmine.rake
+++ /dev/null
@@ -1,12 +0,0 @@
-# Since we no longer explicitly require the 'jasmine' gem, we lost the
-# `jasmine:ci` task used by GitLab CI jobs.
-#
-# This provides a simple alias to run the `spec:javascript` task from the
-# 'jasmine-rails' gem.
-task jasmine: ['jasmine:ci']
-
-namespace :jasmine do
- task :ci do
- Rake::Task['teaspoon'].invoke
- end
-end
diff --git a/lib/tasks/migrate/add_limits_mysql.rake b/lib/tasks/migrate/add_limits_mysql.rake
index a1972a682d8..6ded519aff2 100644
--- a/lib/tasks/migrate/add_limits_mysql.rake
+++ b/lib/tasks/migrate/add_limits_mysql.rake
@@ -1,6 +1,6 @@
require Rails.root.join('db/migrate/limits_to_mysql')
-desc "GITLAB | Add limits to strings in mysql database"
+desc "GitLab | Add limits to strings in mysql database"
task add_limits_mysql: :environment do
puts "Adding limits to schema.rb for mysql"
LimitsToMysql.new.up
diff --git a/lib/tasks/migrate/migrate_iids.rake b/lib/tasks/migrate/migrate_iids.rake
index 33271e1a2bb..d258c6fd08d 100644
--- a/lib/tasks/migrate/migrate_iids.rake
+++ b/lib/tasks/migrate/migrate_iids.rake
@@ -1,4 +1,4 @@
-desc "GITLAB | Build internal ids for issues and merge requests"
+desc "GitLab | Build internal ids for issues and merge requests"
task migrate_iids: :environment do
puts 'Issues'.yellow
Issue.where(iid: nil).find_each(batch_size: 100) do |issue|
diff --git a/lib/tasks/setup.rake b/lib/tasks/setup.rake
index 93701de8f63..4c79ffbfa6b 100644
--- a/lib/tasks/setup.rake
+++ b/lib/tasks/setup.rake
@@ -1,4 +1,4 @@
-desc "GITLAB | Setup gitlab db"
+desc "GitLab | Setup gitlab db"
task :setup do
Rake::Task["gitlab:setup"].invoke
end
diff --git a/lib/tasks/sidekiq.rake b/lib/tasks/sidekiq.rake
index e4bd6545755..d1f6ed87704 100644
--- a/lib/tasks/sidekiq.rake
+++ b/lib/tasks/sidekiq.rake
@@ -1,10 +1,10 @@
namespace :sidekiq do
- desc "GITLAB | Stop sidekiq"
+ desc "GitLab | Stop sidekiq"
task :stop do
system *%W(bin/background_jobs stop)
end
- desc "GITLAB | Start sidekiq"
+ desc "GitLab | Start sidekiq"
task :start do
system *%W(bin/background_jobs start)
end
@@ -14,7 +14,7 @@ namespace :sidekiq do
system *%W(bin/background_jobs restart)
end
- desc "GITLAB | Start sidekiq with launchd on Mac OS X"
+ desc "GitLab | Start sidekiq with launchd on Mac OS X"
task :launchd do
system *%W(bin/background_jobs start_no_deamonize)
end
diff --git a/lib/tasks/spec.rake b/lib/tasks/spec.rake
index bee22300298..831746815d7 100644
--- a/lib/tasks/spec.rake
+++ b/lib/tasks/spec.rake
@@ -1,7 +1,7 @@
Rake::Task["spec"].clear if Rake::Task.task_defined?('spec')
namespace :spec do
- desc 'GITLAB | Run request specs'
+ desc 'GitLab | Rspec | Run request specs'
task :api do
cmds = [
%W(rake gitlab:setup),
@@ -10,7 +10,7 @@ namespace :spec do
run_commands(cmds)
end
- desc 'GITLAB | Run feature specs'
+ desc 'GitLab | Rspec | Run feature specs'
task :feature do
cmds = [
%W(rake gitlab:setup),
@@ -19,7 +19,7 @@ namespace :spec do
run_commands(cmds)
end
- desc 'GITLAB | Run other specs'
+ desc 'GitLab | Rspec | Run other specs'
task :other do
cmds = [
%W(rake gitlab:setup),
@@ -29,7 +29,7 @@ namespace :spec do
end
end
-desc "GITLAB | Run specs"
+desc "GitLab | Run specs"
task :spec do
cmds = [
%W(rake gitlab:setup),
diff --git a/lib/tasks/spinach.rake b/lib/tasks/spinach.rake
index 4aefc18ce14..c8881be0954 100644
--- a/lib/tasks/spinach.rake
+++ b/lib/tasks/spinach.rake
@@ -1,34 +1,30 @@
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 --tags #{tags}),
- ]
- run_commands(cmds)
-end
+namespace :spinach do
+ desc "GitLab | Spinach | Run project spinach features"
+ task :project do
+ cmds = [
+ %W(rake gitlab:setup),
+ %W(spinach --tags ~@admin,~@dashboard,~@profile,~@public,~@snippets),
+ ]
+ run_commands(cmds)
+ end
-desc "GITLAB | Run project spinach features"
-task :spinach_project do
- cmds = [
- %W(rake gitlab:setup),
- %W(spinach --tags ~@admin,~@dashboard,~@profile,~@public,~@snippets),
- ]
- run_commands(cmds)
+ desc "GitLab | Spinach | Run other spinach features"
+ task :other do
+ cmds = [
+ %W(rake gitlab:setup),
+ %W(spinach --tags @admin,@dashboard,@profile,@public,@snippets),
+ ]
+ run_commands(cmds)
+ end
end
-desc "GITLAB | Run other spinach features"
-task :spinach_other do
+desc "GitLab | Run spinach"
+task :spinach do
cmds = [
%W(rake gitlab:setup),
- %W(spinach --tags @admin,@dashboard,@profile,@public,@snippets),
+ %W(spinach),
]
run_commands(cmds)
end
diff --git a/lib/tasks/test.rake b/lib/tasks/test.rake
index a39d9649876..c5666d49e61 100644
--- a/lib/tasks/test.rake
+++ b/lib/tasks/test.rake
@@ -1,6 +1,6 @@
Rake::Task["test"].clear
-desc "GITLAB | Run all tests"
+desc "GitLab | Run all tests"
task :test do
Rake::Task["gitlab:test"].invoke
end
@@ -8,6 +8,6 @@ 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']
+ desc "GitLab | Run all tests on CI with simplecov"
+ task :test_ci => [:rubocop, :brakeman, 'teaspoon', :spinach, :spec, 'coveralls:push']
end
diff --git a/public/apple-touch-icon-precomposed.png b/public/apple-touch-icon-precomposed.png
index 6f2e0dd090f..7da5f23ed9b 100644
--- a/public/apple-touch-icon-precomposed.png
+++ b/public/apple-touch-icon-precomposed.png
Binary files differ
diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png
index 6f2e0dd090f..7da5f23ed9b 100644
--- a/public/apple-touch-icon.png
+++ b/public/apple-touch-icon.png
Binary files differ
diff --git a/public/deploy.html b/public/deploy.html
index 1a41b772f3c..3822ed4b64d 100644
--- a/public/deploy.html
+++ b/public/deploy.html
@@ -7,7 +7,7 @@
<body>
<h1>
- <img src="/gitlab_logo.png" /><br />
+ <img src="/logo.svg" /><br />
Deploy in progress
</h1>
<h3>Please try again in a few minutes.</h3>
diff --git a/public/favicon.ico b/public/favicon.ico
index bfb74960c48..3479cbbb46f 100644
--- a/public/favicon.ico
+++ b/public/favicon.ico
Binary files differ
diff --git a/public/gitlab_logo.png b/public/gitlab_logo.png
deleted file mode 100644
index dbe6dabb784..00000000000
--- a/public/gitlab_logo.png
+++ /dev/null
Binary files differ
diff --git a/public/logo.svg b/public/logo.svg
new file mode 100644
index 00000000000..c09785cb96f
--- /dev/null
+++ b/public/logo.svg
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="210px" height="210px" viewBox="0 0 210 210" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
+ <!-- Generator: Sketch 3.3.2 (12043) - http://www.bohemiancoding.com/sketch -->
+ <title>Slice 1</title>
+ <desc>Created with Sketch.</desc>
+ <defs></defs>
+ <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
+ <g id="logo" sketch:type="MSLayerGroup" transform="translate(0.000000, 10.000000)">
+ <g id="Page-1" sketch:type="MSShapeGroup">
+ <g id="Fill-1-+-Group-24">
+ <g id="Group-24">
+ <g id="Group">
+ <path d="M105.0614,193.655 L105.0614,193.655 L143.7014,74.734 L66.4214,74.734 L105.0614,193.655 L105.0614,193.655 Z" id="Fill-4" fill="#E24329"></path>
+ <path d="M105.0614,193.6548 L66.4214,74.7338 L12.2684,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-8" fill="#FC6D26"></path>
+ <path d="M12.2685,74.7341 L12.2685,74.7341 L0.5265,110.8731 C-0.5445,114.1691 0.6285,117.7801 3.4325,119.8171 L105.0615,193.6551 L12.2685,74.7341 L12.2685,74.7341 Z" id="Fill-12" fill="#FCA326"></path>
+ <path d="M12.2685,74.7342 L66.4215,74.7342 L43.1485,3.1092 C41.9515,-0.5768 36.7375,-0.5758 35.5405,3.1092 L12.2685,74.7342 L12.2685,74.7342 Z" id="Fill-16" fill="#E24329"></path>
+ <path d="M105.0614,193.6548 L143.7014,74.7338 L197.8544,74.7338 L105.0614,193.6548 L105.0614,193.6548 Z" id="Fill-18" fill="#FC6D26"></path>
+ <path d="M197.8544,74.7341 L197.8544,74.7341 L209.5964,110.8731 C210.6674,114.1691 209.4944,117.7801 206.6904,119.8171 L105.0614,193.6551 L197.8544,74.7341 L197.8544,74.7341 Z" id="Fill-20" fill="#FCA326"></path>
+ <path d="M197.8544,74.7342 L143.7014,74.7342 L166.9744,3.1092 C168.1714,-0.5768 173.3854,-0.5758 174.5824,3.1092 L197.8544,74.7342 L197.8544,74.7342 Z" id="Fill-22" fill="#E24329"></path>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg> \ No newline at end of file
diff --git a/scripts/prepare_build.sh b/scripts/prepare_build.sh
index 5525ab77435..dca5e1c5db3 100644..100755
--- a/scripts/prepare_build.sh
+++ b/scripts/prepare_build.sh
@@ -4,7 +4,7 @@ if [ -f /.dockerinit ]; then
dpkg -i phantomjs_1.9.0-1+b1_amd64.deb
apt-get update -qq
- apt-get install -y -qq libicu-dev libkrb5-dev cmake nodejs
+ apt-get install -y -qq libicu-dev libkrb5-dev cmake nodejs postgresql-client mysql-client
cp config/database.yml.mysql config/database.yml
sed -i 's/username:.*/username: root/g' config/database.yml
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
new file mode 100644
index 00000000000..6f4c8987637
--- /dev/null
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -0,0 +1,67 @@
+require 'spec_helper'
+
+describe Admin::UsersController do
+ let(:admin) { create(:admin) }
+
+ before do
+ sign_in(admin)
+ end
+
+ describe 'DELETE #user with projects' do
+ let(:user) { create(:user) }
+ let(:project) { create(:project, namespace: user.namespace) }
+
+ before do
+ project.team << [user, :developer]
+ end
+
+ it 'deletes user' do
+ delete :destroy, id: user.username, format: :json
+ expect(response.status).to eq(200)
+ expect { User.find(user.id) }.to raise_exception(ActiveRecord::RecordNotFound)
+ end
+ end
+
+ describe 'PUT unlock/:id' do
+ let(:user) { create(:user) }
+
+ before do
+ request.env["HTTP_REFERER"] = "/"
+ user.lock_access!
+ end
+
+ it 'unlocks user' do
+ put :unlock, id: user.username
+ user.reload
+ expect(user.access_locked?).to be_falsey
+ end
+ end
+
+ describe 'PATCH disable_two_factor' do
+ let(:user) { create(:user) }
+
+ it 'disables 2FA for the user' do
+ expect(user).to receive(:disable_two_factor!)
+ allow(subject).to receive(:user).and_return(user)
+
+ go
+ end
+
+ it 'redirects back' do
+ go
+
+ expect(response).to redirect_to(admin_user_path(user))
+ end
+
+ it 'displays an alert' do
+ go
+
+ expect(flash[:notice]).
+ to eq 'Two-factor Authentication has been disabled for this user'
+ end
+
+ def go
+ patch :disable_two_factor, id: user.to_param
+ end
+ end
+end
diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb
index 1ea1227b28b..1230017c270 100644
--- a/spec/controllers/autocomplete_controller_spec.rb
+++ b/spec/controllers/autocomplete_controller_spec.rb
@@ -9,15 +9,27 @@ describe AutocompleteController do
before do
sign_in(user)
project.team << [user, :master]
-
- get(:users, project_id: project.id)
end
let(:body) { JSON.parse(response.body) }
- it { expect(body).to be_kind_of(Array) }
- it { expect(body.size).to eq(1) }
- it { expect(body.first["username"]).to eq user.username }
+ describe 'GET #users with project ID' do
+ before do
+ get(:users, project_id: project.id)
+ end
+
+ it { expect(body).to be_kind_of(Array) }
+ it { expect(body.size).to eq 1 }
+ it { expect(body.first["username"]).to eq user.username }
+ end
+
+ describe 'GET #users with unknown project' do
+ before do
+ get(:users, project_id: 'unknown')
+ end
+
+ it { expect(response.status).to eq(404) }
+ end
end
context 'group members' do
@@ -26,15 +38,27 @@ describe AutocompleteController do
before do
sign_in(user)
group.add_owner(user)
-
- get(:users, group_id: group.id)
end
let(:body) { JSON.parse(response.body) }
- it { expect(body).to be_kind_of(Array) }
- it { expect(body.size).to eq(1) }
- it { expect(body.first["username"]).to eq user.username }
+ describe 'GET #users with group ID' do
+ before do
+ get(:users, group_id: group.id)
+ end
+
+ it { expect(body).to be_kind_of(Array) }
+ it { expect(body.size).to eq 1 }
+ it { expect(body.first["username"]).to eq user.username }
+ end
+
+ describe 'GET #users with unknown group ID' do
+ before do
+ get(:users, group_id: 'unknown')
+ end
+
+ it { expect(response.status).to eq(404) }
+ end
end
context 'all users' do
@@ -46,6 +70,54 @@ describe AutocompleteController do
let(:body) { JSON.parse(response.body) }
it { expect(body).to be_kind_of(Array) }
- it { expect(body.size).to eq(User.count) }
+ it { expect(body.size).to eq User.count }
+ end
+
+ context 'unauthenticated user' do
+ let(:public_project) { create(:project, :public) }
+ let(:body) { JSON.parse(response.body) }
+
+ describe 'GET #users with public project' do
+ before do
+ public_project.team << [user, :guest]
+ get(:users, project_id: public_project.id)
+ end
+
+ it { expect(body).to be_kind_of(Array) }
+ it { expect(body.size).to eq 1 }
+ end
+
+ describe 'GET #users with project' do
+ before do
+ get(:users, project_id: project.id)
+ end
+
+ it { expect(response.status).to eq(302) }
+ end
+
+ describe 'GET #users with unknown project' do
+ before do
+ get(:users, project_id: 'unknown')
+ end
+
+ it { expect(response.status).to eq(302) }
+ end
+
+ describe 'GET #users with inaccessible group' do
+ before do
+ project.team << [user, :guest]
+ get(:users, group_id: user.namespace.id)
+ end
+
+ it { expect(response.status).to eq(302) }
+ end
+
+ describe 'GET #users with no project' do
+ before do
+ get(:users)
+ end
+
+ it { expect(response.status).to eq(302) }
+ end
end
end
diff --git a/spec/controllers/blob_controller_spec.rb b/spec/controllers/blob_controller_spec.rb
index a1102f28340..eb91e577b87 100644
--- a/spec/controllers/blob_controller_spec.rb
+++ b/spec/controllers/blob_controller_spec.rb
@@ -18,8 +18,10 @@ describe Projects::BlobController do
render_views
before do
- get(:show, namespace_id: project.namespace.to_param,
- project_id: project.to_param, id: id)
+ get(:show,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: id)
end
context "valid branch, valid file" do
@@ -42,8 +44,10 @@ describe Projects::BlobController do
render_views
before do
- get(:show, namespace_id: project.namespace.to_param,
- 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
diff --git a/spec/controllers/branches_controller_spec.rb b/spec/controllers/branches_controller_spec.rb
index 51397382cfb..bd4c946b64b 100644
--- a/spec/controllers/branches_controller_spec.rb
+++ b/spec/controllers/branches_controller_spec.rb
@@ -17,13 +17,13 @@ describe Projects::BranchesController do
describe "POST create" do
render_views
- before {
+ before do
post :create,
namespace_id: project.namespace.to_param,
project_id: project.to_param,
branch_name: branch,
ref: ref
- }
+ end
context "valid branch name, valid source" do
let(:branch) { "merge_branch" }
@@ -55,4 +55,30 @@ describe Projects::BranchesController do
it { is_expected.to render_template('new') }
end
end
+
+ describe "POST destroy" do
+ render_views
+
+ before do
+ post :destroy,
+ format: :js,
+ id: branch,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param
+ end
+
+ context "valid branch name, valid source" do
+ let(:branch) { "feature" }
+
+ it { expect(response.status).to eq(200) }
+ it { expect(subject).to render_template('destroy') }
+ end
+
+ context "invalid branch name, valid ref" do
+ let(:branch) { "no-branch" }
+
+ it { expect(response.status).to eq(404) }
+ it { expect(subject).to render_template('destroy') }
+ end
+ end
end
diff --git a/spec/controllers/commit_controller_spec.rb b/spec/controllers/commit_controller_spec.rb
index 2cfa399a047..bb3d87f3840 100644
--- a/spec/controllers/commit_controller_spec.rb
+++ b/spec/controllers/commit_controller_spec.rb
@@ -13,8 +13,11 @@ describe Projects::CommitController do
describe "#show" do
shared_examples "export as" do |format|
it "should generally work" do
- get(:show, namespace_id: project.namespace.to_param,
- 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
@@ -22,13 +25,17 @@ describe Projects::CommitController do
it "should generate it" do
expect_any_instance_of(Commit).to receive(:"to_#{format}")
- get(:show, namespace_id: project.namespace.to_param,
- 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, namespace_id: project.namespace.to_param,
- 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
@@ -37,13 +44,15 @@ describe Projects::CommitController do
allow_any_instance_of(Commit).to receive(:"to_#{format}").
and_return('HTML entities &<>" ')
- get(:show, namespace_id: project.namespace.to_param,
- 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('&amp;')
- expect(response.body).to_not include('&gt;')
- expect(response.body).to_not include('&lt;')
- expect(response.body).to_not include('&quot;')
+ expect(response.body).not_to include('&amp;')
+ expect(response.body).not_to include('&gt;')
+ expect(response.body).not_to include('&lt;')
+ expect(response.body).not_to include('&quot;')
end
end
@@ -52,8 +61,11 @@ describe Projects::CommitController do
let(:format) { :diff }
it "should really only be a git diff" do
- get(:show, namespace_id: project.namespace.to_param,
- 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
@@ -64,15 +76,21 @@ describe Projects::CommitController do
let(:format) { :patch }
it "should really be a git email patch" do
- get(:show, namespace_id: project.namespace.to_param,
- 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, namespace_id: project.namespace.to_param,
- 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
@@ -81,8 +99,10 @@ describe Projects::CommitController do
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)
+ 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")
diff --git a/spec/controllers/commits_controller_spec.rb b/spec/controllers/commits_controller_spec.rb
index 2184b35152e..7d8089c4bc6 100644
--- a/spec/controllers/commits_controller_spec.rb
+++ b/spec/controllers/commits_controller_spec.rb
@@ -12,8 +12,11 @@ describe Projects::CommitsController do
describe "GET show" do
context "as atom feed" do
it "should render as atom" do
- get(:show, namespace_id: project.namespace.to_param,
- project_id: project.to_param, id: "master", format: "atom")
+ 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
diff --git a/spec/controllers/help_controller_spec.rb b/spec/controllers/help_controller_spec.rb
index 93535ced7ae..1f7fd517342 100644
--- a/spec/controllers/help_controller_spec.rb
+++ b/spec/controllers/help_controller_spec.rb
@@ -35,8 +35,10 @@ describe HelpController do
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
+ 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;/)
@@ -45,7 +47,10 @@ describe HelpController do
context 'when requested file is missing' do
it 'renders not found' do
- get :show, category: 'foo', file: 'bar', format: :png
+ get :show,
+ category: 'foo',
+ file: 'bar',
+ format: :png
expect(response).to be_not_found
end
end
@@ -53,7 +58,10 @@ describe HelpController do
context 'for other formats' do
it 'always renders not found' do
- get :show, category: 'ssh', file: 'README', format: :foo
+ get :show,
+ category: 'ssh',
+ file: 'README',
+ format: :foo
expect(response).to be_not_found
end
end
diff --git a/spec/controllers/import/bitbucket_controller_spec.rb b/spec/controllers/import/bitbucket_controller_spec.rb
index c31563e6d77..d5d9310e603 100644
--- a/spec/controllers/import/bitbucket_controller_spec.rb
+++ b/spec/controllers/import/bitbucket_controller_spec.rb
@@ -1,24 +1,28 @@
require 'spec_helper'
+require_relative 'import_spec_helper'
describe Import::BitbucketController do
+ include ImportSpecHelper
+
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)
+ allow(controller).to receive(: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")
+ allow_any_instance_of(Gitlab::BitbucketImport::Client).
+ to receive(:get_token).and_return(access_token)
+ stub_omniauth_provider('bitbucket')
get :callback
@@ -35,7 +39,7 @@ describe Import::BitbucketController do
it "assigns variables" do
@project = create(:project, import_type: 'bitbucket', creator_id: user.id)
- controller.stub_chain(:client, :projects).and_return([@repo])
+ stub_client(projects: [@repo])
get :status
@@ -45,7 +49,7 @@ describe Import::BitbucketController do
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])
+ stub_client(projects: [@repo])
get :status
@@ -57,28 +61,20 @@ describe Import::BitbucketController do
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
- }
+ let(:bitbucket_user) do
+ { user: { username: bitbucket_username } }.with_indifferent_access
+ end
+
+ let(:bitbucket_repo) do
+ { slug: "vim", owner: bitbucket_username }.with_indifferent_access
+ end
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)
+ stub_client(user: bitbucket_user, project: bitbucket_repo)
end
context "when the repository owner is the Bitbucket user" do
diff --git a/spec/controllers/import/github_controller_spec.rb b/spec/controllers/import/github_controller_spec.rb
index 3d3846b2e3a..0bc14059a35 100644
--- a/spec/controllers/import/github_controller_spec.rb
+++ b/spec/controllers/import/github_controller_spec.rb
@@ -1,11 +1,14 @@
require 'spec_helper'
+require_relative 'import_spec_helper'
describe Import::GithubController do
+ include ImportSpecHelper
+
let(:user) { create(:user, github_access_token: 'asd123') }
before do
sign_in(user)
- controller.stub(:github_import_enabled?).and_return(true)
+ allow(controller).to receive(:github_import_enabled?).and_return(true)
end
describe "GET callback" do
@@ -13,9 +16,7 @@ describe Import::GithubController 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')
+ stub_omniauth_provider('github')
get :callback
@@ -33,9 +34,7 @@ describe Import::GithubController do
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])
+ stub_client(repos: [@repo], orgs: [@org], org_repos: [@org_repo])
get :status
@@ -45,8 +44,7 @@ describe Import::GithubController do
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([])
+ stub_client(repos: [@repo], orgs: [])
get :status
@@ -57,18 +55,17 @@ describe Import::GithubController do
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))
- }
+ let(:github_user) { OpenStruct.new(login: github_username) }
+ let(:github_repo) do
+ OpenStruct.new(
+ name: 'vim',
+ full_name: "#{github_username}/vim",
+ owner: OpenStruct.new(login: github_username)
+ )
+ end
before do
- controller.stub_chain(:client, :user).and_return(github_user)
- controller.stub_chain(:client, :repo).and_return(github_repo)
+ stub_client(user: github_user, repo: github_repo)
end
context "when the repository owner is the GitHub user" do
diff --git a/spec/controllers/import/gitlab_controller_spec.rb b/spec/controllers/import/gitlab_controller_spec.rb
index 112e51d431e..4bc67c86703 100644
--- a/spec/controllers/import/gitlab_controller_spec.rb
+++ b/spec/controllers/import/gitlab_controller_spec.rb
@@ -1,18 +1,22 @@
require 'spec_helper'
+require_relative 'import_spec_helper'
describe Import::GitlabController do
+ include ImportSpecHelper
+
let(:user) { create(:user, gitlab_access_token: 'asd123') }
before do
sign_in(user)
- controller.stub(:gitlab_import_enabled?).and_return(true)
+ allow(controller).to receive(: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")
+ allow_any_instance_of(Gitlab::GitlabImport::Client).
+ to receive(:get_token).and_return(token)
+ stub_omniauth_provider('gitlab')
get :callback
@@ -28,7 +32,7 @@ describe Import::GitlabController do
it "assigns variables" do
@project = create(:project, import_type: 'gitlab', creator_id: user.id)
- controller.stub_chain(:client, :projects).and_return([@repo])
+ stub_client(projects: [@repo])
get :status
@@ -38,7 +42,7 @@ describe Import::GitlabController do
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])
+ stub_client(projects: [@repo])
get :status
@@ -49,25 +53,20 @@ describe Import::GitlabController do
describe "POST create" do
let(:gitlab_username) { user.username }
-
- let(:gitlab_user) {
- {
- username: gitlab_username
- }.with_indifferent_access
- }
-
- let(:gitlab_repo) {
+ let(:gitlab_user) do
+ { username: gitlab_username }.with_indifferent_access
+ end
+ let(:gitlab_repo) do
{
path: 'vim',
path_with_namespace: "#{gitlab_username}/vim",
owner: { name: gitlab_username },
namespace: { path: gitlab_username }
}.with_indifferent_access
- }
+ end
before do
- controller.stub_chain(:client, :user).and_return(gitlab_user)
- controller.stub_chain(:client, :project).and_return(gitlab_repo)
+ stub_client(user: gitlab_user, project: gitlab_repo)
end
context "when the repository owner is the GitLab.com user" do
diff --git a/spec/controllers/import/gitorious_controller_spec.rb b/spec/controllers/import/gitorious_controller_spec.rb
index 07c9484bf1a..7cb1b85a46d 100644
--- a/spec/controllers/import/gitorious_controller_spec.rb
+++ b/spec/controllers/import/gitorious_controller_spec.rb
@@ -1,6 +1,9 @@
require 'spec_helper'
+require_relative 'import_spec_helper'
describe Import::GitoriousController do
+ include ImportSpecHelper
+
let(:user) { create(:user) }
before do
@@ -30,7 +33,7 @@ describe Import::GitoriousController do
it "assigns variables" do
@project = create(:project, import_type: 'gitorious', creator_id: user.id)
- controller.stub_chain(:client, :repos).and_return([@repo])
+ stub_client(repos: [@repo])
get :status
@@ -40,7 +43,7 @@ describe Import::GitoriousController do
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])
+ stub_client(repos: [@repo])
get :status
@@ -59,7 +62,7 @@ describe Import::GitoriousController do
expect(Gitlab::GitoriousImport::ProjectCreator).
to receive(:new).with(@repo, namespace, user).
and_return(double(execute: true))
- controller.stub_chain(:client, :repo).and_return(@repo)
+ stub_client(repo: @repo)
post :create, format: :js
end
diff --git a/spec/controllers/import/google_code_controller_spec.rb b/spec/controllers/import/google_code_controller_spec.rb
index 78c0f5079cc..66088139a69 100644
--- a/spec/controllers/import/google_code_controller_spec.rb
+++ b/spec/controllers/import/google_code_controller_spec.rb
@@ -1,6 +1,9 @@
require 'spec_helper'
+require_relative 'import_spec_helper'
describe Import::GoogleCodeController do
+ include ImportSpecHelper
+
let(:user) { create(:user) }
let(:dump_file) { fixture_file_upload(Rails.root + 'spec/fixtures/GoogleCodeProjectHosting.json', 'application/json') }
@@ -21,13 +24,12 @@ describe Import::GoogleCodeController do
describe "GET status" do
before do
@repo = OpenStruct.new(name: 'vim')
- controller.stub_chain(:client, :valid?).and_return(true)
+ stub_client(valid?: 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])
- controller.stub_chain(:client, :incompatible_repos).and_return([])
+ stub_client(repos: [@repo], incompatible_repos: [])
get :status
@@ -38,8 +40,7 @@ describe Import::GoogleCodeController do
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])
- controller.stub_chain(:client, :incompatible_repos).and_return([])
+ stub_client(repos: [@repo], incompatible_repos: [])
get :status
@@ -48,8 +49,7 @@ describe Import::GoogleCodeController do
end
it "does not show any invalid projects" do
- controller.stub_chain(:client, :repos).and_return([])
- controller.stub_chain(:client, :incompatible_repos).and_return([@repo])
+ stub_client(repos: [], incompatible_repos: [@repo])
get :status
diff --git a/spec/controllers/import/import_spec_helper.rb b/spec/controllers/import/import_spec_helper.rb
new file mode 100644
index 00000000000..9d7648e25a7
--- /dev/null
+++ b/spec/controllers/import/import_spec_helper.rb
@@ -0,0 +1,33 @@
+require 'ostruct'
+
+# Helper methods for controller specs in the Import namespace
+#
+# Must be included manually.
+module ImportSpecHelper
+ # Stub `controller` to return a null object double with the provided messages
+ # when `client` is called
+ #
+ # Examples:
+ #
+ # stub_client(foo: %w(foo))
+ #
+ # controller.client.foo # => ["foo"]
+ # controller.client.bar.baz.foo # => ["foo"]
+ #
+ # Returns the client double
+ def stub_client(messages = {})
+ client = double('client', messages).as_null_object
+ allow(controller).to receive(:client).and_return(client)
+
+ client
+ end
+
+ def stub_omniauth_provider(name)
+ provider = OpenStruct.new(
+ name: name,
+ app_id: 'asd123',
+ app_secret: 'asd123'
+ )
+ Gitlab.config.omniauth.providers << provider
+ end
+end
diff --git a/spec/controllers/merge_requests_controller_spec.rb b/spec/controllers/merge_requests_controller_spec.rb
deleted file mode 100644
index c94ef1629ae..00000000000
--- a/spec/controllers/merge_requests_controller_spec.rb
+++ /dev/null
@@ -1,101 +0,0 @@
-require 'spec_helper'
-
-describe Projects::MergeRequestsController do
- let(:project) { create(:project) }
- let(:user) { create(:user) }
- let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
-
- before do
- sign_in(user)
- project.team << [user, :master]
- end
-
- describe "#show" do
- shared_examples "export merge as" do |format|
- it "should generally work" do
- 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
- expect_any_instance_of(MergeRequest).to receive(:"to_#{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, 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
- allow_any_instance_of(MergeRequest).to receive(:"to_#{format}").
- and_return('HTML entities &<>" ')
-
- 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('&amp;')
- expect(response.body).to_not include('&gt;')
- expect(response.body).to_not include('&lt;')
- expect(response.body).to_not include('&quot;')
- end
- end
-
- describe "as diff" do
- include_examples "export merge as", :diff
- let(:format) { :diff }
-
- it "should really only be a git diff" do
- 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
- end
-
- describe "as patch" do
- include_examples "export merge as", :patch
- let(:format) { :patch }
-
- it "should really be a git email patch with commit" do
- 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, 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
- end
- end
-
- context '#diffs with forked projects with submodules' do
- render_views
- let(:project) { create(:project) }
- let(:fork_project) { create(:forked_project_with_submodules) }
- let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) }
-
- before do
- fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
- fork_project.save
- merge_request.reload
- end
-
- it '#diffs' do
- get(:diffs, namespace_id: project.namespace.to_param,
- project_id: project.to_param, id: merge_request.iid, format: 'json')
- expect(response).to be_success
- expect(response.body).to have_content('Subproject commit')
- end
- end
-end
diff --git a/spec/controllers/profiles/preferences_controller_spec.rb b/spec/controllers/profiles/preferences_controller_spec.rb
index 1f0943c93d8..8f02003992a 100644
--- a/spec/controllers/profiles/preferences_controller_spec.rb
+++ b/spec/controllers/profiles/preferences_controller_spec.rb
@@ -63,7 +63,7 @@ describe Profiles::PreferencesController do
context 'on invalid dashboard setting' do
it 'sets the flash' do
- prefs = {dashboard: 'invalid'}
+ prefs = { dashboard: 'invalid' }
go params: prefs
diff --git a/spec/controllers/profiles/two_factor_auths_controller_spec.rb b/spec/controllers/profiles/two_factor_auths_controller_spec.rb
index 65415f21e55..f54706e3aa3 100644
--- a/spec/controllers/profiles/two_factor_auths_controller_spec.rb
+++ b/spec/controllers/profiles/two_factor_auths_controller_spec.rb
@@ -40,11 +40,11 @@ describe Profiles::TwoFactorAuthsController do
expect(user).to receive(:valid_otp?).with(pin).and_return(true)
end
- it 'sets otp_required_for_login' do
+ it 'sets two_factor_enabled' do
go
user.reload
- expect(user.otp_required_for_login).to eq true
+ expect(user).to be_two_factor_enabled
end
it 'presents plaintext codes for the user to save' do
@@ -105,19 +105,12 @@ describe Profiles::TwoFactorAuthsController do
end
describe 'DELETE destroy' do
- let(:user) { create(:user, :two_factor) }
- let!(:codes) { user.generate_otp_backup_codes! }
+ let(:user) { create(:user, :two_factor) }
- it 'clears all 2FA-related fields' do
- expect(user.otp_required_for_login).to eq true
- expect(user.otp_backup_codes).not_to be_nil
- expect(user.encrypted_otp_secret).not_to be_nil
+ it 'disables two factor' do
+ expect(user).to receive(:disable_two_factor!)
delete :destroy
-
- expect(user.otp_required_for_login).to eq false
- expect(user.otp_backup_codes).to be_nil
- expect(user.encrypted_otp_secret).to be_nil
end
it 'redirects to profile_account_path' do
diff --git a/spec/controllers/projects/compare_controller_spec.rb b/spec/controllers/projects/compare_controller_spec.rb
index 23e1566b8f3..b643b354073 100644
--- a/spec/controllers/projects/compare_controller_spec.rb
+++ b/spec/controllers/projects/compare_controller_spec.rb
@@ -12,8 +12,11 @@ describe Projects::CompareController do
end
it 'compare should show some diffs' do
- get(:show, namespace_id: project.namespace.to_param,
- project_id: project.to_param, from: ref_from, to: ref_to)
+ get(:show,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ from: ref_from,
+ to: ref_to)
expect(response).to be_success
expect(assigns(:diffs).length).to be >= 1
diff --git a/spec/controllers/projects/merge_requests_controller_spec.rb b/spec/controllers/projects/merge_requests_controller_spec.rb
new file mode 100644
index 00000000000..b8db8591709
--- /dev/null
+++ b/spec/controllers/projects/merge_requests_controller_spec.rb
@@ -0,0 +1,176 @@
+require 'spec_helper'
+
+describe Projects::MergeRequestsController do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:merge_request) { create(:merge_request_with_diffs, target_project: project, source_project: project) }
+
+ before do
+ sign_in(user)
+ project.team << [user, :master]
+ end
+
+ describe "#show" do
+ shared_examples "export merge as" do |format|
+ it "should generally work" do
+ 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
+ expect_any_instance_of(MergeRequest).to receive(:"to_#{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,
+ 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
+ allow_any_instance_of(MergeRequest).to receive(:"to_#{format}").
+ and_return('HTML entities &<>" ')
+
+ get(:show,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: merge_request.iid,
+ format: format)
+
+ expect(response.body).not_to include('&amp;')
+ expect(response.body).not_to include('&gt;')
+ expect(response.body).not_to include('&lt;')
+ expect(response.body).not_to include('&quot;')
+ end
+ end
+
+ describe "as diff" do
+ include_examples "export merge as", :diff
+ let(:format) { :diff }
+
+ it "should really only be a git diff" do
+ 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
+ end
+
+ describe "as patch" do
+ include_examples "export merge as", :patch
+ let(:format) { :patch }
+
+ it "should really be a git email patch with commit" do
+ 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,
+ 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
+ end
+ end
+
+ describe 'GET diffs' do
+ def go(format: 'html')
+ get :diffs,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: merge_request.iid,
+ format: format
+ end
+
+ context 'as html' do
+ it 'renders the diff template' do
+ go
+
+ expect(response).to render_template('diffs')
+ end
+ end
+
+ context 'as json' do
+ it 'renders the diffs template to a string' do
+ go format: 'json'
+
+ expect(response).to render_template('projects/merge_requests/show/_diffs')
+ expect(JSON.parse(response.body)).to have_key('html')
+ end
+ end
+
+ context 'with forked projects with submodules' do
+ render_views
+
+ let(:project) { create(:project) }
+ let(:fork_project) { create(:forked_project_with_submodules) }
+ let(:merge_request) { create(:merge_request_with_diffs, source_project: fork_project, source_branch: 'add-submodule-version-bump', target_branch: 'master', target_project: project) }
+
+ before do
+ fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
+ fork_project.save
+ merge_request.reload
+ end
+
+ it 'renders' do
+ go format: 'json'
+
+ expect(response).to be_success
+ expect(response.body).to have_content('Subproject commit')
+ end
+ end
+ end
+
+ describe 'GET commits' do
+ def go(format: 'html')
+ get :commits,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: merge_request.iid,
+ format: format
+ end
+
+ context 'as html' do
+ it 'renders the show template' do
+ go
+
+ expect(response).to render_template('show')
+ end
+ end
+
+ context 'as json' do
+ it 'renders the commits template to a string' do
+ go format: 'json'
+
+ expect(response).to render_template('projects/merge_requests/show/_commits')
+ expect(JSON.parse(response.body)).to have_key('html')
+ end
+ end
+ end
+end
diff --git a/spec/controllers/projects/milestones_controller_spec.rb b/spec/controllers/projects/milestones_controller_spec.rb
new file mode 100644
index 00000000000..d3868c13202
--- /dev/null
+++ b/spec/controllers/projects/milestones_controller_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+describe Projects::MilestonesController do
+ let(:project) { create(:project) }
+ let(:user) { create(:user) }
+ let(:milestone) { create(:milestone, project: project) }
+ let(:issue) { create(:issue, project: project, milestone: milestone) }
+
+ before do
+ sign_in(user)
+ project.team << [user, :master]
+ controller.instance_variable_set(:@project, project)
+ end
+
+ describe "#destroy" do
+ it "should remove milestone" do
+ expect(issue.milestone_id).to eq(milestone.id)
+ delete :destroy, namespace_id: project.namespace.id, project_id: project.id, id: milestone.id, format: :js
+ expect(response).to be_success
+ expect { Milestone.find(milestone.id) }.to raise_exception(ActiveRecord::RecordNotFound)
+ issue.reload
+ expect(issue.milestone_id).to eq(nil)
+ # Check system note left for milestone removal
+ last_note = project.issues.find(issue.id).notes[-1].note
+ expect(last_note).to eq('Milestone removed')
+ end
+ end
+end
diff --git a/spec/controllers/projects/refs_controller_spec.rb b/spec/controllers/projects/refs_controller_spec.rb
index c254ab7cb6e..abd45a74e2d 100644
--- a/spec/controllers/projects/refs_controller_spec.rb
+++ b/spec/controllers/projects/refs_controller_spec.rb
@@ -11,15 +11,20 @@ describe Projects::RefsController do
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
+ 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
+ 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
diff --git a/spec/controllers/projects_controller_spec.rb b/spec/controllers/projects_controller_spec.rb
index a1b82a32150..29233e9fae6 100644
--- a/spec/controllers/projects_controller_spec.rb
+++ b/spec/controllers/projects_controller_spec.rb
@@ -16,30 +16,34 @@ describe ProjectsController do
get :show, "go-get" => "1", namespace_id: "bogus_namespace", id: "bogus_project"
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_falsey
- post(:toggle_star, namespace_id: public_project.namespace.to_param,
+ 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,
+ 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, namespace_id: project.namespace.to_param,
+ 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,
+ post(:toggle_star,
+ namespace_id: project.namespace.to_param,
id: public_project.to_param)
expect(user.starred?(public_project)).to be_falsey
end
diff --git a/spec/controllers/tree_controller_spec.rb b/spec/controllers/tree_controller_spec.rb
index 7b219819bbc..e09caf5df13 100644
--- a/spec/controllers/tree_controller_spec.rb
+++ b/spec/controllers/tree_controller_spec.rb
@@ -19,8 +19,10 @@ describe Projects::TreeController do
render_views
before do
- get(:show, namespace_id: project.namespace.to_param,
- project_id: project.to_param, id: id)
+ get(:show,
+ namespace_id: project.namespace.to_param,
+ project_id: project.to_param,
+ id: id)
end
context "valid branch, no path" do
@@ -48,8 +50,10 @@ describe Projects::TreeController do
render_views
before do
- get(:show, namespace_id: project.namespace.to_param,
- 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
diff --git a/spec/factories.rb b/spec/factories.rb
index 9373a7af024..05e3211d551 100644
--- a/spec/factories.rb
+++ b/spec/factories.rb
@@ -30,8 +30,9 @@ FactoryGirl.define do
trait :two_factor do
before(:create) do |user|
- user.otp_required_for_login = true
+ user.two_factor_enabled = true
user.otp_secret = User.generate_otp_secret(32)
+ user.generate_otp_backup_codes!
end
end
diff --git a/spec/factories/merge_requests.rb b/spec/factories/merge_requests.rb
index 77cd37c22d9..3b7adfe4398 100644
--- a/spec/factories/merge_requests.rb
+++ b/spec/factories/merge_requests.rb
@@ -28,7 +28,7 @@ FactoryGirl.define do
source_project factory: :project
target_project { source_project }
- # → git log --pretty=oneline feature..master
+ # $ git log --pretty=oneline feature..master
# 5937ac0a7beb003549fc5fd26fc247adbce4a52e Add submodule from gitlab.com
# 570e7b2abdd848b95f2f578043fc23bd6f6fd24d Change some files
# 6f6d7e7ed97bb5f0054f2b1df789b39ca89b6ff9 More submodules
diff --git a/spec/features/admin/admin_disables_two_factor_spec.rb b/spec/features/admin/admin_disables_two_factor_spec.rb
new file mode 100644
index 00000000000..71be66303d2
--- /dev/null
+++ b/spec/features/admin/admin_disables_two_factor_spec.rb
@@ -0,0 +1,33 @@
+require 'rails_helper'
+
+feature 'Admin disables 2FA for a user', feature: true do
+ scenario 'successfully', js: true do
+ login_as(:admin)
+ user = create(:user, :two_factor)
+
+ edit_user(user)
+ page.within('.two-factor-status') do
+ click_link 'Disable'
+ end
+
+ page.within('.two-factor-status') do
+ expect(page).to have_content 'Disabled'
+ expect(page).not_to have_button 'Disable'
+ end
+ end
+
+ scenario 'for a user without 2FA enabled' do
+ login_as(:admin)
+ user = create(:user)
+
+ edit_user(user)
+
+ page.within('.two-factor-status') do
+ expect(page).not_to have_button 'Disable'
+ end
+ end
+
+ def edit_user(user)
+ visit admin_user_path(user)
+ end
+end
diff --git a/spec/features/admin/admin_users_spec.rb b/spec/features/admin/admin_users_spec.rb
index f97b69713ce..86717761582 100644
--- a/spec/features/admin/admin_users_spec.rb
+++ b/spec/features/admin/admin_users_spec.rb
@@ -16,6 +16,46 @@ describe "Admin::Users", feature: true do
expect(page).to have_content(@user.email)
expect(page).to have_content(@user.name)
end
+
+ describe 'Two-factor Authentication filters' do
+ it 'counts users who have enabled 2FA' do
+ create(:user, two_factor_enabled: true)
+
+ visit admin_users_path
+
+ page.within('.filter-two-factor-enabled small') do
+ expect(page).to have_content('1')
+ end
+ end
+
+ it 'filters by users who have enabled 2FA' do
+ user = create(:user, two_factor_enabled: true)
+
+ visit admin_users_path
+ click_link '2FA Enabled'
+
+ expect(page).to have_content(user.email)
+ end
+
+ it 'counts users who have not enabled 2FA' do
+ create(:user, two_factor_enabled: false)
+
+ visit admin_users_path
+
+ page.within('.filter-two-factor-disabled small') do
+ expect(page).to have_content('2') # Including admin
+ end
+ end
+
+ it 'filters by users who have not enabled 2FA' do
+ user = create(:user, two_factor_enabled: false)
+
+ visit admin_users_path
+ click_link '2FA Disabled'
+
+ expect(page).to have_content(user.email)
+ end
+ end
end
describe "GET /admin/users/new" do
@@ -63,15 +103,35 @@ describe "Admin::Users", feature: true do
end
describe "GET /admin/users/:id" do
- before do
+ it "should have user info" do
visit admin_users_path
- click_link "#{@user.name}"
- end
+ click_link @user.name
- it "should have user info" do
expect(page).to have_content(@user.email)
expect(page).to have_content(@user.name)
end
+
+ describe 'Two-factor Authentication status' do
+ it 'shows when enabled' do
+ @user.update_attribute(:two_factor_enabled, true)
+
+ visit admin_user_path(@user)
+
+ expect_two_factor_status('Enabled')
+ end
+
+ it 'shows when disabled' do
+ visit admin_user_path(@user)
+
+ expect_two_factor_status('Disabled')
+ end
+
+ def expect_two_factor_status(status)
+ page.within('.two-factor-status') do
+ expect(page).to have_content(status)
+ end
+ end
+ end
end
describe "GET /admin/users/:id/edit" do
diff --git a/spec/features/atom/users_spec.rb b/spec/features/atom/users_spec.rb
index 770ac04c2c5..dc41be8246f 100644
--- a/spec/features/atom/users_spec.rb
+++ b/spec/features/atom/users_spec.rb
@@ -14,17 +14,24 @@ describe "User Feed", feature: true do
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.")
+ 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)
+ 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,
+ title: 'Fix bug',
+ author: user,
+ source_project: project,
+ target_project: project,
description: "Here is the fix: ![an image](image.png)")
end
diff --git a/spec/features/gitlab_flavored_markdown_spec.rb b/spec/features/gitlab_flavored_markdown_spec.rb
index 16d1ca55f8d..0c1bc53cdb5 100644
--- a/spec/features/gitlab_flavored_markdown_spec.rb
+++ b/spec/features/gitlab_flavored_markdown_spec.rb
@@ -11,7 +11,8 @@ describe "GitLab Flavored Markdown", feature: true do
end
before do
- Commit.any_instance.stub(title: "fix #{issue.to_reference}\n\nask #{fred.to_reference} for details")
+ allow_any_instance_of(Commit).to receive(:title).
+ and_return("fix #{issue.to_reference}\n\nask #{fred.to_reference} for details")
end
let(:commit) { project.commit }
diff --git a/spec/features/groups_spec.rb b/spec/features/groups_spec.rb
index edc1c63a0aa..891df65216d 100644
--- a/spec/features/groups_spec.rb
+++ b/spec/features/groups_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Group' do
+feature 'Group', feature: true do
describe 'description' do
let(:group) { create(:group) }
let(:path) { group_path(group) }
diff --git a/spec/features/issues/filter_by_milestone_spec.rb b/spec/features/issues/filter_by_milestone_spec.rb
new file mode 100644
index 00000000000..f600f8684ac
--- /dev/null
+++ b/spec/features/issues/filter_by_milestone_spec.rb
@@ -0,0 +1,36 @@
+require 'rails_helper'
+
+feature 'Issue filtering by Milestone', feature: true do
+ include Select2Helper
+
+ let(:project) { create(:project, :public) }
+ let(:milestone) { create(:milestone, project: project) }
+
+ scenario 'filters by no Milestone', js: true do
+ create(:issue, project: project)
+ create(:issue, project: project, milestone: milestone)
+
+ visit_issues(project)
+ filter_by_milestone(Milestone::None.title)
+
+ expect(page).to have_css('.issue-title', count: 1)
+ end
+
+ scenario 'filters by a specific Milestone', js: true do
+ create(:issue, project: project, milestone: milestone)
+ create(:issue, project: project)
+
+ visit_issues(project)
+ filter_by_milestone(milestone.title)
+
+ expect(page).to have_css('.issue-title', count: 1)
+ end
+
+ def visit_issues(project)
+ visit namespace_project_issues_path(project.namespace, project)
+ end
+
+ def filter_by_milestone(title)
+ select2(title, from: '#milestone_title')
+ end
+end
diff --git a/spec/features/issues_spec.rb b/spec/features/issues_spec.rb
index d803a1805de..32fd4065bb4 100644
--- a/spec/features/issues_spec.rb
+++ b/spec/features/issues_spec.rb
@@ -36,9 +36,7 @@ describe 'Issues', feature: true do
end
it 'does not change issue count' do
- expect {
- click_button 'Save changes'
- }.to_not change { Issue.count }
+ expect { click_button 'Save changes' }.to_not change { Issue.count }
end
it 'should update issue fields' do
@@ -94,22 +92,6 @@ describe 'Issues', feature: true do
let(:issue) { @issue }
- it 'should allow filtering by issues with no specified milestone' do
- visit namespace_project_issues_path(project.namespace, project, milestone_title: IssuableFinder::NONE)
-
- 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 namespace_project_issues_path(project.namespace, project, milestone_title: issue.milestone.title)
-
- 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 namespace_project_issues_path(project.namespace, project, assignee_id: IssuableFinder::NONE)
@@ -220,7 +202,7 @@ describe 'Issues', feature: true do
it 'with dropdown menu' do
visit namespace_project_issue_path(project.namespace, project, issue)
- find('.edit-issue.inline-update #issue_assignee_id').
+ find('.context #issue_assignee_id').
set project.team.members.first.id
click_button 'Update Issue'
@@ -259,7 +241,7 @@ describe 'Issues', feature: true do
it 'with dropdown menu' do
visit namespace_project_issue_path(project.namespace, project, issue)
- find('.edit-issue.inline-update').
+ find('.context').
select(milestone.title, from: 'issue_milestone_id')
click_button 'Update Issue'
@@ -295,7 +277,7 @@ describe 'Issues', feature: true do
issue.save
end
- it 'allows user to remove assignee', :js => true do
+ it 'allows user to remove assignee', js: true do
visit namespace_project_issue_path(project.namespace, project, issue)
expect(page).to have_content "Assignee: #{user2.name}"
diff --git a/spec/features/login_spec.rb b/spec/features/login_spec.rb
index 046a9f6191d..cef432e512b 100644
--- a/spec/features/login_spec.rb
+++ b/spec/features/login_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Login' do
+feature 'Login', feature: true do
describe 'with two-factor authentication' do
context 'with valid username/password' do
let(:user) { create(:user, :two_factor) }
diff --git a/spec/features/markdown_spec.rb b/spec/features/markdown_spec.rb
index 902968cebcb..b8199aa5e61 100644
--- a/spec/features/markdown_spec.rb
+++ b/spec/features/markdown_spec.rb
@@ -32,7 +32,7 @@ require 'erb'
#
# See the MarkdownFeature class for setup details.
-describe 'GitLab Markdown' do
+describe 'GitLab Markdown', feature: true do
include ActionView::Helpers::TagHelper
include ActionView::Helpers::UrlHelper
include Capybara::Node::Matchers
diff --git a/spec/features/merge_requests/filter_by_milestone_spec.rb b/spec/features/merge_requests/filter_by_milestone_spec.rb
new file mode 100644
index 00000000000..f70214e1122
--- /dev/null
+++ b/spec/features/merge_requests/filter_by_milestone_spec.rb
@@ -0,0 +1,36 @@
+require 'rails_helper'
+
+feature 'Merge Request filtering by Milestone', feature: true do
+ include Select2Helper
+
+ let(:project) { create(:project, :public) }
+ let(:milestone) { create(:milestone, project: project) }
+
+ scenario 'filters by no Milestone', js: true do
+ create(:merge_request, :with_diffs, source_project: project)
+ create(:merge_request, :simple, source_project: project, milestone: milestone)
+
+ visit_merge_requests(project)
+ filter_by_milestone(Milestone::None.title)
+
+ expect(page).to have_css('.merge-request-title', count: 1)
+ end
+
+ scenario 'filters by a specific Milestone', js: true do
+ create(:merge_request, :with_diffs, source_project: project, milestone: milestone)
+ create(:merge_request, :simple, source_project: project)
+
+ visit_merge_requests(project)
+ filter_by_milestone(milestone.title)
+
+ expect(page).to have_css('.merge-request-title', count: 1)
+ end
+
+ def visit_merge_requests(project)
+ visit namespace_project_merge_requests_path(project.namespace, project)
+ end
+
+ def filter_by_milestone(title)
+ select2(title, from: '#milestone_title')
+ end
+end
diff --git a/spec/features/notes_on_merge_requests_spec.rb b/spec/features/notes_on_merge_requests_spec.rb
index 219bb3129e7..d7cb3b2e86e 100644
--- a/spec/features/notes_on_merge_requests_spec.rb
+++ b/spec/features/notes_on_merge_requests_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Comments' do
+describe 'Comments', feature: true do
include RepoHelpers
describe 'On a merge request', js: true, feature: true do
@@ -86,13 +86,13 @@ describe 'Comments' do
end
# 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
- #expect(find(".js-note-text", visible: false).text).to eq note.note
- #end
- #end
+ # 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
+ # expect(find(".js-note-text", visible: false).text).to eq note.note
+ # end
+ # end
it 'appends the edited at time to the note' do
page.within('.current-note-edit-form') do
@@ -223,8 +223,7 @@ describe 'Comments' do
sample_compare.changes.last[:line_code]
end
- def click_diff_line(data = nil)
- data ||= line_code
- find("button[data-line-code=\"#{data}\"]").click
+ def click_diff_line(data = line_code)
+ page.find(%Q{button[data-line-code="#{data}"]}, visible: false).click
end
end
diff --git a/spec/features/password_reset_spec.rb b/spec/features/password_reset_spec.rb
index a34efce09ef..2b6311e4fd7 100644
--- a/spec/features/password_reset_spec.rb
+++ b/spec/features/password_reset_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Password reset' do
+feature 'Password reset', feature: true do
def forgot_password
click_on 'Forgot your password?'
fill_in 'Email', with: user.email
diff --git a/spec/features/profile_spec.rb b/spec/features/profile_spec.rb
index 3d36a3c02d0..c80253fead8 100644
--- a/spec/features/profile_spec.rb
+++ b/spec/features/profile_spec.rb
@@ -9,7 +9,7 @@ describe 'Profile account page', feature: true do
describe 'when signup is enabled' do
before do
- ApplicationSetting.any_instance.stub(signup_enabled?: true)
+ stub_application_setting(signup_enabled: true)
visit profile_account_path
end
@@ -23,7 +23,7 @@ describe 'Profile account page', feature: true do
describe 'when signup is disabled' do
before do
- ApplicationSetting.any_instance.stub(signup_enabled?: false)
+ stub_application_setting(signup_enabled: false)
visit profile_account_path
end
diff --git a/spec/features/profiles/preferences_spec.rb b/spec/features/profiles/preferences_spec.rb
index 69d15f41706..9bc6145dda4 100644
--- a/spec/features/profiles/preferences_spec.rb
+++ b/spec/features/profiles/preferences_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-describe 'Profile > Preferences' do
+describe 'Profile > Preferences', feature: true do
let(:user) { create(:user) }
before do
@@ -75,7 +75,7 @@ describe 'Profile > Preferences' do
end
def expect_preferences_saved_message
- within('.flash-container') do
+ page.within('.flash-container') do
expect(page).to have_content('Preferences saved.')
end
end
diff --git a/spec/features/projects_spec.rb b/spec/features/projects_spec.rb
index f8eea70ec4a..c8d44efdc8b 100644
--- a/spec/features/projects_spec.rb
+++ b/spec/features/projects_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Project' do
+feature 'Project', feature: true do
describe 'description' do
let(:project) { create(:project) }
let(:path) { namespace_project_path(project.namespace, project) }
diff --git a/spec/features/search_spec.rb b/spec/features/search_spec.rb
index 479334f45d8..84c036e59c0 100644
--- a/spec/features/search_spec.rb
+++ b/spec/features/search_spec.rb
@@ -17,4 +17,3 @@ describe "Search", feature: true do
expect(page).to have_content @project.name
end
end
-
diff --git a/spec/features/security/profile_access_spec.rb b/spec/features/security/profile_access_spec.rb
index 2b09771851e..bcabc2d53ac 100644
--- a/spec/features/security/profile_access_spec.rb
+++ b/spec/features/security/profile_access_spec.rb
@@ -6,7 +6,7 @@ describe "Profile access", feature: true do
end
describe "GET /login" do
- it { expect(new_user_session_path).not_to be_404_for :visitor }
+ it { expect(new_user_session_path).not_to be_not_found_for :visitor }
end
describe "GET /profile/keys" do
@@ -45,8 +45,8 @@ describe "Profile access", feature: true do
it { is_expected.to be_denied_for :visitor }
end
- describe "GET /profile/history" do
- subject { history_profile_path }
+ describe "GET /profile/audit_log" do
+ subject { audit_log_profile_path }
it { is_expected.to be_allowed_for @u1 }
it { is_expected.to be_allowed_for :admin }
diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb
index 8d1bfd25223..4649e58cb1a 100644
--- a/spec/features/security/project/internal_access_spec.rb
+++ b/spec/features/security/project/internal_access_spec.rb
@@ -138,6 +138,18 @@ describe "Internal Project Access", feature: true do
it { is_expected.to be_denied_for :visitor }
end
+ describe "GET /:project_path/issues/:id/edit" do
+ let(:issue) { create(:issue, project: project) }
+ subject { edit_namespace_project_issue_path(project.namespace, project, issue) }
+
+ 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 { namespace_project_snippets_path(project.namespace, project) }
diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb
index 9021ff33186..2866bf0355b 100644
--- a/spec/features/security/project/private_access_spec.rb
+++ b/spec/features/security/project/private_access_spec.rb
@@ -138,6 +138,18 @@ describe "Private Project Access", feature: true do
it { is_expected.to be_denied_for :visitor }
end
+ describe "GET /:project_path/issues/:id/edit" do
+ let(:issue) { create(:issue, project: project) }
+ subject { edit_namespace_project_issue_path(project.namespace, project, issue) }
+
+ 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 { namespace_project_snippets_path(project.namespace, project) }
diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb
index 6ec190ed777..554c96bcdc5 100644
--- a/spec/features/security/project/public_access_spec.rb
+++ b/spec/features/security/project/public_access_spec.rb
@@ -143,6 +143,18 @@ describe "Public Project Access", feature: true do
it { is_expected.to be_allowed_for :visitor }
end
+ describe "GET /:project_path/issues/:id/edit" do
+ let(:issue) { create(:issue, project: project) }
+ subject { edit_namespace_project_issue_path(project.namespace, project, issue) }
+
+ 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 { namespace_project_snippets_path(project.namespace, project) }
diff --git a/spec/features/task_lists_spec.rb b/spec/features/task_lists_spec.rb
index 2099fc40cca..fca3c77fc64 100644
--- a/spec/features/task_lists_spec.rb
+++ b/spec/features/task_lists_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Task Lists' do
+feature 'Task Lists', feature: true do
include Warden::Test::Helpers
let(:project) { create(:project) }
@@ -52,7 +52,7 @@ feature 'Task Lists' do
expect(page).to have_selector(container)
expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox")
expect(page).to have_selector("#{container} .js-task-list-field")
- expect(page).to have_selector('form.js-issue-update')
+ expect(page).to have_selector('form.js-issuable-update')
expect(page).to have_selector('a.btn-close')
end
@@ -128,7 +128,7 @@ feature 'Task Lists' do
expect(page).to have_selector(container)
expect(page).to have_selector("#{container} .wiki .task-list .task-list-item .task-list-item-checkbox")
expect(page).to have_selector("#{container} .js-task-list-field")
- expect(page).to have_selector('form.js-merge-request-update')
+ expect(page).to have_selector('form.js-issuable-update')
expect(page).to have_selector('a.btn-close')
end
diff --git a/spec/features/users_spec.rb b/spec/features/users_spec.rb
index a4c3dfe9205..efcb8a31abe 100644
--- a/spec/features/users_spec.rb
+++ b/spec/features/users_spec.rb
@@ -1,6 +1,6 @@
require 'spec_helper'
-feature 'Users' do
+feature 'Users', feature: true do
scenario 'GET /users/sign_in creates a new user account' do
visit new_user_session_path
fill_in 'user_name', with: 'Name Surname'
diff --git a/spec/helpers/application_helper_spec.rb b/spec/helpers/application_helper_spec.rb
index 47e10197f5c..742420f550e 100644
--- a/spec/helpers/application_helper_spec.rb
+++ b/spec/helpers/application_helper_spec.rb
@@ -2,157 +2,177 @@ require 'spec_helper'
describe ApplicationHelper do
describe 'current_controller?' do
- before do
- allow(controller).to receive(:controller_name).and_return('foo')
- end
-
it 'returns true when controller matches argument' do
- expect(current_controller?(:foo)).to be_truthy
+ stub_controller_name('foo')
+
+ expect(helper.current_controller?(:foo)).to eq true
end
it 'returns false when controller does not match argument' do
- expect(current_controller?(:bar)).not_to be_truthy
+ stub_controller_name('foo')
+
+ expect(helper.current_controller?(:bar)).to eq false
end
- 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
+ it 'takes any number of arguments' do
+ stub_controller_name('foo')
+
+ expect(helper.current_controller?(:baz, :bar)).to eq false
+ expect(helper.current_controller?(:baz, :bar, :foo)).to eq true
+ end
+
+ def stub_controller_name(value)
+ allow(helper.controller).to receive(:controller_name).and_return(value)
end
end
describe 'current_action?' do
- before do
- allow(self).to receive(:action_name).and_return('foo')
+ it 'returns true when action matches' do
+ stub_action_name('foo')
+
+ expect(helper.current_action?(:foo)).to eq true
end
- it 'returns true when action matches argument' do
- expect(current_action?(:foo)).to be_truthy
+ it 'returns false when action does not match' do
+ stub_action_name('foo')
+
+ expect(helper.current_action?(:bar)).to eq false
end
- it 'returns false when action does not match argument' do
- expect(current_action?(:bar)).not_to be_truthy
+ it 'takes any number of arguments' do
+ stub_action_name('foo')
+
+ expect(helper.current_action?(:baz, :bar)).to eq false
+ expect(helper.current_action?(:baz, :bar, :foo)).to eq true
end
- 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
+ def stub_action_name(value)
+ allow(helper).to receive(:action_name).and_return(value)
end
end
describe 'project_icon' do
- avatar_file_path = File.join(Rails.root, 'public', 'gitlab_logo.png')
+ let(:avatar_file_path) { File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') }
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(
- "<img alt=\"Gitlab logo\" src=\"#{avatar_url}\" />"
- )
+ project = create(:project, avatar: File.open(avatar_file_path))
+
+ avatar_url = "http://localhost/uploads/project/avatar/#{project.id}/banana_sample.gif"
+ expect(helper.project_icon("#{project.namespace.to_param}/#{project.to_param}").to_s).
+ to eq "<img alt=\"Banana sample\" src=\"#{avatar_url}\" />"
end
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(
+ expect(helper.project_icon("#{project.namespace.to_param}/#{project.to_param}").to_s).to match(
image_tag(avatar_url))
end
end
describe 'avatar_icon' do
- avatar_file_path = File.join(Rails.root, 'public', 'gitlab_logo.png')
+ let(:avatar_file_path) { File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif') }
it 'should return an url for the avatar' do
- user = create(:user)
- user.avatar = File.open(avatar_file_path)
- user.save!
- expect(avatar_icon(user.email).to_s).
- to match("/uploads/user/avatar/#{ user.id }/gitlab_logo.png")
+ user = create(:user, avatar: File.open(avatar_file_path))
+
+ expect(helper.avatar_icon(user.email).to_s).
+ to match("/uploads/user/avatar/#{user.id}/banana_sample.gif")
end
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))
+ stub_config_setting(relative_url_root: '/gitlab')
+ # Must be stubbed after the stub above, and separately
+ stub_config_setting(url: Settings.send(:build_gitlab_url))
+
+ user = create(:user, avatar: File.open(avatar_file_path))
- 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")
+ expect(helper.avatar_icon(user.email).to_s).
+ to match("/gitlab/uploads/user/avatar/#{user.id}/banana_sample.gif")
end
- it 'should call gravatar_icon when no avatar is present' do
- user = create(:user, email: 'test@example.com')
- user.save!
- expect(avatar_icon(user.email).to_s).to eq('http://www.gravatar.com/avatar/55502f40dc8b7c769880b10874abc9d0?s=40&d=identicon')
+ it 'should call gravatar_icon when no User exists with the given email' do
+ expect(helper).to receive(:gravatar_icon).with('foo@example.com', 20)
+
+ helper.avatar_icon('foo@example.com', 20)
end
end
describe 'gravatar_icon' do
let(:user_email) { 'user@email.com' }
- 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
+ context 'with Gravatar disabled' do
+ before do
+ stub_application_setting(gravatar_enabled?: false)
+ end
- it 'should return a generic avatar path when email is blank' do
- expect(gravatar_icon('')).to match('no_avatar.png')
+ it 'returns a generic avatar' do
+ expect(helper.gravatar_icon(user_email)).to match('no_avatar.png')
+ end
end
- it 'should return default gravatar url' do
- Gitlab.config.gitlab.stub(https: false)
- url = 'http://www.gravatar.com/avatar/b58c6f14d292556214bd64909bcdb118'
- expect(gravatar_icon(user_email)).to match(url)
- end
+ context 'with Gravatar enabled' do
+ before do
+ stub_application_setting(gravatar_enabled?: true)
+ end
- it 'should use SSL when appropriate' do
- Gitlab.config.gitlab.stub(https: true)
- expect(gravatar_icon(user_email)).to match('https://secure.gravatar.com')
- end
+ it 'returns a generic avatar when email is blank' do
+ expect(helper.gravatar_icon('')).to match('no_avatar.png')
+ end
- it 'should return custom gravatar path when gravatar_url is set' do
- allow(self).to receive(:request).and_return(double(:ssl? => false))
- 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 'returns a valid Gravatar URL' do
+ stub_config_setting(https: false)
- it 'should accept a custom size' do
- allow(self).to receive(:request).and_return(double(:ssl? => false))
- expect(gravatar_icon(user_email, 64)).to match(/\?s=64/)
- end
+ expect(helper.gravatar_icon(user_email)).
+ to match('http://www.gravatar.com/avatar/b58c6f14d292556214bd64909bcdb118')
+ end
- it 'should use default size when size is wrong' do
- allow(self).to receive(:request).and_return(double(:ssl? => false))
- expect(gravatar_icon(user_email, nil)).to match(/\?s=40/)
- end
+ it 'uses HTTPs when configured' do
+ stub_config_setting(https: true)
+
+ expect(helper.gravatar_icon(user_email)).
+ to match('https://secure.gravatar.com')
+ end
+
+ it 'should return custom gravatar path when gravatar_url is set' do
+ stub_gravatar_setting(plain_url: 'http://example.local/?s=%{size}&hash=%{hash}')
+
+ expect(gravatar_icon(user_email, 20)).
+ to eq('http://example.local/?s=20&hash=b58c6f14d292556214bd64909bcdb118')
+ end
- it 'should be case insensitive' do
- allow(self).to receive(:request).and_return(double(:ssl? => false))
- expect(gravatar_icon(user_email)).
- to eq(gravatar_icon(user_email.upcase + ' '))
+ it 'accepts a custom size argument' do
+ expect(helper.gravatar_icon(user_email, 64)).to include '?s=64'
+ end
+
+ it 'defaults size to 40 when given an invalid size' do
+ expect(helper.gravatar_icon(user_email, nil)).to include '?s=40'
+ end
+
+ it 'ignores case and surrounding whitespace' do
+ normal = helper.gravatar_icon('foo@example.com')
+ upcase = helper.gravatar_icon(' FOO@EXAMPLE.COM ')
+
+ expect(normal).to eq upcase
+ end
end
end
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
- end
-
- let(:options) { grouped_options_refs }
+ let(:options) { helper.grouped_options_refs }
+ let(:project) { create(:project) }
before do
- # Must be an instance variable
- @project = create(:project)
+ assign(:project, project)
+
+ # Override Rails' grouped_options_for_select helper to just return the
+ # first argument (`options`), since it's easier to work with than the
+ # generated HTML.
+ allow(helper).to receive(:grouped_options_for_select).
+ and_wrap_original { |_, *args| args.first }
end
it 'includes a list of branch names' do
@@ -167,15 +187,16 @@ describe ApplicationHelper do
it 'includes a specific commit ref if defined' do
# Must be an instance variable
- @ref = '2ed06dc41dbb5936af845b87d79e05bbf24c73b8'
+ ref = '2ed06dc41dbb5936af845b87d79e05bbf24c73b8'
+ assign(:ref, ref)
expect(options[2][0]).to eq('Commit')
- expect(options[2][1]).to eq([@ref])
+ expect(options[2][1]).to eq([ref])
end
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).
+ 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'])
@@ -189,54 +210,66 @@ describe ApplicationHelper do
let(:a_tag) { '<a href="#">Foo</a>' }
it 'allows the a tag' do
- expect(simple_sanitize(a_tag)).to eq(a_tag)
+ expect(helper.simple_sanitize(a_tag)).to eq(a_tag)
end
it 'allows the span tag' do
input = '<span class="foo">Bar</span>'
- expect(simple_sanitize(input)).to eq(input)
+ expect(helper.simple_sanitize(input)).to eq(input)
end
it 'disallows other tags' do
input = "<strike><b>#{a_tag}</b></strike>"
- expect(simple_sanitize(input)).to eq(a_tag)
+ expect(helper.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('<a href="/">Home</a>')
+ describe 'time_ago_with_tooltip' do
+ def element(*arguments)
+ Time.zone = 'UTC'
+ time = Time.zone.parse('2015-07-02 08:00')
+ element = helper.time_ago_with_tooltip(time, *arguments)
+
+ Nokogiri::HTML::DocumentFragment.parse(element).first_element_child
+ end
+
+ it 'returns a time element' do
+ expect(element.name).to eq 'time'
end
- it 'should include rel=nofollow for external links' do
- expect(link_to('Example', 'http://www.example.com')).
- to eq '<a href="http://www.example.com" rel="nofollow">Example</a>'
+ it 'includes the date string' do
+ expect(element.text).to eq '2015-07-02 08:00:00 UTC'
end
- 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 '<a class="toggle" data-toggle="dropdown" href="http://www.example.com" rel="nofollow">Example</a>'
+ it 'has a datetime attribute' do
+ expect(element.attr('datetime')).to eq '2015-07-02T08:00:00Z'
end
- 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 '<a href="http://www.example.com" rel="noreferrer nofollow">Example</a>'
+ it 'has a formatted title attribute' do
+ expect(element.attr('title')).to eq 'Jul 02, 2015 8:00am'
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 '<a href="http://example.foo/bar">Example</a>'
+ it 'includes a default js-timeago class' do
+ expect(element.attr('class')).to eq 'time_ago js-timeago'
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
+ it 'accepts a custom html_class' do
+ expect(element(html_class: 'custom_class').attr('class')).to eq 'custom_class js-timeago'
+ end
+
+ it 'accepts a custom tooltip placement' do
+ expect(element(placement: 'bottom').attr('data-placement')).to eq 'bottom'
+ end
+
+ it 're-initializes timeago Javascript' do
+ el = element.next_element
+
+ expect(el.name).to eq 'script'
+ expect(el.text).to include "$('.js-timeago').timeago()"
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
+ it 'allows the script tag to be excluded' do
+ expect(element(skip_js: true)).not_to include 'script'
end
end
@@ -245,21 +278,21 @@ describe ApplicationHelper do
it 'should preserve encoding' do
expect(content.encoding.name).to eq('UTF-8')
- expect(render_markup('foo.rst', content).encoding.name).to eq('UTF-8')
+ expect(helper.render_markup('foo.rst', content).encoding.name).to eq('UTF-8')
end
it "should delegate to #markdown when file name corresponds to Markdown" do
- expect(self).to receive(:gitlab_markdown?).with('foo.md').and_return(true)
- expect(self).to receive(:markdown).and_return('NOEL')
+ expect(helper).to receive(:gitlab_markdown?).with('foo.md').and_return(true)
+ expect(helper).to receive(:markdown).and_return('NOEL')
- expect(render_markup('foo.md', content)).to eq('NOEL')
+ expect(helper.render_markup('foo.md', content)).to eq('NOEL')
end
it "should delegate to #asciidoc when file name corresponds to AsciiDoc" do
- expect(self).to receive(:asciidoc?).with('foo.adoc').and_return(true)
- expect(self).to receive(:asciidoc).and_return('NOEL')
+ expect(helper).to receive(:asciidoc?).with('foo.adoc').and_return(true)
+ expect(helper).to receive(:asciidoc).and_return('NOEL')
- expect(render_markup('foo.adoc', content)).to eq('NOEL')
+ expect(helper.render_markup('foo.adoc', content)).to eq('NOEL')
end
end
end
diff --git a/spec/helpers/broadcast_messages_helper_spec.rb b/spec/helpers/broadcast_messages_helper_spec.rb
index f6df12662bb..c7c6f45d144 100644
--- a/spec/helpers/broadcast_messages_helper_spec.rb
+++ b/spec/helpers/broadcast_messages_helper_spec.rb
@@ -2,20 +2,20 @@ require 'spec_helper'
describe BroadcastMessagesHelper do
describe 'broadcast_styling' do
- let(:broadcast_message) { double(color: "", font: "") }
+ let(:broadcast_message) { double(color: '', font: '') }
context "default style" do
it "should have no style" do
- expect(broadcast_styling(broadcast_message)).to match('')
+ expect(broadcast_styling(broadcast_message)).to eq ''
end
end
- context "customiezd style" do
- before { broadcast_message.stub(color: "#f2dede", font: "#b94a48") }
+ context "customized style" do
+ let(:broadcast_message) { double(color: "#f2dede", font: '#b94a48') }
it "should have a customized style" do
expect(broadcast_styling(broadcast_message)).
- to match('background-color:#f2dede;color:#b94a48')
+ 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
index e0be2df0e5e..7c96a74e581 100644
--- a/spec/helpers/diff_helper_spec.rb
+++ b/spec/helpers/diff_helper_spec.rb
@@ -48,19 +48,19 @@ describe DiffHelper do
end
it 'should return only the first file if the diff line count in the 2nd file takes the total beyond safe limits' do
- diffs[1].diff.stub(lines: [""] * 4999) #simulate 4999 lines
+ allow(diffs[1].diff).to receive(:lines).and_return([""] * 4999) #simulate 4999 lines
expect(safe_diff_files(diffs).length).to eq(1)
end
it 'should return all files from a commit that is beyond safe limit for numbers of lines if force diff is true' do
allow(controller).to receive(:params) { { force_show_diff: true } }
- diffs[1].diff.stub(lines: [""] * 4999) #simulate 4999 lines
+ allow(diffs[1].diff).to receive(:lines).and_return([""] * 4999) #simulate 4999 lines
expect(safe_diff_files(diffs).length).to eq(2)
end
it 'should return only the first file if the diff line count in the 2nd file takes the total beyond hard limits' do
allow(controller).to receive(:params) { { force_show_diff: true } }
- diffs[1].diff.stub(lines: [""] * 49999) #simulate 49999 lines
+ allow(diffs[1].diff).to receive(:lines).and_return([""] * 49999) #simulate 49999 lines
expect(safe_diff_files(diffs).length).to eq(1)
end
diff --git a/spec/helpers/gitlab_markdown_helper_spec.rb b/spec/helpers/gitlab_markdown_helper_spec.rb
index bbb434638ce..a42ccb9b501 100644
--- a/spec/helpers/gitlab_markdown_helper_spec.rb
+++ b/spec/helpers/gitlab_markdown_helper_spec.rb
@@ -133,4 +133,11 @@ describe GitlabMarkdownHelper do
helper.render_wiki_content(@wiki)
end
end
+
+ describe 'random_markdown_tip' do
+ it 'returns a random Markdown tip' do
+ stub_const("#{described_class}::MARKDOWN_TIPS", ['Random tip'])
+ expect(random_markdown_tip).to eq 'Random tip'
+ end
+ end
end
diff --git a/spec/helpers/groups_helper.rb b/spec/helpers/groups_helper.rb
index 3e99ab84ec9..5d174460681 100644
--- a/spec/helpers/groups_helper.rb
+++ b/spec/helpers/groups_helper.rb
@@ -2,14 +2,14 @@ require 'spec_helper'
describe GroupsHelper do
describe 'group_icon' do
- avatar_file_path = File.join(Rails.root, 'public', 'gitlab_logo.png')
+ avatar_file_path = File.join(Rails.root, 'spec', 'fixtures', 'banana_sample.gif')
it 'should return an url for the avatar' do
group = create(:group)
group.avatar = File.open(avatar_file_path)
group.save!
expect(group_icon(group.path).to_s).
- to match("/uploads/group/avatar/#{ group.id }/gitlab_logo.png")
+ to match("/uploads/group/avatar/#{ group.id }/banana_sample.gif")
end
it 'should give default avatar_icon when no avatar is present' do
diff --git a/spec/helpers/notifications_helper_spec.rb b/spec/helpers/notifications_helper_spec.rb
index 482cb33e94f..f1aba4cfdf3 100644
--- a/spec/helpers/notifications_helper_spec.rb
+++ b/spec/helpers/notifications_helper_spec.rb
@@ -1,14 +1,11 @@
require 'spec_helper'
describe NotificationsHelper do
- include FontAwesome::Rails::IconHelper
- include IconsHelper
-
describe 'notification_icon' do
let(:notification) { double(disabled?: false, participating?: false, watch?: false) }
context "disabled notification" do
- before { notification.stub(disabled?: true) }
+ before { allow(notification).to receive(:disabled?).and_return(true) }
it "has a red icon" do
expect(notification_icon(notification)).to match('class="fa fa-volume-off ns-mute"')
@@ -16,7 +13,7 @@ describe NotificationsHelper do
end
context "participating notification" do
- before { notification.stub(participating?: true) }
+ before { allow(notification).to receive(:participating?).and_return(true) }
it "has a blue icon" do
expect(notification_icon(notification)).to match('class="fa fa-volume-down ns-part"')
@@ -24,7 +21,7 @@ describe NotificationsHelper do
end
context "watched notification" do
- before { notification.stub(watch?: true) }
+ before { allow(notification).to receive(:watch?).and_return(true) }
it "has a green icon" do
expect(notification_icon(notification)).to match('class="fa fa-volume-up ns-watch"')
diff --git a/spec/helpers/oauth_helper_spec.rb b/spec/helpers/oauth_helper_spec.rb
index 088c342fa13..3ef35f35102 100644
--- a/spec/helpers/oauth_helper_spec.rb
+++ b/spec/helpers/oauth_helper_spec.rb
@@ -17,4 +17,4 @@ describe OauthHelper do
expect(helper.additional_providers).to eq([])
end
end
-end \ No newline at end of file
+end
diff --git a/spec/helpers/preferences_helper_spec.rb b/spec/helpers/preferences_helper_spec.rb
index 920de8c4325..d814b562113 100644
--- a/spec/helpers/preferences_helper_spec.rb
+++ b/spec/helpers/preferences_helper_spec.rb
@@ -61,7 +61,7 @@ describe PreferencesHelper do
(1..5).each do |color_scheme_id|
context "with color_scheme_id == #{color_scheme_id}" do
it 'should return a string' do
- current_user = double(:color_scheme_id => color_scheme_id)
+ current_user = double(color_scheme_id: color_scheme_id)
allow(self).to receive(:current_user).and_return(current_user)
expect(user_color_scheme_class).to be_kind_of(String)
end
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 0f78725e3d9..beb9b4e438e 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -8,4 +8,48 @@ describe ProjectsHelper do
expect(project_status_css_class("finished")).to eq("success")
end
end
+
+ describe "can_change_visibility_level?" do
+ let(:project) { create(:project) }
+
+ let(:fork_project) do
+ fork_project = create(:forked_project_with_submodules)
+ fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
+ fork_project.save
+
+ fork_project
+ end
+
+ let(:user) { create(:user) }
+
+ it "returns false if there are no approipriate permissions" do
+ allow(helper).to receive(:can?) { false }
+
+ expect(helper.can_change_visibility_level?(project, user)).to be_falsey
+ end
+
+ it "returns true if there are permissions and it is not fork" do
+ allow(helper).to receive(:can?) { true }
+
+ expect(helper.can_change_visibility_level?(project, user)).to be_truthy
+ end
+
+ context "forks" do
+ it "returns false if there are permissions and origin project is PRIVATE" do
+ allow(helper).to receive(:can?) { true }
+
+ project.update visibility_level: Gitlab::VisibilityLevel::PRIVATE
+
+ expect(helper.can_change_visibility_level?(fork_project, user)).to be_falsey
+ end
+
+ it "returns true if there are permissions and origin project is INTERNAL" do
+ allow(helper).to receive(:can?) { true }
+
+ project.update visibility_level: Gitlab::VisibilityLevel::INTERNAL
+
+ expect(helper.can_change_visibility_level?(fork_project, user)).to be_truthy
+ end
+ end
+ end
end
diff --git a/spec/helpers/submodule_helper_spec.rb b/spec/helpers/submodule_helper_spec.rb
index e98b75afabc..10121759132 100644
--- a/spec/helpers/submodule_helper_spec.rb
+++ b/spec/helpers/submodule_helper_spec.rb
@@ -14,41 +14,41 @@ describe SubmoduleHelper do
context 'submodule on self' do
before do
- Gitlab.config.gitlab.stub(protocol: 'http') # set this just to be sure
+ allow(Gitlab.config.gitlab).to receive(:protocol).and_return('http') # set this just to be sure
end
it 'should detect ssh on standard port' 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))
+ allow(Gitlab.config.gitlab_shell).to receive(:ssh_port).and_return(22) # set this just to be sure
+ allow(Gitlab.config.gitlab_shell).to receive(:ssh_path_prefix).and_return(Settings.send(:build_gitlab_shell_ssh_path_prefix))
stub_url([ config.user, '@', config.host, ':gitlab-org/gitlab-ce.git' ].join(''))
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))
+ allow(Gitlab.config.gitlab_shell).to receive(:ssh_port).and_return(2222)
+ allow(Gitlab.config.gitlab_shell).to receive(:ssh_path_prefix).and_return(Settings.send(:build_gitlab_shell_ssh_path_prefix))
stub_url([ 'ssh://', config.user, '@', config.host, ':2222/gitlab-org/gitlab-ce.git' ].join(''))
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))
+ allow(Gitlab.config.gitlab).to receive(:port).and_return(80)
+ allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
stub_url([ 'http://', config.host, '/gitlab-org/gitlab-ce.git' ].join(''))
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))
+ allow(Gitlab.config.gitlab).to receive(:port).and_return(3000)
+ allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
stub_url([ 'http://', config.host, ':3000/gitlab-org/gitlab-ce.git' ].join(''))
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
- Gitlab.config.gitlab.stub(port: 80) # set this just to be sure
- Gitlab.config.gitlab.stub(relative_url_root: '/gitlab/root')
- Gitlab.config.gitlab.stub(url: Settings.send(:build_gitlab_url))
+ allow(Gitlab.config.gitlab).to receive(:port).and_return(80) # set this just to be sure
+ allow(Gitlab.config.gitlab).to receive(:relative_url_root).and_return('/gitlab/root')
+ allow(Gitlab.config.gitlab).to receive(:url).and_return(Settings.send(:build_gitlab_url))
stub_url([ 'http://', config.host, '/gitlab/root/gitlab-org/gitlab-ce.git' ].join(''))
expect(submodule_links(submodule_item)).to eq([ namespace_project_path('gitlab-org', 'gitlab-ce'), namespace_project_tree_path('gitlab-org', 'gitlab-ce', 'hash') ])
end
@@ -115,7 +115,7 @@ describe SubmoduleHelper do
end
context 'submodules with relative links' do
- let(:group) { create(:group) }
+ let(:group) { create(:group, name: "Master Project", path: "master-project") }
let(:project) { create(:project, group: group) }
let(:commit_id) { sample_commit[:id] }
@@ -156,6 +156,6 @@ describe SubmoduleHelper do
end
def stub_url(url)
- repo.stub(submodule_url_for: url)
+ allow(repo).to receive(:submodule_url_for).and_return(url)
end
end
diff --git a/spec/helpers/tab_helper_spec.rb b/spec/helpers/tab_helper_spec.rb
index fc0ceecfbe7..b473c0a7416 100644
--- a/spec/helpers/tab_helper_spec.rb
+++ b/spec/helpers/tab_helper_spec.rb
@@ -37,8 +37,8 @@ describe TabHelper do
end
it "passes extra html options to the list element" do
- expect(nav_link(action: :foo, html_options: {class: 'home'})).to match(/<li class="home active">/)
- expect(nav_link(html_options: {class: 'active'})).to match(/<li class="active">/)
+ expect(nav_link(action: :foo, html_options: { class: 'home' })).to match(/<li class="home active">/)
+ expect(nav_link(html_options: { class: 'active' })).to match(/<li class="active">/)
end
end
end
diff --git a/spec/helpers/tree_helper_spec.rb b/spec/helpers/tree_helper_spec.rb
index 2013b3e4c2a..c70dd8076e0 100644
--- a/spec/helpers/tree_helper_spec.rb
+++ b/spec/helpers/tree_helper_spec.rb
@@ -4,10 +4,10 @@ describe TreeHelper do
describe 'flatten_tree' do
let(:project) { create(:project) }
- before {
+ before do
@repository = project.repository
@commit = project.commit("e56497bb")
- }
+ end
context "on a directory containing more than one file/directory" do
let(:tree_item) { double(name: "files", path: "files") }
diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb
index 3840e64981f..c4f7693329c 100644
--- a/spec/helpers/visibility_level_helper_spec.rb
+++ b/spec/helpers/visibility_level_helper_spec.rb
@@ -72,4 +72,43 @@ describe VisibilityLevelHelper do
end
end
end
+
+ describe "skip_level?" do
+ describe "forks" do
+ let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+ let(:fork_project) { create(:forked_project_with_submodules) }
+
+ before do
+ fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
+ fork_project.save
+ end
+
+ it "skips levels" do
+ expect(skip_level?(fork_project, Gitlab::VisibilityLevel::PUBLIC)).to be_truthy
+ expect(skip_level?(fork_project, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey
+ expect(skip_level?(fork_project, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey
+ end
+ end
+
+ describe "non-forked project" do
+ let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+
+ it "skips levels" do
+ expect(skip_level?(project, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey
+ expect(skip_level?(project, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey
+ expect(skip_level?(project, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey
+ end
+ end
+
+ describe "Snippet" do
+ let(:snippet) { create(:snippet, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+
+ it "skips levels" do
+ expect(skip_level?(snippet, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey
+ expect(skip_level?(snippet, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey
+ expect(skip_level?(snippet, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey
+ end
+ end
+
+ end
end
diff --git a/spec/javascripts/behaviors/requires_input_spec.js.coffee b/spec/javascripts/behaviors/requires_input_spec.js.coffee
new file mode 100644
index 00000000000..61a17632173
--- /dev/null
+++ b/spec/javascripts/behaviors/requires_input_spec.js.coffee
@@ -0,0 +1,49 @@
+#= require behaviors/requires_input
+
+describe 'requiresInput', ->
+ fixture.preload('behaviors/requires_input.html')
+
+ beforeEach ->
+ fixture.load('behaviors/requires_input.html')
+
+ it 'disables submit when any field is required', ->
+ $('.js-requires-input').requiresInput()
+
+ expect($('.submit')).toBeDisabled()
+
+ it 'enables submit when no field is required', ->
+ $('*[required=required]').removeAttr('required')
+
+ $('.js-requires-input').requiresInput()
+
+ expect($('.submit')).not.toBeDisabled()
+
+ it 'enables submit when all required fields are pre-filled', ->
+ $('*[required=required]').remove()
+
+ $('.js-requires-input').requiresInput()
+
+ expect($('.submit')).not.toBeDisabled()
+
+ it 'enables submit when all required fields receive input', ->
+ $('.js-requires-input').requiresInput()
+
+ $('#required1').val('input1').change()
+ expect($('.submit')).toBeDisabled()
+
+ $('#optional1').val('input1').change()
+ expect($('.submit')).toBeDisabled()
+
+ $('#required2').val('input2').change()
+ $('#required3').val('input3').change()
+ $('#required4').val('input4').change()
+ $('#required5').val('1').change()
+
+ expect($('.submit')).not.toBeDisabled()
+
+ it 'is called on page:load event', ->
+ spy = spyOn($.fn, 'requiresInput')
+
+ $(document).trigger('page:load')
+
+ expect(spy).toHaveBeenCalled()
diff --git a/spec/javascripts/fixtures/behaviors/requires_input.html.haml b/spec/javascripts/fixtures/behaviors/requires_input.html.haml
new file mode 100644
index 00000000000..c3f905e912e
--- /dev/null
+++ b/spec/javascripts/fixtures/behaviors/requires_input.html.haml
@@ -0,0 +1,18 @@
+%form.js-requires-input
+ %input{type: 'text', id: 'required1', required: 'required'}
+ %input{type: 'text', id: 'required2', required: 'required'}
+ %input{type: 'text', id: 'required3', required: 'required', value: 'Pre-filled'}
+ %input{type: 'text', id: 'optional1'}
+
+ %textarea{id: 'required4', required: 'required'}
+ %textarea{id: 'optional2'}
+
+ %select{id: 'required5', required: 'required'}
+ %option Zero
+ %option{value: '1'} One
+ %select{id: 'optional3', required: 'required'}
+ %option Zero
+ %option{value: '1'} One
+
+ %button.submit{type: 'submit', value: 'Submit'}
+ %input.submit{type: 'submit', value: 'Submit'}
diff --git a/spec/javascripts/fixtures/issues_show.html.haml b/spec/javascripts/fixtures/issues_show.html.haml
index db5abe0cae3..7e8b2a64351 100644
--- a/spec/javascripts/fixtures/issues_show.html.haml
+++ b/spec/javascripts/fixtures/issues_show.html.haml
@@ -10,4 +10,4 @@
%textarea.js-task-list-field
\- [ ] Task List Item
-%form.js-issue-update{action: '/foo'}
+%form.js-issuable-update{action: '/foo'}
diff --git a/spec/javascripts/fixtures/line_highlighter.html.haml b/spec/javascripts/fixtures/line_highlighter.html.haml
new file mode 100644
index 00000000000..da1ebcdb23c
--- /dev/null
+++ b/spec/javascripts/fixtures/line_highlighter.html.haml
@@ -0,0 +1,11 @@
+#tree-content-holder
+ .file-content
+ .line-numbers
+ - 1.upto(25) do |i|
+ %a{href: "#L#{i}", id: "L#{i}", 'data-line-number' => i}
+ %i.fa.fa-link
+ = i
+ %pre.code.highlight
+ %code
+ - 1.upto(25) do |i|
+ %span.line{id: "LC#{i}"}= "Line #{i}"
diff --git a/spec/javascripts/fixtures/merge_request_tabs.html.haml b/spec/javascripts/fixtures/merge_request_tabs.html.haml
new file mode 100644
index 00000000000..7624a713948
--- /dev/null
+++ b/spec/javascripts/fixtures/merge_request_tabs.html.haml
@@ -0,0 +1,22 @@
+%ul.nav.nav-tabs.merge-request-tabs
+ %li.notes-tab
+ %a{href: '/foo/bar/merge_requests/1', data: {target: '#notes', action: 'notes', toggle: 'tab'}}
+ Discussion
+ %li.commits-tab
+ %a{href: '/foo/bar/merge_requests/1/commits', data: {target: '#commits', action: 'commits', toggle: 'tab'}}
+ Commits
+ %li.diffs-tab
+ %a{href: '/foo/bar/merge_requests/1/diffs', data: {target: '#diffs', action: 'diffs', toggle: 'tab'}}
+ Diffs
+
+.tab-content
+ #notes.notes.tab-pane
+ Notes Content
+ #commits.commits.tab-pane
+ Commits Content
+ #diffs.diffs.tab-pane
+ Diffs Content
+
+.mr-loading-status
+ .loading
+ Loading Animation
diff --git a/spec/javascripts/fixtures/merge_requests_show.html.haml b/spec/javascripts/fixtures/merge_requests_show.html.haml
index c4329b8f94a..f0c622935f8 100644
--- a/spec/javascripts/fixtures/merge_requests_show.html.haml
+++ b/spec/javascripts/fixtures/merge_requests_show.html.haml
@@ -10,4 +10,4 @@
%textarea.js-task-list-field
\- [ ] Task List Item
-%form.js-merge-request-update{action: '/foo'}
+%form.js-issuable-update{action: '/foo'}
diff --git a/spec/javascripts/line_highlighter_spec.js.coffee b/spec/javascripts/line_highlighter_spec.js.coffee
new file mode 100644
index 00000000000..57453c716a5
--- /dev/null
+++ b/spec/javascripts/line_highlighter_spec.js.coffee
@@ -0,0 +1,158 @@
+#= require line_highlighter
+
+describe 'LineHighlighter', ->
+ fixture.preload('line_highlighter.html')
+
+ clickLine = (number, eventData = {}) ->
+ if $.isEmptyObject(eventData)
+ $("#L#{number}").mousedown().click()
+ else
+ e = $.Event 'mousedown', eventData
+ $("#L#{number}").trigger(e).click()
+
+ beforeEach ->
+ fixture.load('line_highlighter.html')
+ @class = new LineHighlighter()
+ @css = @class.highlightClass
+ @spies = {
+ __setLocationHash__: spyOn(@class, '__setLocationHash__').and.callFake ->
+ }
+
+ describe 'behavior', ->
+ it 'highlights one line given in the URL hash', ->
+ new LineHighlighter('#L13')
+ expect($('#LC13')).toHaveClass(@css)
+
+ it 'highlights a range of lines given in the URL hash', ->
+ new LineHighlighter('#L5-25')
+ expect($(".#{@css}").length).toBe(21)
+ expect($("#LC#{line}")).toHaveClass(@css) for line in [5..25]
+
+ it 'scrolls to the first highlighted line on initial load', ->
+ spy = spyOn($, 'scrollTo')
+ new LineHighlighter('#L5-25')
+ expect(spy).toHaveBeenCalledWith('#L5', jasmine.anything())
+
+ it 'discards click events', ->
+ spy = spyOnEvent('a[data-line-number]', 'click')
+ clickLine(13)
+ expect(spy).toHaveBeenPrevented()
+
+ it 'handles garbage input from the hash', ->
+ func = -> new LineHighlighter('#tree-content-holder')
+ expect(func).not.toThrow()
+
+ describe '#clickHandler', ->
+ it 'discards the mousedown event', ->
+ spy = spyOnEvent('a[data-line-number]', 'mousedown')
+ clickLine(13)
+ expect(spy).toHaveBeenPrevented()
+
+ it 'handles clicking on a child icon element', ->
+ spy = spyOn(@class, 'setHash').and.callThrough()
+
+ $('#L13 i').mousedown().click()
+
+ expect(spy).toHaveBeenCalledWith(13)
+ expect($('#LC13')).toHaveClass(@css)
+
+ describe 'without shiftKey', ->
+ it 'highlights one line when clicked', ->
+ clickLine(13)
+ expect($('#LC13')).toHaveClass(@css)
+
+ it 'unhighlights previously highlighted lines', ->
+ clickLine(13)
+ clickLine(20)
+
+ expect($('#LC13')).not.toHaveClass(@css)
+ expect($('#LC20')).toHaveClass(@css)
+
+ it 'sets the hash', ->
+ spy = spyOn(@class, 'setHash').and.callThrough()
+ clickLine(13)
+ expect(spy).toHaveBeenCalledWith(13)
+
+ describe 'with shiftKey', ->
+ it 'sets the hash', ->
+ spy = spyOn(@class, 'setHash').and.callThrough()
+ clickLine(13)
+ clickLine(20, shiftKey: true)
+ expect(spy).toHaveBeenCalledWith(13)
+ expect(spy).toHaveBeenCalledWith(13, 20)
+
+ describe 'without existing highlight', ->
+ it 'highlights the clicked line', ->
+ clickLine(13, shiftKey: true)
+ expect($('#LC13')).toHaveClass(@css)
+ expect($(".#{@css}").length).toBe(1)
+
+ it 'sets the hash', ->
+ spy = spyOn(@class, 'setHash')
+ clickLine(13, shiftKey: true)
+ expect(spy).toHaveBeenCalledWith(13)
+
+ describe 'with existing single-line highlight', ->
+ it 'uses existing line as last line when target is lesser', ->
+ clickLine(20)
+ clickLine(15, shiftKey: true)
+ expect($(".#{@css}").length).toBe(6)
+ expect($("#LC#{line}")).toHaveClass(@css) for line in [15..20]
+
+ it 'uses existing line as first line when target is greater', ->
+ clickLine(5)
+ clickLine(10, shiftKey: true)
+ expect($(".#{@css}").length).toBe(6)
+ expect($("#LC#{line}")).toHaveClass(@css) for line in [5..10]
+
+ describe 'with existing multi-line highlight', ->
+ beforeEach ->
+ clickLine(10, shiftKey: true)
+ clickLine(13, shiftKey: true)
+
+ it 'uses target as first line when it is less than existing first line', ->
+ clickLine(5, shiftKey: true)
+ expect($(".#{@css}").length).toBe(6)
+ expect($("#LC#{line}")).toHaveClass(@css) for line in [5..10]
+
+ it 'uses target as last line when it is greater than existing first line', ->
+ clickLine(15, shiftKey: true)
+ expect($(".#{@css}").length).toBe(6)
+ expect($("#LC#{line}")).toHaveClass(@css) for line in [10..15]
+
+ describe '#hashToRange', ->
+ beforeEach ->
+ @subject = @class.hashToRange
+
+ it 'extracts a single line number from the hash', ->
+ expect(@subject('#L5')).toEqual([5, null])
+
+ it 'extracts a range of line numbers from the hash', ->
+ expect(@subject('#L5-15')).toEqual([5, 15])
+
+ it 'returns [null, null] when the hash is not a line number', ->
+ expect(@subject('#foo')).toEqual([null, null])
+
+ describe '#highlightLine', ->
+ beforeEach ->
+ @subject = @class.highlightLine
+
+ it 'highlights the specified line', ->
+ @subject(13)
+ expect($('#LC13')).toHaveClass(@css)
+
+ it 'accepts a String-based number', ->
+ @subject('13')
+ expect($('#LC13')).toHaveClass(@css)
+
+ describe '#setHash', ->
+ beforeEach ->
+ @subject = @class.setHash
+
+ it 'sets the location hash for a single line', ->
+ @subject(5)
+ expect(@spies.__setLocationHash__).toHaveBeenCalledWith('#L5')
+
+ it 'sets the location hash for a range', ->
+ @subject(5, 15)
+ expect(@spies.__setLocationHash__).toHaveBeenCalledWith('#L5-15')
diff --git a/spec/javascripts/merge_request_spec.js.coffee b/spec/javascripts/merge_request_spec.js.coffee
index a4735af0343..22ebc7039d1 100644
--- a/spec/javascripts/merge_request_spec.js.coffee
+++ b/spec/javascripts/merge_request_spec.js.coffee
@@ -1,7 +1,5 @@
#= require merge_request
-window.disableButtonIfEmptyField = -> null
-
describe 'MergeRequest', ->
describe 'task lists', ->
fixture.preload('merge_requests_show.html')
diff --git a/spec/javascripts/merge_request_tabs_spec.js.coffee b/spec/javascripts/merge_request_tabs_spec.js.coffee
new file mode 100644
index 00000000000..6cc96fb68a0
--- /dev/null
+++ b/spec/javascripts/merge_request_tabs_spec.js.coffee
@@ -0,0 +1,82 @@
+#= require merge_request_tabs
+
+describe 'MergeRequestTabs', ->
+ stubLocation = (stubs) ->
+ defaults = {pathname: '', search: '', hash: ''}
+ $.extend(defaults, stubs)
+
+ fixture.preload('merge_request_tabs.html')
+
+ beforeEach ->
+ @class = new MergeRequestTabs()
+ @spies = {
+ ajax: spyOn($, 'ajax').and.callFake ->
+ history: spyOn(history, 'replaceState').and.callFake ->
+ }
+
+ describe '#activateTab', ->
+ beforeEach ->
+ fixture.load('merge_request_tabs.html')
+ @subject = @class.activateTab
+
+ it 'shows the first tab when action is show', ->
+ @subject('show')
+ expect($('#notes')).toHaveClass('active')
+
+ it 'shows the notes tab when action is notes', ->
+ @subject('notes')
+ expect($('#notes')).toHaveClass('active')
+
+ it 'shows the commits tab when action is commits', ->
+ @subject('commits')
+ expect($('#commits')).toHaveClass('active')
+
+ it 'shows the diffs tab when action is diffs', ->
+ @subject('diffs')
+ expect($('#diffs')).toHaveClass('active')
+
+ describe '#setCurrentAction', ->
+ beforeEach ->
+ @subject = @class.setCurrentAction
+
+ it 'changes from commits', ->
+ @class._location = stubLocation(pathname: '/foo/bar/merge_requests/1/commits')
+
+ expect(@subject('notes')).toBe('/foo/bar/merge_requests/1')
+ expect(@subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs')
+
+ it 'changes from diffs', ->
+ @class._location = stubLocation(pathname: '/foo/bar/merge_requests/1/diffs')
+
+ expect(@subject('notes')).toBe('/foo/bar/merge_requests/1')
+ expect(@subject('commits')).toBe('/foo/bar/merge_requests/1/commits')
+
+ it 'changes from notes', ->
+ @class._location = stubLocation(pathname: '/foo/bar/merge_requests/1')
+
+ expect(@subject('diffs')).toBe('/foo/bar/merge_requests/1/diffs')
+ expect(@subject('commits')).toBe('/foo/bar/merge_requests/1/commits')
+
+ it 'includes search parameters and hash string', ->
+ @class._location = stubLocation({
+ pathname: '/foo/bar/merge_requests/1/diffs'
+ search: '?view=parallel'
+ hash: '#L15-35'
+ })
+
+ expect(@subject('show')).toBe('/foo/bar/merge_requests/1?view=parallel#L15-35')
+
+ it 'replaces the current history state', ->
+ @class._location = stubLocation(pathname: '/foo/bar/merge_requests/1')
+ new_state = @subject('commits')
+
+ expect(@spies.history).toHaveBeenCalledWith(
+ {turbolinks: true, url: new_state},
+ document.title,
+ new_state
+ )
+
+ it 'treats "show" like "notes"', ->
+ @class._location = stubLocation(pathname: '/foo/bar/merge_requests/1/commits')
+
+ expect(@subject('show')).toBe('/foo/bar/merge_requests/1')
diff --git a/spec/lib/disable_email_interceptor_spec.rb b/spec/lib/disable_email_interceptor_spec.rb
index 06d5450688b..a9624e9a2b7 100644
--- a/spec/lib/disable_email_interceptor_spec.rb
+++ b/spec/lib/disable_email_interceptor_spec.rb
@@ -7,9 +7,7 @@ describe DisableEmailInterceptor do
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)
+ expect { deliver_mail }.not_to change(ActionMailer::Base.deliveries, :count)
end
after do
diff --git a/spec/lib/extracts_path_spec.rb b/spec/lib/extracts_path_spec.rb
index 05bcebaa3a2..4439775f612 100644
--- a/spec/lib/extracts_path_spec.rb
+++ b/spec/lib/extracts_path_spec.rb
@@ -9,13 +9,17 @@ describe ExtractsPath do
before do
@project = project
- project.stub(repository: double(ref_names: ['master', 'foo/bar/baz', 'v1.0.0', 'v2.0.0']))
- project.stub(path_with_namespace: 'gitlab/gitlab-ci')
+
+ repo = double(ref_names: ['master', 'foo/bar/baz', 'v1.0.0', 'v2.0.0',
+ 'release/app', 'release/app/v1.0.0'])
+ allow(project).to receive(:repository).and_return(repo)
+ allow(project).to receive(:path_with_namespace).
+ and_return('gitlab/gitlab-ci')
end
describe '#assign_ref' do
let(:ref) { sample_commit[:id] }
- let(:params) { {path: sample_commit[:line_code_path], ref: ref} }
+ let(:params) { { path: sample_commit[:line_code_path], ref: ref } }
before do
@project = create(:project)
@@ -51,11 +55,17 @@ describe ExtractsPath do
it "falls back to a primitive split for an invalid ref" do
expect(extract_ref('stable')).to eq(['stable', ''])
end
+
+ it "extracts the longest matching ref" do
+ expect(extract_ref('release/app/v1.0.0/README.md')).to eq(
+ ['release/app/v1.0.0', 'README.md'])
+ end
end
context "with a path" do
it "extracts a valid branch" do
- expect(extract_ref('foo/bar/baz/CHANGELOG')).to eq(['foo/bar/baz', 'CHANGELOG'])
+ expect(extract_ref('foo/bar/baz/CHANGELOG')).to eq(
+ ['foo/bar/baz', 'CHANGELOG'])
end
it "extracts a valid tag" do
diff --git a/spec/lib/file_size_validator_spec.rb b/spec/lib/file_size_validator_spec.rb
index 5c89c854714..12ccc051c74 100644
--- a/spec/lib/file_size_validator_spec.rb
+++ b/spec/lib/file_size_validator_spec.rb
@@ -9,9 +9,9 @@ describe 'Gitlab::FileSizeValidatorSpec' 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)
+ 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
@@ -22,8 +22,13 @@ describe 'Gitlab::FileSizeValidatorSpec' do
end
describe 'options uses a symbol' do
- let(:options) { { maximum: :test,
- attributes: { attachment: attachment } } }
+ let(:options) do
+ {
+ maximum: :test,
+ attributes: { attachment: attachment }
+ }
+ end
+
before do
allow(note).to receive(:test) { 10 }
end
diff --git a/spec/lib/gitlab/asciidoc_spec.rb b/spec/lib/gitlab/asciidoc_spec.rb
index 23f83339ec5..03e36fd3552 100644
--- a/spec/lib/gitlab/asciidoc_spec.rb
+++ b/spec/lib/gitlab/asciidoc_spec.rb
@@ -11,8 +11,11 @@ module Gitlab
context "without project" do
it "should convert the input using Asciidoctor and default options" do
- expected_asciidoc_opts = { safe: :secure, backend: :html5,
- attributes: described_class::DEFAULT_ADOC_ATTRS }
+ expected_asciidoc_opts = {
+ safe: :secure,
+ backend: :html5,
+ attributes: described_class::DEFAULT_ADOC_ATTRS
+ }
expect(Asciidoctor).to receive(:convert)
.with(input, expected_asciidoc_opts).and_return(html)
@@ -22,11 +25,14 @@ module Gitlab
context "with asciidoc_opts" do
- let(:asciidoc_opts) { {safe: :safe, attributes: ['foo']} }
+ let(:asciidoc_opts) { { safe: :safe, attributes: ['foo'] } }
it "should merge the options with default ones" do
- expected_asciidoc_opts = { safe: :safe, backend: :html5,
- attributes: described_class::DEFAULT_ADOC_ATTRS + ['foo'] }
+ expected_asciidoc_opts = {
+ safe: :safe,
+ backend: :html5,
+ attributes: described_class::DEFAULT_ADOC_ATTRS + ['foo']
+ }
expect(Asciidoctor).to receive(:convert)
.with(input, expected_asciidoc_opts).and_return(html)
@@ -38,7 +44,7 @@ module Gitlab
context "with project in context" do
- let(:context) { {project: create(:project)} }
+ let(:context) { { project: create(:project) } }
it "should filter converted input via HTML pipeline and return result" do
filtered_html = '<b>ASCII</b>'
diff --git a/spec/lib/gitlab/auth_spec.rb b/spec/lib/gitlab/auth_spec.rb
index 95fc7e16a11..72806bebe1f 100644
--- a/spec/lib/gitlab/auth_spec.rb
+++ b/spec/lib/gitlab/auth_spec.rb
@@ -27,16 +27,18 @@ describe Gitlab::Auth do
it "should not find user with invalid password" do
password = 'wrong'
- expect( gl_auth.find(username, password) ).to_not eql user
+ expect( gl_auth.find(username, password) ).not_to eql user
end
it "should not find user with invalid login" do
user = 'wrong'
- expect( gl_auth.find(username, password) ).to_not eql user
+ expect( gl_auth.find(username, password) ).not_to eql user
end
context "with ldap enabled" do
- before { Gitlab::LDAP::Config.stub(enabled?: true) }
+ before do
+ allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true)
+ end
it "tries to autheticate with db before ldap" do
expect(Gitlab::LDAP::Authentication).not_to receive(:login)
diff --git a/spec/lib/gitlab/backend/grack_auth_spec.rb b/spec/lib/gitlab/backend/grack_auth_spec.rb
index 42c9946d2a9..d9676445908 100644
--- a/spec/lib/gitlab/backend/grack_auth_spec.rb
+++ b/spec/lib/gitlab/backend/grack_auth_spec.rb
@@ -6,13 +6,13 @@ describe Grack::Auth do
let(:app) { lambda { |env| [200, {}, "Success!"] } }
let!(:auth) { Grack::Auth.new(app) }
- let(:env) {
+ let(:env) do
{
- "rack.input" => "",
- "REQUEST_METHOD" => "GET",
- "QUERY_STRING" => "service=git-upload-pack"
+ 'rack.input' => '',
+ 'REQUEST_METHOD' => 'GET',
+ 'QUERY_STRING' => 'service=git-upload-pack'
}
- }
+ end
let(:status) { auth.call(env).first }
describe "#call" do
@@ -121,7 +121,7 @@ describe Grack::Auth do
context "when the user isn't blocked" do
before do
- expect(Rack::Attack::Allow2Ban).to receive(:reset)
+ expect(Rack::Attack::Allow2Ban).to receive(:reset)
end
it "responds with status 200" do
diff --git a/spec/lib/gitlab/backend/shell_spec.rb b/spec/lib/gitlab/backend/shell_spec.rb
index 27279465c1a..b6d04330599 100644
--- a/spec/lib/gitlab/backend/shell_spec.rb
+++ b/spec/lib/gitlab/backend/shell_spec.rb
@@ -5,7 +5,7 @@ describe Gitlab::Shell do
let(:gitlab_shell) { Gitlab::Shell.new }
before do
- Project.stub(find: project)
+ allow(Project).to receive(:find).and_return(project)
end
it { is_expected.to respond_to :add_key }
diff --git a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
index 0ec6a43f681..f8958c9bab8 100644
--- a/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/bitbucket_import/project_creator_spec.rb
@@ -2,24 +2,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(:repo) do
+ {
+ name: 'Vim',
+ slug: 'vim',
+ is_private: true,
+ owner: "asd"
+ }.with_indifferent_access
+ end
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
diff --git a/spec/lib/gitlab/github_import/project_creator_spec.rb b/spec/lib/gitlab/github_import/project_creator_spec.rb
index 3bf52cb685e..4fe7bd3b77d 100644
--- a/spec/lib/gitlab/github_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/github_import/project_creator_spec.rb
@@ -2,14 +2,16 @@ 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(:repo) do
+ 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")
+ )
+ end
let(:namespace){ create(:group, owner: user) }
before do
@@ -18,10 +20,10 @@ describe Gitlab::GithubImport::ProjectCreator do
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
diff --git a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb
index 3cefe4ea8e2..938d08396fd 100644
--- a/spec/lib/gitlab/gitlab_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/gitlab_import/project_creator_spec.rb
@@ -2,14 +2,16 @@ 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(:repo) do
+ {
+ 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
+ end
let(:namespace){ create(:group, owner: user) }
before do
@@ -18,10 +20,10 @@ describe Gitlab::GitlabImport::ProjectCreator do
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
diff --git a/spec/lib/gitlab/google_code_import/client_spec.rb b/spec/lib/gitlab/google_code_import/client_spec.rb
index a66b811e0fd..6aa4428f367 100644
--- a/spec/lib/gitlab/google_code_import/client_spec.rb
+++ b/spec/lib/gitlab/google_code_import/client_spec.rb
@@ -15,7 +15,7 @@ describe Gitlab::GoogleCodeImport::Client do
let(:raw_data) { "No clue" }
it "returns true" do
- expect(subject).to_not be_valid
+ expect(subject).not_to be_valid
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
index 67378328336..c53ddeb87b5 100644
--- a/spec/lib/gitlab/google_code_import/importer_spec.rb
+++ b/spec/lib/gitlab/google_code_import/importer_spec.rb
@@ -4,16 +4,15 @@ 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) {
+ let(:import_data) do
{
- "repo" => client.repo("tint2").raw_data,
- "user_map" => {
- "thilo..." => "@#{mapped_user.username}"
- }
- }
- }
+ 'repo' => client.repo('tint2').raw_data,
+ 'user_map' => { 'thilo...' => "@#{mapped_user.username}" }
+ }
+ end
let(:project) { create(:project) }
- subject { described_class.new(project) }
+
+ subject { described_class.new(project) }
before do
project.create_import_data(data: import_data)
@@ -25,7 +24,7 @@ describe Gitlab::GoogleCodeImport::Importer 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
+ expect(project.labels.find_by(name: "Status: #{status}")).not_to be_nil
end
end
@@ -39,7 +38,7 @@ describe Gitlab::GoogleCodeImport::Importer do
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
+ expect(project.labels.find_by(name: label)).not_to be_nil
end
end
@@ -47,7 +46,7 @@ describe Gitlab::GoogleCodeImport::Importer do
subject.execute
issue = project.issues.first
- expect(issue).to_not be_nil
+ expect(issue).not_to be_nil
expect(issue.iid).to eq(169)
expect(issue.author).to eq(project.creator)
expect(issue.assignee).to eq(mapped_user)
@@ -72,7 +71,7 @@ describe Gitlab::GoogleCodeImport::Importer do
subject.execute
note = project.issues.first.notes.first
- expect(note).to_not be_nil
+ expect(note).not_to 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")
diff --git a/spec/lib/gitlab/google_code_import/project_creator_spec.rb b/spec/lib/gitlab/google_code_import/project_creator_spec.rb
index 7a224538b8b..35549b48687 100644
--- a/spec/lib/gitlab/google_code_import/project_creator_spec.rb
+++ b/spec/lib/gitlab/google_code_import/project_creator_spec.rb
@@ -2,13 +2,13 @@ require 'spec_helper'
describe Gitlab::GoogleCodeImport::ProjectCreator do
let(:user) { create(:user) }
- let(:repo) {
+ let(:repo) do
Gitlab::GoogleCodeImport::Repository.new(
- "name" => 'vim',
- "summary" => 'VI Improved',
- "repositoryUrls" => [ "https://vim.googlecode.com/git/" ]
+ "name" => 'vim',
+ "summary" => 'VI Improved',
+ "repositoryUrls" => ["https://vim.googlecode.com/git/"]
)
- }
+ end
let(:namespace){ create(:group, owner: user) }
before do
@@ -20,7 +20,7 @@ describe Gitlab::GoogleCodeImport::ProjectCreator do
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
diff --git a/spec/lib/gitlab/ldap/access_spec.rb b/spec/lib/gitlab/ldap/access_spec.rb
index 038ac7e0d75..c38f212b405 100644
--- a/spec/lib/gitlab/ldap/access_spec.rb
+++ b/spec/lib/gitlab/ldap/access_spec.rb
@@ -8,16 +8,24 @@ describe Gitlab::LDAP::Access do
subject { access.allowed? }
context 'when the user cannot be found' do
- before { Gitlab::LDAP::Person.stub(find_by_dn: nil) }
+ before do
+ allow(Gitlab::LDAP::Person).to receive(:find_by_dn).and_return(nil)
+ end
it { is_expected.to be_falsey }
end
context 'when the user is found' do
- before { Gitlab::LDAP::Person.stub(find_by_dn: :ldap_user) }
+ before do
+ allow(Gitlab::LDAP::Person).
+ to receive(:find_by_dn).and_return(:ldap_user)
+ end
context 'and the user is disabled via active directory' do
- before { Gitlab::LDAP::Person.stub(disabled_via_active_directory?: true) }
+ before do
+ allow(Gitlab::LDAP::Person).
+ to receive(:disabled_via_active_directory?).and_return(true)
+ end
it { is_expected.to be_falsey }
@@ -30,8 +38,9 @@ describe Gitlab::LDAP::Access do
context 'and has no disabled flag in active diretory' do
before do
user.block
-
- Gitlab::LDAP::Person.stub(disabled_via_active_directory?: false)
+
+ allow(Gitlab::LDAP::Person).
+ to receive(:disabled_via_active_directory?).and_return(false)
end
it { is_expected.to be_truthy }
@@ -39,7 +48,8 @@ describe Gitlab::LDAP::Access do
context 'when auto-created users are blocked' do
before do
- Gitlab::LDAP::Config.any_instance.stub(block_auto_created_users: true)
+ allow_any_instance_of(Gitlab::LDAP::Config).
+ to receive(:block_auto_created_users).and_return(true)
end
it "does not unblock user in GitLab" do
@@ -51,7 +61,8 @@ describe Gitlab::LDAP::Access do
context "when auto-created users are not blocked" do
before do
- Gitlab::LDAP::Config.any_instance.stub(block_auto_created_users: false)
+ allow_any_instance_of(Gitlab::LDAP::Config).
+ to receive(:block_auto_created_users).and_return(false)
end
it "should unblock user in GitLab" do
@@ -63,8 +74,9 @@ describe Gitlab::LDAP::Access do
context 'without ActiveDirectory enabled' do
before do
- Gitlab::LDAP::Config.stub(enabled?: true)
- Gitlab::LDAP::Config.any_instance.stub(active_directory: false)
+ allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true)
+ allow_any_instance_of(Gitlab::LDAP::Config).
+ to receive(:active_directory).and_return(false)
end
it { is_expected.to be_truthy }
diff --git a/spec/lib/gitlab/ldap/adapter_spec.rb b/spec/lib/gitlab/ldap/adapter_spec.rb
index b609e4b38f2..38076602df9 100644
--- a/spec/lib/gitlab/ldap/adapter_spec.rb
+++ b/spec/lib/gitlab/ldap/adapter_spec.rb
@@ -3,27 +3,32 @@ require 'spec_helper'
describe Gitlab::LDAP::Adapter do
let(:adapter) { Gitlab::LDAP::Adapter.new 'ldapmain' }
- describe :dn_matches_filter? do
+ describe '#dn_matches_filter?' do
let(:ldap) { double(:ldap) }
subject { adapter.dn_matches_filter?(:dn, :filter) }
- before { adapter.stub(ldap: ldap) }
+ before { allow(adapter).to receive(:ldap).and_return(ldap) }
context "when the search is successful" do
context "and the result is non-empty" do
- before { ldap.stub(search: [:foo]) }
+ before { allow(ldap).to receive(:search).and_return([:foo]) }
it { is_expected.to be_truthy }
end
context "and the result is empty" do
- before { ldap.stub(search: []) }
+ before { allow(ldap).to receive(:search).and_return([]) }
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')) }
+ before do
+ allow(ldap).to receive_messages(
+ search: nil,
+ get_operation_result: double(code: 1, message: 'some error')
+ )
+ end
it { is_expected.to be_falsey }
end
diff --git a/spec/lib/gitlab/ldap/authentication_spec.rb b/spec/lib/gitlab/ldap/authentication_spec.rb
index 8afc2b21f46..6e3de914a45 100644
--- a/spec/lib/gitlab/ldap/authentication_spec.rb
+++ b/spec/lib/gitlab/ldap/authentication_spec.rb
@@ -1,53 +1,58 @@
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(: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 }
+ describe 'login' do
before do
- Gitlab::LDAP::Config.stub(enabled?: true)
+ allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true)
end
it "finds the user if authentication is successful" do
- user
+ expect(user).not_to be_nil
+
# 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
+ adapter = double('adapter', dn: dn).as_null_object
+ allow_any_instance_of(described_class).
+ to receive(:adapter).and_return(adapter)
+
+ expect(described_class.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
+ adapter = double('adapter', dn: dn).as_null_object
+ allow_any_instance_of(described_class).
+ to receive(:adapter).and_return(adapter)
+
+ expect(described_class.login(login, password)).to be_falsey
end
it "is false if authentication fails" do
- user
+ expect(user).not_to be_nil
+
# 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
+ adapter = double('adapter', bind_as: nil).as_null_object
+ allow_any_instance_of(described_class).
+ to receive(:adapter).and_return(adapter)
+
+ expect(described_class.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
+ allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(false)
+ expect(described_class.login(login, password)).to be_falsey
end
it "fails if no login is supplied" do
- expect(klass.login('', password)).to be_falsey
+ expect(described_class.login('', password)).to be_falsey
end
it "fails if no password is supplied" do
- expect(klass.login(login, '')).to be_falsey
+ expect(described_class.login(login, '')).to be_falsey
end
end
-end \ No newline at end of file
+end
diff --git a/spec/lib/gitlab/ldap/config_spec.rb b/spec/lib/gitlab/ldap/config_spec.rb
index 00e9076c787..3548d647c84 100644
--- a/spec/lib/gitlab/ldap/config_spec.rb
+++ b/spec/lib/gitlab/ldap/config_spec.rb
@@ -14,7 +14,7 @@ describe Gitlab::LDAP::Config do
end
it "raises an error if a unknow provider is used" do
- expect{ Gitlab::LDAP::Config.new 'unknown' }.to raise_error
+ expect{ Gitlab::LDAP::Config.new 'unknown' }.to raise_error(RuntimeError)
end
end
end
diff --git a/spec/lib/gitlab/ldap/user_spec.rb b/spec/lib/gitlab/ldap/user_spec.rb
index 42015c28c81..7cfca96f4e0 100644
--- a/spec/lib/gitlab/ldap/user_spec.rb
+++ b/spec/lib/gitlab/ldap/user_spec.rb
@@ -16,31 +16,31 @@ describe Gitlab::LDAP::User do
describe :changed? do
it "marks existing ldap user as changed" do
- existing_user = create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain')
+ 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')
+ 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')
+ 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')
+ create(:omniauth_user, extern_uid: 'my-uid', provider: 'ldapmain')
- expect{ ldap_user.save }.to_not change{ User.count }
+ expect{ ldap_user.save }.not_to 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 }
+ expect{ ldap_user.save }.not_to change{ User.count }
existing_user.reload
expect(existing_user.ldap_identity.extern_uid).to eql 'my-uid'
@@ -52,11 +52,15 @@ describe Gitlab::LDAP::User do
end
end
-
describe 'blocking' do
+ def configure_block(value)
+ allow_any_instance_of(Gitlab::LDAP::Config).
+ to receive(:block_auto_created_users).and_return(value)
+ end
+
context 'signup' do
context 'dont block on create' do
- before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: false }
+ before { configure_block(false) }
it do
ldap_user.save
@@ -66,7 +70,7 @@ describe Gitlab::LDAP::User do
end
context 'block on create' do
- before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: true }
+ before { configure_block(true) }
it do
ldap_user.save
@@ -83,7 +87,7 @@ describe Gitlab::LDAP::User do
end
context 'dont block on create' do
- before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: false }
+ before { configure_block(false) }
it do
ldap_user.save
@@ -93,7 +97,7 @@ describe Gitlab::LDAP::User do
end
context 'block on create' do
- before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: true }
+ before { configure_block(true) }
it do
ldap_user.save
diff --git a/spec/lib/gitlab/markdown/autolink_filter_spec.rb b/spec/lib/gitlab/markdown/autolink_filter_spec.rb
index a14cb2da089..982be0782c9 100644
--- a/spec/lib/gitlab/markdown/autolink_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/autolink_filter_spec.rb
@@ -48,7 +48,7 @@ module Gitlab::Markdown
end
it 'accepts link_attr options' do
- doc = filter("See #{link}", link_attr: {class: 'custom'})
+ doc = filter("See #{link}", link_attr: { class: 'custom' })
expect(doc.at_css('a')['class']).to eq 'custom'
end
@@ -89,7 +89,7 @@ module Gitlab::Markdown
end
it 'accepts link_attr options' do
- doc = filter("See #{link}", link_attr: {class: 'custom'})
+ doc = filter("See #{link}", link_attr: { class: 'custom' })
expect(doc.at_css('a')['class']).to eq 'custom'
end
diff --git a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
index cf3337b1ba1..e9f8ed277a5 100644
--- a/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
+++ b/spec/lib/gitlab/markdown/label_reference_filter_spec.rb
@@ -127,7 +127,7 @@ module Gitlab::Markdown
it 'gracefully handles non-references matching the pattern' do
exp = act = '(format nil "~0f" 3.0) ; 3.0'
expect(filter(act).to_html).to eq exp
- end
+ end
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
index 165cde4f160..4c0a4a49d2a 100644
--- a/spec/lib/gitlab/o_auth/auth_hash_spec.rb
+++ b/spec/lib/gitlab/o_auth/auth_hash_spec.rb
@@ -11,9 +11,9 @@ describe Gitlab::OAuth::AuthHash do
)
end
- let(:uid_raw) {
+ let(:uid_raw) do
"CN=Onur K\xC3\xBC\xC3\xA7\xC3\xBCk,OU=Test,DC=example,DC=net"
- }
+ end
let(:email_raw) { "onur.k\xC3\xBC\xC3\xA7\xC3\xBCk@example.net" }
let(:nickname_raw) { "ok\xC3\xBC\xC3\xA7\xC3\xBCk" }
let(:first_name_raw) { 'Onur' }
@@ -34,16 +34,16 @@ describe Gitlab::OAuth::AuthHash do
let(:nickname_utf8) { nickname_ascii.force_encoding(Encoding::UTF_8) }
let(:name_utf8) { name_ascii.force_encoding(Encoding::UTF_8) }
- let(:info_hash) {
+ let(:info_hash) do
{
- email: email_ascii,
+ email: email_ascii,
first_name: first_name_ascii,
- last_name: last_name_ascii,
- name: name_ascii,
- nickname: nickname_ascii,
- uid: uid_ascii
+ last_name: last_name_ascii,
+ name: name_ascii,
+ nickname: nickname_ascii,
+ uid: uid_ascii
}
- }
+ end
context 'defaults' do
it { expect(auth_hash.provider).to eql provider_utf8 }
@@ -51,7 +51,7 @@ describe Gitlab::OAuth::AuthHash do
it { expect(auth_hash.email).to eql email_utf8 }
it { expect(auth_hash.username).to eql nickname_utf8 }
it { expect(auth_hash.name).to eql name_utf8 }
- it { expect(auth_hash.password).to_not be_empty }
+ it { expect(auth_hash.password).not_to be_empty }
end
context 'email not provided' do
diff --git a/spec/lib/gitlab/o_auth/user_spec.rb b/spec/lib/gitlab/o_auth/user_spec.rb
index d383ea2d051..c6cca98a037 100644
--- a/spec/lib/gitlab/o_auth/user_spec.rb
+++ b/spec/lib/gitlab/o_auth/user_spec.rb
@@ -19,23 +19,34 @@ describe Gitlab::OAuth::User 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
+ # FIXME (rspeicher): It's unlikely that this test is actually doing anything
+ # `auth` is never used and removing it entirely doesn't break the test, so
+ # what's it doing?
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')
+ allow(auth_hash).to receive(:uid).and_return('non-existing')
expect( oauth_user.persisted? ).to be_falsey
end
end
describe :save do
+ def stub_omniauth_config(messages)
+ allow(Gitlab.config.omniauth).to receive_messages(messages)
+ end
+
+ def stub_ldap_config(messages)
+ allow(Gitlab::LDAP::Config).to receive_messages(messages)
+ end
+
let(:provider) { 'twitter' }
describe 'signup' do
shared_examples "to verify compliance with allow_single_sign_on" do
context "with allow_single_sign_on enabled" do
- before { Gitlab.config.omniauth.stub allow_single_sign_on: true }
+ before { stub_omniauth_config(allow_single_sign_on: true) }
it "creates a user from Omniauth" do
oauth_user.save
@@ -48,7 +59,7 @@ describe Gitlab::OAuth::User do
end
context "with allow_single_sign_on disabled (Default)" do
- before { Gitlab.config.omniauth.stub allow_single_sign_on: false }
+ before { stub_omniauth_config(allow_single_sign_on: false) }
it "throws an error" do
expect{ oauth_user.save }.to raise_error StandardError
end
@@ -56,36 +67,36 @@ describe Gitlab::OAuth::User do
end
context "with auto_link_ldap_user disabled (default)" do
- before { Gitlab.config.omniauth.stub auto_link_ldap_user: false }
+ before { stub_omniauth_config(auto_link_ldap_user: false) }
include_examples "to verify compliance with allow_single_sign_on"
end
context "with auto_link_ldap_user enabled" do
- before { Gitlab.config.omniauth.stub auto_link_ldap_user: true }
-
+ before { stub_omniauth_config(auto_link_ldap_user: true) }
+
context "and no LDAP provider defined" do
- before { allow(Gitlab::LDAP::Config).to receive(:providers).and_return([]) }
-
+ before { stub_ldap_config(providers: []) }
+
include_examples "to verify compliance with allow_single_sign_on"
end
-
+
context "and at least one LDAP provider is defined" do
- before { allow(Gitlab::LDAP::Config).to receive(:providers).and_return(['ldapmain']) }
+ before { stub_ldap_config(providers: %w(ldapmain)) }
context "and a corresponding LDAP person" do
before do
- ldap_user.stub(:uid) { uid }
- ldap_user.stub(:username) { uid }
- ldap_user.stub(:email) { ['johndoe@example.com','john2@example.com'] }
- ldap_user.stub(:dn) { 'uid=user1,ou=People,dc=example' }
+ allow(ldap_user).to receive(:uid) { uid }
+ allow(ldap_user).to receive(:username) { uid }
+ allow(ldap_user).to receive(:email) { ['johndoe@example.com','john2@example.com'] }
+ allow(ldap_user).to receive(:dn) { 'uid=user1,ou=People,dc=example' }
allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(ldap_user)
end
-
+
context "and no account for the LDAP user" do
-
+
it "creates a user with dual LDAP and omniauth identities" do
oauth_user.save
-
+
expect(gl_user).to be_valid
expect(gl_user.username).to eql uid
expect(gl_user.email).to eql 'johndoe@example.com'
@@ -97,12 +108,12 @@ describe Gitlab::OAuth::User do
])
end
end
-
+
context "and LDAP user has an account already" do
let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') }
it "adds the omniauth identity to the LDAP account" do
oauth_user.save
-
+
expect(gl_user).to be_valid
expect(gl_user.username).to eql 'john'
expect(gl_user.email).to eql 'john@example.com'
@@ -115,10 +126,10 @@ describe Gitlab::OAuth::User do
end
end
end
-
+
context "and no corresponding LDAP person" do
before { allow(Gitlab::LDAP::Person).to receive(:find_by_uid).and_return(nil) }
-
+
include_examples "to verify compliance with allow_single_sign_on"
end
end
@@ -128,11 +139,11 @@ describe Gitlab::OAuth::User do
describe 'blocking' do
let(:provider) { 'twitter' }
- before { Gitlab.config.omniauth.stub allow_single_sign_on: true }
+ before { stub_omniauth_config(allow_single_sign_on: true) }
context 'signup with omniauth only' do
context 'dont block on create' do
- before { Gitlab.config.omniauth.stub block_auto_created_users: false }
+ before { stub_omniauth_config(block_auto_created_users: false) }
it do
oauth_user.save
@@ -142,7 +153,7 @@ describe Gitlab::OAuth::User do
end
context 'block on create' do
- before { Gitlab.config.omniauth.stub block_auto_created_users: true }
+ before { stub_omniauth_config(block_auto_created_users: true) }
it do
oauth_user.save
@@ -154,17 +165,17 @@ describe Gitlab::OAuth::User do
context 'signup with linked omniauth and LDAP account' do
before do
- Gitlab.config.omniauth.stub auto_link_ldap_user: true
- ldap_user.stub(:uid) { uid }
- ldap_user.stub(:username) { uid }
- ldap_user.stub(:email) { ['johndoe@example.com','john2@example.com'] }
- ldap_user.stub(:dn) { 'uid=user1,ou=People,dc=example' }
+ stub_omniauth_config(auto_link_ldap_user: true)
+ allow(ldap_user).to receive(:uid) { uid }
+ allow(ldap_user).to receive(:username) { uid }
+ allow(ldap_user).to receive(:email) { ['johndoe@example.com','john2@example.com'] }
+ allow(ldap_user).to receive(:dn) { 'uid=user1,ou=People,dc=example' }
allow(oauth_user).to receive(:ldap_person).and_return(ldap_user)
end
context "and no account for the LDAP user" do
context 'dont block on create (LDAP)' do
- before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: false }
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) }
it do
oauth_user.save
@@ -174,7 +185,7 @@ describe Gitlab::OAuth::User do
end
context 'block on create (LDAP)' do
- before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: true }
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) }
it do
oauth_user.save
@@ -188,7 +199,7 @@ describe Gitlab::OAuth::User do
let!(:existing_user) { create(:omniauth_user, email: 'john@example.com', extern_uid: 'uid=user1,ou=People,dc=example', provider: 'ldapmain', username: 'john') }
context 'dont block on create (LDAP)' do
- before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: false }
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) }
it do
oauth_user.save
@@ -198,7 +209,7 @@ describe Gitlab::OAuth::User do
end
context 'block on create (LDAP)' do
- before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: true }
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) }
it do
oauth_user.save
@@ -217,7 +228,7 @@ describe Gitlab::OAuth::User do
end
context 'dont block on create' do
- before { Gitlab.config.omniauth.stub block_auto_created_users: false }
+ before { stub_omniauth_config(block_auto_created_users: false) }
it do
oauth_user.save
@@ -227,7 +238,7 @@ describe Gitlab::OAuth::User do
end
context 'block on create' do
- before { Gitlab.config.omniauth.stub block_auto_created_users: true }
+ before { stub_omniauth_config(block_auto_created_users: true) }
it do
oauth_user.save
@@ -237,7 +248,7 @@ describe Gitlab::OAuth::User do
end
context 'dont block on create (LDAP)' do
- before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: false }
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: false) }
it do
oauth_user.save
@@ -247,7 +258,7 @@ describe Gitlab::OAuth::User do
end
context 'block on create (LDAP)' do
- before { Gitlab::LDAP::Config.any_instance.stub block_auto_created_users: true }
+ before { allow_any_instance_of(Gitlab::LDAP::Config).to receive_messages(block_auto_created_users: true) }
it do
oauth_user.save
diff --git a/spec/lib/gitlab/popen_spec.rb b/spec/lib/gitlab/popen_spec.rb
index cd9d0456b25..e53efec6c67 100644
--- a/spec/lib/gitlab/popen_spec.rb
+++ b/spec/lib/gitlab/popen_spec.rb
@@ -1,7 +1,7 @@
require 'spec_helper'
describe 'Gitlab::Popen', no_db: true do
- let (:path) { Rails.root.join('tmp').to_s }
+ let(:path) { Rails.root.join('tmp').to_s }
before do
@klass = Class.new(Object)
@@ -28,7 +28,7 @@ describe 'Gitlab::Popen', no_db: true do
context 'unsafe string command' do
it 'raises an error when it gets called with a string argument' do
- expect { @klass.new.popen('ls', path) }.to raise_error
+ expect { @klass.new.popen('ls', path) }.to raise_error(RuntimeError)
end
end
@@ -40,6 +40,4 @@ describe 'Gitlab::Popen', no_db: true do
it { expect(@status).to be_zero }
it { expect(@output).to include('spec') }
end
-
end
-
diff --git a/spec/lib/gitlab/project_search_results_spec.rb b/spec/lib/gitlab/project_search_results_spec.rb
new file mode 100644
index 00000000000..32a25f08cac
--- /dev/null
+++ b/spec/lib/gitlab/project_search_results_spec.rb
@@ -0,0 +1,23 @@
+require 'spec_helper'
+
+describe Gitlab::ProjectSearchResults do
+ let(:project) { create(:project) }
+ let(:query) { 'hello world' }
+
+ describe 'initialize with empty ref' do
+ let(:results) { Gitlab::ProjectSearchResults.new(project.id, query, '') }
+
+ it { expect(results.project).to eq(project) }
+ it { expect(results.repository_ref).to be_nil }
+ it { expect(results.query).to eq('hello\\ world') }
+ end
+
+ describe 'initialize with ref' do
+ let(:ref) { 'refs/heads/test' }
+ let(:results) { Gitlab::ProjectSearchResults.new(project.id, query, ref) }
+
+ it { expect(results.project).to eq(project) }
+ it { expect(results.repository_ref).to eq(ref) }
+ it { expect(results.query).to eq('hello\\ world') }
+ end
+end
diff --git a/spec/lib/gitlab/satellite/action_spec.rb b/spec/lib/gitlab/satellite/action_spec.rb
index 28e3d64ee2b..0a93676edc3 100644
--- a/spec/lib/gitlab/satellite/action_spec.rb
+++ b/spec/lib/gitlab/satellite/action_spec.rb
@@ -17,15 +17,13 @@ describe 'Gitlab::Satellite::Action' do
starting_remote_count = repo.git.list_remotes.size
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')
+ origin_uri = repo.git.remote({ v: true }).split(" ")[1]
+
+ repo.git.remote({ raise: true }, 'add', 'another-remote', origin_uri)
+ repo.git.branch({ raise: true }, 'a-new-branch')
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")
@@ -35,10 +33,10 @@ describe 'Gitlab::Satellite::Action' do
#These must happen in the context of the satellite directory...
satellite_action = Gitlab::Satellite::Action.new(user, project)
- project.satellite.lock {
+ project.satellite.lock do
#Now clean it up, use send to get around prepare_satellite! being protected
satellite_action.send(:prepare_satellite!, repo)
- }
+ end
#verify it's clean
heads = repo.heads.map(&:name)
@@ -100,16 +98,16 @@ describe 'Gitlab::Satellite::Action' do
def flocked?(&block)
status = flock LOCK_EX|LOCK_NB
case status
- when false
- return true
- when 0
- begin
- block ? block.call : false
- ensure
- flock LOCK_UN
- end
- else
- raise SystemCallError, status
+ when false
+ return true
+ when 0
+ begin
+ block ? block.call : false
+ ensure
+ flock LOCK_UN
+ end
+ else
+ raise SystemCallError, status
end
end
end
diff --git a/spec/lib/gitlab/satellite/merge_action_spec.rb b/spec/lib/gitlab/satellite/merge_action_spec.rb
index 915e3ff0e51..9b1c9a34e29 100644
--- a/spec/lib/gitlab/satellite/merge_action_spec.rb
+++ b/spec/lib/gitlab/satellite/merge_action_spec.rb
@@ -27,7 +27,7 @@ describe 'Gitlab::Satellite::MergeAction' do
context 'between branches' do
it 'should raise exception -- not expected to be used by non forks' do
- expect { Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).commits_between }.to raise_error
+ expect { Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).commits_between }.to raise_error(RuntimeError)
end
end
end
@@ -75,30 +75,30 @@ describe 'Gitlab::Satellite::MergeAction' do
context 'between branches' do
it 'should get proper diffs' do
- expect{ Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).diffs_between_satellite }.to raise_error
+ expect{ Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).diffs_between_satellite }.to raise_error(RuntimeError)
end
end
end
describe '#can_be_merged?' do
context 'on fork' do
- it { expect(Gitlab::Satellite::MergeAction.new(
- merge_request_fork.author,
- merge_request_fork).can_be_merged?).to be_truthy }
+ it do
+ expect(Gitlab::Satellite::MergeAction.new(merge_request_fork.author, merge_request_fork).can_be_merged?).to be_truthy
+ end
- it { expect(Gitlab::Satellite::MergeAction.new(
- merge_request_fork_with_conflict.author,
- merge_request_fork_with_conflict).can_be_merged?).to be_falsey }
+ it do
+ expect(Gitlab::Satellite::MergeAction.new(merge_request_fork_with_conflict.author, merge_request_fork_with_conflict).can_be_merged?).to be_falsey
+ end
end
context 'between branches' do
- it { expect(Gitlab::Satellite::MergeAction.new(
- merge_request.author,
- merge_request).can_be_merged?).to be_truthy }
+ it do
+ expect(Gitlab::Satellite::MergeAction.new(merge_request.author, merge_request).can_be_merged?).to be_truthy
+ end
- it { expect(Gitlab::Satellite::MergeAction.new(
- merge_request_with_conflict.author,
- merge_request_with_conflict).can_be_merged?).to be_falsey }
+ it do
+ expect(Gitlab::Satellite::MergeAction.new(merge_request_with_conflict.author, merge_request_with_conflict).can_be_merged?).to be_falsey
+ end
end
end
end
diff --git a/spec/lib/gitlab/upgrader_spec.rb b/spec/lib/gitlab/upgrader_spec.rb
index baa4bd0f28f..8df84665e16 100644
--- a/spec/lib/gitlab/upgrader_spec.rb
+++ b/spec/lib/gitlab/upgrader_spec.rb
@@ -10,14 +10,14 @@ describe Gitlab::Upgrader do
describe 'latest_version?' do
it 'should be true if newest version' do
- upgrader.stub(latest_version_raw: current_version)
+ allow(upgrader).to receive(:latest_version_raw).and_return(current_version)
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")
+ allow(upgrader).to receive(:current_version_raw).and_return("5.3.0")
expect(upgrader.latest_version_raw).to eq("v5.4.2")
end
diff --git a/spec/lib/gitlab/version_info_spec.rb b/spec/lib/gitlab/version_info_spec.rb
index 5afeb1c1ec3..18f71b40fe0 100644
--- a/spec/lib/gitlab/version_info_spec.rb
+++ b/spec/lib/gitlab/version_info_spec.rb
@@ -66,4 +66,3 @@ describe 'Gitlab::VersionInfo', no_db: true do
it { expect(@unknown.to_s).to eq("Unknown") }
end
end
-
diff --git a/spec/lib/votes_spec.rb b/spec/lib/votes_spec.rb
index df243a26008..39e5d054e62 100644
--- a/spec/lib/votes_spec.rb
+++ b/spec/lib/votes_spec.rb
@@ -179,7 +179,10 @@ describe Issue, 'Votes' do
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)
+ 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 c40ae7b5703..97c07ad7d55 100644
--- a/spec/mailers/notify_spec.rb
+++ b/spec/mailers/notify_spec.rb
@@ -1,4 +1,5 @@
require 'spec_helper'
+require 'email_spec'
describe Notify do
include EmailSpec::Helpers
@@ -418,9 +419,7 @@ describe Notify do
describe 'project access changed' do
let(:project) { create(:project) }
let(:user) { create(:user) }
- let(:project_member) { create(:project_member,
- project: project,
- user: user) }
+ let(:project_member) { create(:project_member, project: project, user: user) }
subject { Notify.project_access_granted_email(project_member.id) }
it_behaves_like 'an email sent from GitLab'
diff --git a/spec/models/commit_range_spec.rb b/spec/models/commit_range_spec.rb
index e7fb43ff335..1031af097bd 100644
--- a/spec/models/commit_range_spec.rb
+++ b/spec/models/commit_range_spec.rb
@@ -14,7 +14,7 @@ describe CommitRange do
let(:range2) { described_class.new("#{sha_from}..#{sha_to}") }
it 'raises ArgumentError when given an invalid range string' do
- expect { described_class.new("Foo") }.to raise_error
+ expect { described_class.new("Foo") }.to raise_error(ArgumentError)
end
describe '#to_s' do
@@ -60,11 +60,11 @@ describe CommitRange do
end
it 'includes the correct values for a three-dot range' do
- expect(range.to_param).to eq({from: sha_from, to: sha_to})
+ expect(range.to_param).to eq({ from: sha_from, to: sha_to })
end
it 'includes the correct values for a two-dot range' do
- expect(range2.to_param).to eq({from: sha_from + '^', to: sha_to})
+ expect(range2.to_param).to eq({ from: sha_from + '^', to: sha_to })
end
end
diff --git a/spec/models/commit_spec.rb b/spec/models/commit_spec.rb
index 27eb02a870b..e303a97e6b5 100644
--- a/spec/models/commit_spec.rb
+++ b/spec/models/commit_spec.rb
@@ -77,13 +77,13 @@ eos
let(:other_issue) { create :issue, project: other_project }
it 'detects issues that this commit is marked as closing' do
- commit.stub(safe_message: "Fixes ##{issue.iid}")
+ allow(commit).to receive(:safe_message).and_return("Fixes ##{issue.iid}")
expect(commit.closes_issues).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}")
+ allow(commit).to receive(:safe_message).and_return("Fixes #{ext_ref}")
expect(commit.closes_issues).to be_empty
end
end
@@ -93,7 +93,9 @@ eos
let(:author) { create(:user, email: commit.author_email) }
let(:backref_text) { "commit #{subject.id}" }
- let(:set_mentionable_text) { ->(txt){ subject.stub(safe_message: txt) } }
+ let(:set_mentionable_text) do
+ ->(txt) { allow(subject).to receive(:safe_message).and_return(txt) }
+ end
# Include the subject in the repository stub.
let(:extra_commits) { [subject] }
diff --git a/spec/models/concerns/issuable_spec.rb b/spec/models/concerns/issuable_spec.rb
index 86c395a8e8e..b6d80451d2e 100644
--- a/spec/models/concerns/issuable_spec.rb
+++ b/spec/models/concerns/issuable_spec.rb
@@ -11,7 +11,10 @@ describe Issue, "Issuable" do
end
describe "Validation" do
- before { subject.stub(set_iid: false) }
+ before do
+ allow(subject).to receive(:set_iid).and_return(false)
+ end
+
it { is_expected.to validate_presence_of(:project) }
it { is_expected.to validate_presence_of(:iid) }
it { is_expected.to validate_presence_of(:author) }
diff --git a/spec/models/concerns/mentionable_spec.rb b/spec/models/concerns/mentionable_spec.rb
index 22237f2e9f2..2d6fe003215 100644
--- a/spec/models/concerns/mentionable_spec.rb
+++ b/spec/models/concerns/mentionable_spec.rb
@@ -23,9 +23,58 @@ describe Issue, "Mentionable" do
end
it 'correctly removes already-mentioned Commits' do
- expect(Note).not_to receive(:create_cross_reference_note)
+ expect(SystemNoteService).not_to receive(:cross_reference)
issue.create_cross_references!(project, author, [commit2])
end
end
+
+ describe '#create_new_cross_references!' do
+ let(:project) { create(:project) }
+ let(:issues) { create_list(:issue, 2, project: project) }
+
+ context 'before changes are persisted' do
+ it 'ignores pre-existing references' do
+ issue = create_issue(description: issues[0].to_reference)
+
+ expect(SystemNoteService).not_to receive(:cross_reference)
+
+ issue.description = 'New description'
+ issue.create_new_cross_references!
+ end
+
+ it 'notifies new references' do
+ issue = create_issue(description: issues[0].to_reference)
+
+ expect(SystemNoteService).to receive(:cross_reference).with(issues[1], any_args)
+
+ issue.description = issues[1].to_reference
+ issue.create_new_cross_references!
+ end
+ end
+
+ context 'after changes are persisted' do
+ it 'ignores pre-existing references' do
+ issue = create_issue(description: issues[0].to_reference)
+
+ expect(SystemNoteService).not_to receive(:cross_reference)
+
+ issue.update_attributes(description: 'New description')
+ issue.create_new_cross_references!
+ end
+
+ it 'notifies new references' do
+ issue = create_issue(description: issues[0].to_reference)
+
+ expect(SystemNoteService).to receive(:cross_reference).with(issues[1], any_args)
+
+ issue.update_attributes(description: issues[1].to_reference)
+ issue.create_new_cross_references!
+ end
+ end
+
+ def create_issue(description:)
+ create(:issue, project: project, description: description)
+ end
+ end
end
diff --git a/spec/models/deploy_keys_project_spec.rb b/spec/models/deploy_keys_project_spec.rb
index 705ef257d86..0eb22599d18 100644
--- a/spec/models/deploy_keys_project_spec.rb
+++ b/spec/models/deploy_keys_project_spec.rb
@@ -44,9 +44,7 @@ describe DeployKeysProject do
it "destroys the deploy key" do
subject.destroy
- expect {
- deploy_key.reload
- }.to raise_error(ActiveRecord::RecordNotFound)
+ expect { deploy_key.reload }.to raise_error(ActiveRecord::RecordNotFound)
end
end
end
diff --git a/spec/models/forked_project_link_spec.rb b/spec/models/forked_project_link_spec.rb
index 7d0ad44a92c..d90fbfe1ea5 100644
--- a/spec/models/forked_project_link_spec.rb
+++ b/spec/models/forked_project_link_spec.rb
@@ -58,10 +58,10 @@ describe :forked_from_project do
end
def fork_project(from_project, user)
- context = Projects::ForkService.new(from_project, user)
- shell = double("gitlab_shell")
- shell.stub(fork_repository: true)
- context.stub(gitlab_shell: shell)
- context.execute
-end
+ shell = double('gitlab_shell', fork_repository: true)
+
+ service = Projects::ForkService.new(from_project, user)
+ allow(service).to receive(:gitlab_shell).and_return(shell)
+ service.execute
+end
diff --git a/spec/models/hooks/service_hook_spec.rb b/spec/models/hooks/service_hook_spec.rb
index fb5111dd9f5..4c8b8910ae7 100644
--- a/spec/models/hooks/service_hook_spec.rb
+++ b/spec/models/hooks/service_hook_spec.rb
@@ -26,7 +26,7 @@ describe ServiceHook do
describe "execute" do
before(:each) do
@service_hook = create(:service_hook)
- @data = { project_id: 1, data: {}}
+ @data = { project_id: 1, data: {} }
WebMock.stub_request(:post, @service_hook.url)
end
@@ -34,7 +34,7 @@ describe ServiceHook do
it "POSTs to the web hook URL" do
@service_hook.execute(@data)
expect(WebMock).to have_requested(:post, @service_hook.url).with(
- headers: {'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Service Hook'}
+ headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Service Hook' }
).once
end
@@ -43,16 +43,14 @@ describe ServiceHook do
@service_hook.execute(@data)
expect(WebMock).to have_requested(:post, @service_hook.url).with(
- headers: {'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Service Hook'}
+ headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Service Hook' }
).once
end
it "catches exceptions" do
expect(WebHook).to receive(:post).and_raise("Some HTTP Post error")
- expect {
- @service_hook.execute(@data)
- }.to raise_error
+ expect { @service_hook.execute(@data) }.to raise_error(RuntimeError)
end
end
end
diff --git a/spec/models/hooks/system_hook_spec.rb b/spec/models/hooks/system_hook_spec.rb
index edb21fc2e47..4175f9dd88f 100644
--- a/spec/models/hooks/system_hook_spec.rb
+++ b/spec/models/hooks/system_hook_spec.rb
@@ -29,7 +29,7 @@ describe SystemHook do
Projects::CreateService.new(create(:user), name: 'empty').execute
expect(WebMock).to have_requested(:post, @system_hook.url).with(
body: /project_create/,
- headers: {'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook'}
+ headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
end
@@ -39,7 +39,7 @@ describe SystemHook do
Projects::DestroyService.new(project, user, {}).execute
expect(WebMock).to have_requested(:post, @system_hook.url).with(
body: /project_destroy/,
- headers: {'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook'}
+ headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
end
@@ -47,7 +47,7 @@ describe SystemHook do
create(:user)
expect(WebMock).to have_requested(:post, @system_hook.url).with(
body: /user_create/,
- headers: {'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook'}
+ headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
end
@@ -56,7 +56,7 @@ describe SystemHook do
user.destroy
expect(WebMock).to have_requested(:post, @system_hook.url).with(
body: /user_destroy/,
- headers: {'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook'}
+ headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
end
@@ -66,7 +66,7 @@ describe SystemHook do
project.team << [user, :master]
expect(WebMock).to have_requested(:post, @system_hook.url).with(
body: /user_add_to_team/,
- headers: {'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook'}
+ headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
end
@@ -77,7 +77,7 @@ describe SystemHook do
project.project_members.destroy_all
expect(WebMock).to have_requested(:post, @system_hook.url).with(
body: /user_remove_from_team/,
- headers: {'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook'}
+ headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
end
@@ -85,7 +85,7 @@ describe SystemHook do
create(:group)
expect(WebMock).to have_requested(:post, @system_hook.url).with(
body: /group_create/,
- headers: {'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook'}
+ headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
end
@@ -94,7 +94,7 @@ describe SystemHook do
group.destroy
expect(WebMock).to have_requested(:post, @system_hook.url).with(
body: /group_destroy/,
- headers: {'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook'}
+ headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
end
@@ -104,7 +104,7 @@ describe SystemHook do
group.add_user(user, Gitlab::Access::MASTER)
expect(WebMock).to have_requested(:post, @system_hook.url).with(
body: /user_add_to_group/,
- headers: {'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook'}
+ headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
end
@@ -115,7 +115,7 @@ describe SystemHook do
group.group_members.destroy_all
expect(WebMock).to have_requested(:post, @system_hook.url).with(
body: /user_remove_from_group/,
- headers: {'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook'}
+ headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'System Hook' }
).once
end
diff --git a/spec/models/hooks/web_hook_spec.rb b/spec/models/hooks/web_hook_spec.rb
index 4c3f0cbcbbf..23f30881d99 100644
--- a/spec/models/hooks/web_hook_spec.rb
+++ b/spec/models/hooks/web_hook_spec.rb
@@ -47,7 +47,7 @@ describe ProjectHook do
@project_hook = create(:project_hook)
@project = create(:project)
@project.hooks << [@project_hook]
- @data = { before: 'oldrev', after: 'newrev', ref: 'ref'}
+ @data = { before: 'oldrev', after: 'newrev', ref: 'ref' }
WebMock.stub_request(:post, @project_hook.url)
end
@@ -55,7 +55,7 @@ describe ProjectHook do
it "POSTs to the web hook URL" do
@project_hook.execute(@data, 'push_hooks')
expect(WebMock).to have_requested(:post, @project_hook.url).with(
- headers: {'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Push Hook'}
+ headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Push Hook' }
).once
end
@@ -64,16 +64,14 @@ describe ProjectHook do
@project_hook.execute(@data, 'push_hooks')
expect(WebMock).to have_requested(:post, @project_hook.url).with(
- headers: {'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Push Hook'}
+ headers: { 'Content-Type'=>'application/json', 'X-Gitlab-Event'=>'Push Hook' }
).once
end
it "catches exceptions" do
expect(WebHook).to receive(:post).and_raise("Some HTTP Post error")
- expect {
- @project_hook.execute(@data, 'push_hooks')
- }.to raise_error
+ expect { @project_hook.execute(@data, 'push_hooks') }.to raise_error(RuntimeError)
end
end
end
diff --git a/spec/models/issue_spec.rb b/spec/models/issue_spec.rb
index 614b648bb52..9bac451c28c 100644
--- a/spec/models/issue_spec.rb
+++ b/spec/models/issue_spec.rb
@@ -70,7 +70,7 @@ describe Issue do
it_behaves_like 'an editable mentionable' do
subject { create(:issue, project: project) }
- let(:backref_text) { "issue ##{subject.iid}" }
+ let(:backref_text) { "issue #{subject.to_reference}" }
let(:set_mentionable_text) { ->(txt){ subject.description = txt } }
end
diff --git a/spec/models/key_spec.rb b/spec/models/key_spec.rb
index fbb9e162952..456bf221d62 100644
--- a/spec/models/key_spec.rb
+++ b/spec/models/key_spec.rb
@@ -63,7 +63,7 @@ describe Key do
key = build(:key)
# Not always the middle, but close enough
- key.key = key.key[0..100] + ' ' + key.key[100..-1]
+ key.key = key.key[0..100] + ' ' + key.key[101..-1]
expect(key).not_to be_valid
end
@@ -71,6 +71,12 @@ describe Key do
it 'rejects the unfingerprintable key (not a key)' do
expect(build(:key, key: 'ssh-rsa an-invalid-key==')).not_to be_valid
end
+
+ it 'rejects the multiple line key' do
+ key = build(:key)
+ key.key.gsub!(' ', "\n")
+ expect(key).not_to be_valid
+ end
end
context 'callbacks' do
diff --git a/spec/models/members/group_member_spec.rb b/spec/models/members/group_member_spec.rb
index 7c10c9f0f48..652026729bb 100644
--- a/spec/models/members/group_member_spec.rb
+++ b/spec/models/members/group_member_spec.rb
@@ -24,8 +24,11 @@ describe GroupMember 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)
+
+ allow(membership).to receive(:notification_service).
+ and_return(double('NotificationService').as_null_object)
expect(membership).to receive(:notification_service)
+
membership.save
end
end
@@ -33,7 +36,8 @@ describe GroupMember do
describe "#after_update" do
before do
@group_member = create :group_member
- @group_member.stub(notification_service: double('NotificationService').as_null_object)
+ allow(@group_member).to receive(:notification_service).
+ and_return(double('NotificationService').as_null_object)
end
it "should send email to user" do
diff --git a/spec/models/members/project_member_spec.rb b/spec/models/members/project_member_spec.rb
index 5c72cfe1d6a..ee912bf12a2 100644
--- a/spec/models/members/project_member_spec.rb
+++ b/spec/models/members/project_member_spec.rb
@@ -43,7 +43,7 @@ describe ProjectMember 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_1, :create_project, @project_2)).to be_truthy }
it { expect(@abilities.allowed?(@user_2, :read_project, @project_2)).to be_truthy }
end
diff --git a/spec/models/merge_request_spec.rb b/spec/models/merge_request_spec.rb
index 0465aa34843..76f6d8c54c4 100644
--- a/spec/models/merge_request_spec.rb
+++ b/spec/models/merge_request_spec.rb
@@ -111,17 +111,18 @@ describe MergeRequest do
let(:commit2) { double('commit2', closes_issues: [issue1]) }
before do
- subject.stub(commits: [commit0, commit1, commit2])
+ allow(subject).to receive(:commits).and_return([commit0, commit1, commit2])
end
it 'accesses the set of issues that will be closed on acceptance' do
- subject.project.stub(default_branch: subject.target_branch)
+ allow(subject.project).to receive(:default_branch).
+ and_return(subject.target_branch)
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')
+ allow(subject.project).to receive(:default_branch).and_return('master')
subject.target_branch = 'something-else'
expect(subject.closes_issues).to be_empty
@@ -130,7 +131,8 @@ describe MergeRequest do
it 'detects issues mentioned in the description' do
issue2 = create(:issue, project: subject.project)
subject.description = "Closes #{issue2.to_reference}"
- subject.project.stub(default_branch: subject.target_branch)
+ allow(subject.project).to receive(:default_branch).
+ and_return(subject.target_branch)
expect(subject.closes_issues).to include(issue2)
end
@@ -163,10 +165,10 @@ describe MergeRequest do
end
it_behaves_like 'an editable mentionable' do
- subject { create(:merge_request, source_project: project, target_project: project) }
+ subject { create(:merge_request, source_project: project) }
- let(:backref_text) { "merge request !#{subject.iid}" }
- let(:set_mentionable_text) { ->(txt){ subject.title = txt } }
+ let(:backref_text) { "merge request #{subject.to_reference}" }
+ let(:set_mentionable_text) { ->(txt){ subject.description = txt } }
end
it_behaves_like 'a Taskable' do
diff --git a/spec/models/milestone_spec.rb b/spec/models/milestone_spec.rb
index eb73aa763fc..36352e1ecce 100644
--- a/spec/models/milestone_spec.rb
+++ b/spec/models/milestone_spec.rb
@@ -21,11 +21,11 @@ describe Milestone do
it { is_expected.to have_many(:issues) }
end
- describe "Mass assignment" do
- end
-
describe "Validation" do
- before { subject.stub(set_iid: false) }
+ before do
+ allow(subject).to receive(:set_iid).and_return(false)
+ end
+
it { is_expected.to validate_presence_of(:title) }
it { is_expected.to validate_presence_of(:project) }
end
@@ -66,7 +66,7 @@ describe Milestone do
describe :expired? do
context "expired" do
before do
- milestone.stub(due_date: Date.today.prev_year)
+ allow(milestone).to receive(:due_date).and_return(Date.today.prev_year)
end
it { expect(milestone.expired?).to be_truthy }
@@ -74,7 +74,7 @@ describe Milestone do
context "not expired" do
before do
- milestone.stub(due_date: Date.today.next_year)
+ allow(milestone).to receive(:due_date).and_return(Date.today.next_year)
end
it { expect(milestone.expired?).to be_falsey }
@@ -83,7 +83,7 @@ describe Milestone do
describe :percent_complete do
before do
- milestone.stub(
+ allow(milestone).to receive_messages(
closed_items_count: 3,
total_items_count: 4
)
diff --git a/spec/models/namespace_spec.rb b/spec/models/namespace_spec.rb
index e87432fdf62..1d72a9503ae 100644
--- a/spec/models/namespace_spec.rb
+++ b/spec/models/namespace_spec.rb
@@ -53,7 +53,7 @@ describe Namespace do
describe :move_dir do
before do
@namespace = create :namespace
- @namespace.stub(path_changed?: true)
+ allow(@namespace).to receive(:path_changed?).and_return(true)
end
it "should raise error when directory exists" do
@@ -62,8 +62,8 @@ describe Namespace do
it "should move dir if path changed" do
new_path = @namespace.path + "_new"
- @namespace.stub(path_was: @namespace.path)
- @namespace.stub(path: new_path)
+ allow(@namespace).to receive(:path_was).and_return(@namespace.path)
+ allow(@namespace).to receive(:path).and_return(new_path)
expect(@namespace.move_dir).to be_truthy
end
end
diff --git a/spec/models/note_spec.rb b/spec/models/note_spec.rb
index 9037992bb08..eba33dd510f 100644
--- a/spec/models/note_spec.rb
+++ b/spec/models/note_spec.rb
@@ -172,9 +172,9 @@ describe Note do
@p2.project_members.create(user: @u3, access_level: ProjectMember::DEVELOPER)
end
- 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 }
+ it { expect(@abilities.allowed?(@u1, :create_note, @p1)).to be_falsey }
+ it { expect(@abilities.allowed?(@u2, :create_note, @p1)).to be_truthy }
+ it { expect(@abilities.allowed?(@u3, :create_note, @p1)).to be_falsey }
end
describe 'admin' do
diff --git a/spec/models/project_security_spec.rb b/spec/models/project_security_spec.rb
index 1ee19003543..f600a240c46 100644
--- a/spec/models/project_security_spec.rb
+++ b/spec/models/project_security_spec.rb
@@ -110,17 +110,3 @@ describe Project do
end
end
end
-# == 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
-# private_flag :boolean default(TRUE), not null
-# code :string(255)
-#
-
diff --git a/spec/models/project_services/asana_service_spec.rb b/spec/models/project_services/asana_service_spec.rb
index cc1f99e0c72..64bb92fba95 100644
--- a/spec/models/project_services/asana_service_spec.rb
+++ b/spec/models/project_services/asana_service_spec.rb
@@ -42,7 +42,7 @@ describe AsanaService, models: true do
before do
@asana = AsanaService.new
- @asana.stub(
+ allow(@asana).to receive_messages(
project: project,
project_id: project.id,
service_hook: true,
diff --git a/spec/models/project_services/assembla_service_spec.rb b/spec/models/project_services/assembla_service_spec.rb
index 9aee754dd63..17e9361dd5c 100644
--- a/spec/models/project_services/assembla_service_spec.rb
+++ b/spec/models/project_services/assembla_service_spec.rb
@@ -32,7 +32,7 @@ describe AssemblaService, models: true do
before do
@assembla_service = AssemblaService.new
- @assembla_service.stub(
+ allow(@assembla_service).to receive_messages(
project_id: project.id,
project: project,
service_hook: true,
diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb
index 6db54243eac..9445d96c337 100644
--- a/spec/models/project_services/buildkite_service_spec.rb
+++ b/spec/models/project_services/buildkite_service_spec.rb
@@ -29,12 +29,10 @@ describe BuildkiteService do
describe 'commits methods' do
before do
@project = Project.new
- @project.stub(
- default_branch: 'default-brancho'
- )
+ allow(@project).to receive(:default_branch).and_return('default-brancho')
@service = BuildkiteService.new
- @service.stub(
+ allow(@service).to receive_messages(
project: @project,
service_hook: true,
project_url: 'https://buildkite.com/account-name/example-project',
diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb
index e6e8fbba6a7..7e5b15cb09e 100644
--- a/spec/models/project_services/flowdock_service_spec.rb
+++ b/spec/models/project_services/flowdock_service_spec.rb
@@ -32,7 +32,7 @@ describe FlowdockService do
before do
@flowdock_service = FlowdockService.new
- @flowdock_service.stub(
+ allow(@flowdock_service).to receive_messages(
project_id: project.id,
project: project,
service_hook: true,
diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb
index 1a7765e5c2a..9e156472316 100644
--- a/spec/models/project_services/gemnasium_service_spec.rb
+++ b/spec/models/project_services/gemnasium_service_spec.rb
@@ -32,7 +32,7 @@ describe GemnasiumService do
before do
@gemnasium_service = GemnasiumService.new
- @gemnasium_service.stub(
+ allow(@gemnasium_service).to receive_messages(
project_id: project.id,
project: project,
service_hook: true,
diff --git a/spec/models/project_services/gitlab_ci_service_spec.rb b/spec/models/project_services/gitlab_ci_service_spec.rb
index c92cf3cdae6..a14384c87b4 100644
--- a/spec/models/project_services/gitlab_ci_service_spec.rb
+++ b/spec/models/project_services/gitlab_ci_service_spec.rb
@@ -21,18 +21,42 @@
require 'spec_helper'
describe GitlabCiService do
- describe "Associations" do
- it { is_expected.to belong_to :project }
- it { is_expected.to have_one :service_hook }
+ describe 'associations' do
+ it { is_expected.to belong_to(:project) }
+ it { is_expected.to have_one(:service_hook) }
end
- describe "Mass assignment" do
+ describe 'validations' do
+ context 'active' do
+ before { allow(subject).to receive(:activated?).and_return(true) }
+
+ it { is_expected.to validate_presence_of(:token) }
+ it { is_expected.to validate_presence_of(:project_url) }
+ it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) }
+ it { is_expected.to allow_value('http://ci.example.com/project/1').for(:project_url) }
+ it { is_expected.not_to allow_value('token with spaces').for(:token) }
+ it { is_expected.not_to allow_value('token/with%spaces').for(:token) }
+ it { is_expected.not_to allow_value('this is not url').for(:project_url) }
+ it { is_expected.not_to allow_value('http//noturl').for(:project_url) }
+ it { is_expected.not_to allow_value('ftp://ci.example.com/projects/3').for(:project_url) }
+ end
+
+ context 'inactive' do
+ before { allow(subject).to receive(:activated?).and_return(false) }
+
+ it { is_expected.not_to validate_presence_of(:token) }
+ it { is_expected.not_to validate_presence_of(:project_url) }
+ it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) }
+ it { is_expected.to allow_value('http://ci.example.com/project/1').for(:project_url) }
+ it { is_expected.to allow_value('token with spaces').for(:token) }
+ it { is_expected.to allow_value('ftp://ci.example.com/projects/3').for(:project_url) }
+ end
end
describe 'commits methods' do
before do
@service = GitlabCiService.new
- @service.stub(
+ allow(@service).to receive_messages(
service_hook: true,
project_url: 'http://ci.gitlab.org/projects/2',
token: 'verySecret'
@@ -56,9 +80,9 @@ describe GitlabCiService do
it "calls ci_yaml_file" do
service_hook = double
- service_hook.should_receive(:execute)
- @service.should_receive(:service_hook).and_return(service_hook)
- @service.should_receive(:ci_yaml_file).with(push_sample_data[:checkout_sha])
+ expect(service_hook).to receive(:execute)
+ expect(@service).to receive(:service_hook).and_return(service_hook)
+ expect(@service).to receive(:ci_yaml_file).with(push_sample_data[:checkout_sha])
@service.execute(push_sample_data)
end
@@ -72,7 +96,7 @@ describe GitlabCiService do
@user = create(:user)
@service = GitlabCiService.new
- @service.stub(
+ allow(@service).to receive_messages(
service_hook: true,
project_url: 'http://ci.gitlab.org/projects/2',
token: 'verySecret',
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index e88615e1a2e..4707673269a 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -37,7 +37,7 @@ describe HipchatService do
let(:push_sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) }
before(:each) do
- hipchat.stub(
+ allow(hipchat).to receive_messages(
project_id: project.id,
project: project,
room: 123456,
@@ -48,7 +48,7 @@ describe HipchatService do
end
it 'should use v1 if version is provided' do
- hipchat.stub(api_version: 'v1')
+ allow(hipchat).to receive(:api_version).and_return('v1')
expect(HipChat::Client).to receive(:new).
with(token,
api_version: 'v1',
@@ -59,7 +59,7 @@ describe HipchatService do
end
it 'should use v2 as the version when nothing is provided' do
- hipchat.stub(api_version: '')
+ allow(hipchat).to receive(:api_version).and_return('')
expect(HipChat::Client).to receive(:new).
with(token,
api_version: 'v2',
@@ -241,17 +241,17 @@ describe HipchatService do
context "#message_options" do
it "should be set to the defaults" do
- expect(hipchat.send(:message_options)).to eq({notify: false, color: 'yellow'})
+ expect(hipchat.send(:message_options)).to eq({ notify: false, color: 'yellow' })
end
it "should set notfiy to true" do
- hipchat.stub(notify: '1')
- expect(hipchat.send(:message_options)).to eq({notify: true, color: 'yellow'})
+ allow(hipchat).to receive(:notify).and_return('1')
+ expect(hipchat.send(:message_options)).to eq({ notify: true, color: 'yellow' })
end
it "should set the color" do
- hipchat.stub(color: 'red')
- expect(hipchat.send(:message_options)).to eq({notify: false, color: 'red'})
+ allow(hipchat).to receive(:color).and_return('red')
+ expect(hipchat.send(:message_options)).to eq({ notify: false, color: 'red' })
end
end
end
diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb
index 4c437aab12b..7d483a44c53 100644
--- a/spec/models/project_services/irker_service_spec.rb
+++ b/spec/models/project_services/irker_service_spec.rb
@@ -24,8 +24,8 @@ require 'json'
describe IrkerService 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 'Validations' do
@@ -38,22 +38,6 @@ describe IrkerService 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
- expect(subject.errors).not_to 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
- expect(subject.errors).to be_blank
- end
- end
end
describe 'Execute' do
@@ -62,26 +46,21 @@ describe IrkerService do
let(:project) { create(:project) }
let(:sample_data) { Gitlab::PushDataBuilder.build_sample(project, user) }
- let(:recipients) { '#commits' }
+ let(:recipients) { '#commits irc://test.net/#test ftp://bad' }
let(:colorize_messages) { '1' }
before do
- irker.stub(
+ allow(irker).to receive_messages(
active: true,
project: project,
project_id: project.id,
service_hook: true,
- properties: {
- 'recipients' => recipients,
- 'colorize_messages' => colorize_messages
- }
- )
- irker.settings = {
- server_ip: 'localhost',
+ server_host: 'localhost',
server_port: 6659,
- max_channels: 3,
- default_irc_uri: 'irc://chat.freenode.net/'
- }
+ default_irc_uri: 'irc://chat.freenode.net/',
+ recipients: recipients,
+ colorize_messages: colorize_messages)
+
irker.valid?
@irker_server = TCPServer.new 'localhost', 6659
end
@@ -97,11 +76,8 @@ describe IrkerService do
conn.readlines.each do |line|
msg = JSON.load(line.chomp("\n"))
expect(msg.keys).to match_array(['to', 'privmsg'])
- if msg['to'].is_a?(String)
- expect(msg['to']).to eq 'irc://chat.freenode.net/#commits'
- else
- expect(msg['to']).to match_array(['irc://chat.freenode.net/#commits'])
- end
+ expect(msg['to']).to match_array(["irc://chat.freenode.net/#commits",
+ "irc://test.net/#test"])
end
conn.close
end
diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb
index 5f93703b50a..ac10ffbd39b 100644
--- a/spec/models/project_services/pushover_service_spec.rb
+++ b/spec/models/project_services/pushover_service_spec.rb
@@ -52,7 +52,7 @@ describe PushoverService do
let(:api_url) { 'https://api.pushover.net/1/messages.json' }
before do
- pushover.stub(
+ allow(pushover).to receive_messages(
project: project,
project_id: project.id,
service_hook: true,
diff --git a/spec/models/project_services/slack_service/issue_message_spec.rb b/spec/models/project_services/slack_service/issue_message_spec.rb
index 8bca1fef44c..b78d92f23a1 100644
--- a/spec/models/project_services/slack_service/issue_message_spec.rb
+++ b/spec/models/project_services/slack_service/issue_message_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe SlackService::IssueMessage do
subject { SlackService::IssueMessage.new(args) }
- let(:args) {
+ let(:args) do
{
user: {
name: 'Test User',
@@ -23,7 +23,7 @@ describe SlackService::IssueMessage do
description: 'issue description'
}
}
- }
+ end
let(:color) { '#345' }
diff --git a/spec/models/project_services/slack_service/merge_message_spec.rb b/spec/models/project_services/slack_service/merge_message_spec.rb
index aeb408aa766..581c50d6c88 100644
--- a/spec/models/project_services/slack_service/merge_message_spec.rb
+++ b/spec/models/project_services/slack_service/merge_message_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe SlackService::MergeMessage do
subject { SlackService::MergeMessage.new(args) }
- let(:args) {
+ let(:args) do
{
user: {
name: 'Test User',
@@ -24,7 +24,7 @@ describe SlackService::MergeMessage do
target_branch: 'target_branch',
}
}
- }
+ end
let(:color) { '#345' }
diff --git a/spec/models/project_services/slack_service/push_message_spec.rb b/spec/models/project_services/slack_service/push_message_spec.rb
index 10963481a12..ddc290820d1 100644
--- a/spec/models/project_services/slack_service/push_message_spec.rb
+++ b/spec/models/project_services/slack_service/push_message_spec.rb
@@ -3,7 +3,7 @@ require 'spec_helper'
describe SlackService::PushMessage do
subject { SlackService::PushMessage.new(args) }
- let(:args) {
+ let(:args) do
{
after: 'after',
before: 'before',
@@ -12,7 +12,7 @@ describe SlackService::PushMessage do
user_name: 'user_name',
project_url: 'url'
}
- }
+ end
let(:color) { '#345' }
@@ -40,16 +40,16 @@ describe SlackService::PushMessage do
end
context 'tag push' do
- let(:args) {
+ let(:args) do
{
- after: 'after',
- before: Gitlab::Git::BLANK_SHA,
- project_name: 'project_name',
- ref: 'refs/tags/new_tag',
- user_name: 'user_name',
- project_url: 'url'
+ after: 'after',
+ before: Gitlab::Git::BLANK_SHA,
+ project_name: 'project_name',
+ ref: 'refs/tags/new_tag',
+ user_name: 'user_name',
+ project_url: 'url'
}
- }
+ end
it 'returns a message regarding pushes' do
expect(subject.pretext).to eq('user_name pushed new tag ' \
diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb
index e9105677d23..69466b11f09 100644
--- a/spec/models/project_services/slack_service_spec.rb
+++ b/spec/models/project_services/slack_service_spec.rb
@@ -46,7 +46,7 @@ describe SlackService do
let(:channel) { 'slack_channel' }
before do
- slack.stub(
+ allow(slack).to receive_messages(
project: project,
project_id: project.id,
service_hook: true,
@@ -96,7 +96,7 @@ describe SlackService do
end
it 'should use the username as an option for slack when configured' do
- slack.stub(username: username)
+ allow(slack).to receive(:username).and_return(username)
expect(Slack::Notifier).to receive(:new).
with(webhook_url, username: username).
and_return(
@@ -106,7 +106,7 @@ describe SlackService do
end
it 'should use the channel as an option when it is configured' do
- slack.stub(channel: channel)
+ allow(slack).to receive(:channel).and_return(channel)
expect(Slack::Notifier).to receive(:new).
with(webhook_url, channel: channel).
and_return(
@@ -130,11 +130,11 @@ describe SlackService do
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
+ allow(slack).to receive_messages(
+ project: project,
+ project_id: project.id,
+ service_hook: true,
+ webhook: webhook_url
)
WebMock.stub_request(:post, webhook_url)
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 87c67fa32c3..63091e913ff 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -127,7 +127,7 @@ describe Project do
describe 'last_activity' do
it 'should alias last_activity to last_event' do
- project.stub(last_event: last_event)
+ allow(project).to receive(:last_event).and_return(last_event)
expect(project.last_activity).to eq(last_event)
end
end
diff --git a/spec/models/project_team_spec.rb b/spec/models/project_team_spec.rb
index 19201cc15a7..d125166e336 100644
--- a/spec/models/project_team_spec.rb
+++ b/spec/models/project_team_spec.rb
@@ -67,4 +67,3 @@ describe ProjectTeam do
end
end
end
-
diff --git a/spec/models/project_wiki_spec.rb b/spec/models/project_wiki_spec.rb
index 2acdb7dfddc..f785203af7d 100644
--- a/spec/models/project_wiki_spec.rb
+++ b/spec/models/project_wiki_spec.rb
@@ -231,7 +231,7 @@ describe ProjectWiki do
end
def commit_details
- commit = {name: user.name, email: user.email, message: "test commit"}
+ commit = { name: user.name, email: user.email, message: "test commit" }
end
def create_page(name, content)
diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb
index f41e5a97ca3..d25351b0f0e 100644
--- a/spec/models/repository_spec.rb
+++ b/spec/models/repository_spec.rb
@@ -25,4 +25,50 @@ describe Repository do
it { is_expected.to eq('c1acaa58bbcbc3eafe538cb8274ba387047b69f8') }
end
+
+ describe :blob_at do
+ context 'blank sha' do
+ subject { repository.blob_at(Gitlab::Git::BLANK_SHA, '.gitignore') }
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe :can_be_merged? do
+ context 'mergeable branches' do
+ subject { repository.can_be_merged?('feature', 'master') }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'non-mergeable branches' do
+ subject { repository.can_be_merged?('feature_conflict', 'feature') }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe "search_files" do
+ let(:results) { repository.search_files('feature', 'master') }
+ subject { results }
+
+ it { is_expected.to be_an Array }
+
+ describe 'result' do
+ subject { results.first }
+
+ it { is_expected.to be_an String }
+ it { expect(subject.lines[2]).to eq("master:CHANGELOG:188: - Feature: Replace teams with group membership\n") }
+ end
+
+ describe 'parsing result' do
+ subject { repository.parse_search_result(results.first) }
+
+ it { is_expected.to be_an OpenStruct }
+ it { expect(subject.filename).to eq('CHANGELOG') }
+ it { expect(subject.ref).to eq('master') }
+ it { expect(subject.startline).to eq(186) }
+ it { expect(subject.data.lines[2]).to eq(" - Feature: Replace teams with group membership\n") }
+ end
+ end
end
diff --git a/spec/models/service_spec.rb b/spec/models/service_spec.rb
index 5d4827ce92a..ca11758ee06 100644
--- a/spec/models/service_spec.rb
+++ b/spec/models/service_spec.rb
@@ -36,12 +36,10 @@ describe Service do
end
describe "Testable" do
- let (:project) { create :project }
+ let(:project) { create :project }
before do
- @service.stub(
- project: project
- )
+ allow(@service).to receive(:project).and_return(project)
@testable = @service.can_test?
end
@@ -51,12 +49,10 @@ describe Service do
end
describe "With commits" do
- let (:project) { create :project }
+ let(:project) { create :project }
before do
- @service.stub(
- project: project
- )
+ allow(@service).to receive(:project).and_return(project)
@testable = @service.can_test?
end
@@ -68,9 +64,16 @@ describe Service do
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(:service_template) do
+ PushoverService.create(
+ template: true,
+ properties: {
+ device: 'MyDevice',
+ sound: 'mic',
+ priority: 4,
+ api_key: '123456789'
+ })
+ end
let(:project) { create(:project) }
describe 'should be prefilled for projects pushover service' do
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index c786d0bf103..81581838675 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -40,7 +40,6 @@ describe Snippet do
it { is_expected.to validate_presence_of(:title) }
it { is_expected.to validate_length_of(:title).is_within(0..255) }
- it { is_expected.to validate_presence_of(:file_name) }
it { is_expected.to validate_length_of(:file_name).is_within(0..255) }
it { is_expected.to validate_presence_of(:content) }
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index f3e278e5c5f..16902317f10 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -50,12 +50,12 @@
# bitbucket_access_token :string(255)
# bitbucket_access_token_secret :string(255)
# location :string(255)
-# public_email :string(255) default(""), not null
# encrypted_otp_secret :string(255)
# encrypted_otp_secret_iv :string(255)
# encrypted_otp_secret_salt :string(255)
-# otp_required_for_login :boolean
+# otp_required_for_login :boolean default(FALSE), not null
# otp_backup_codes :text
+# public_email :string(255) default(""), not null
# dashboard :integer default(0)
#
@@ -217,6 +217,24 @@ describe User do
end
end
+ describe '#disable_two_factor!' do
+ it 'clears all 2FA-related fields' do
+ user = create(:user, :two_factor)
+
+ expect(user).to be_two_factor_enabled
+ expect(user.encrypted_otp_secret).not_to be_nil
+ expect(user.otp_backup_codes).not_to be_nil
+
+ user.disable_two_factor!
+
+ expect(user).not_to be_two_factor_enabled
+ expect(user.encrypted_otp_secret).to be_nil
+ expect(user.encrypted_otp_secret_iv).to be_nil
+ expect(user.encrypted_otp_secret_salt).to be_nil
+ expect(user.otp_backup_codes).to be_nil
+ end
+ end
+
describe 'projects' do
before do
@user = create :user
@@ -284,18 +302,44 @@ describe User do
end
end
- describe 'filter' do
- before do
- User.delete_all
- @user = create :user
- @admin = create :user, admin: true
- @blocked = create :user, state: :blocked
+ describe '.filter' do
+ let(:user) { double }
+
+ it 'filters by active users by default' do
+ expect(User).to receive(:active).and_return([user])
+
+ expect(User.filter(nil)).to include user
+ end
+
+ it 'filters by admins' do
+ expect(User).to receive(:admins).and_return([user])
+
+ expect(User.filter('admins')).to include user
end
- 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) }
+ it 'filters by blocked' do
+ expect(User).to receive(:blocked).and_return([user])
+
+ expect(User.filter('blocked')).to include user
+ end
+
+ it 'filters by two_factor_disabled' do
+ expect(User).to receive(:without_two_factor).and_return([user])
+
+ expect(User.filter('two_factor_disabled')).to include user
+ end
+
+ it 'filters by two_factor_enabled' do
+ expect(User).to receive(:with_two_factor).and_return([user])
+
+ expect(User.filter('two_factor_enabled')).to include user
+ end
+
+ it 'filters by wop' do
+ expect(User).to receive(:without_projects).and_return([user])
+
+ expect(User.filter('wop')).to include user
+ end
end
describe :not_in_project do
@@ -340,6 +384,25 @@ describe User do
end
end
+ describe '.find_by_any_email' do
+ it 'finds by primary email' do
+ user = create(:user, email: 'foo@example.com')
+
+ expect(User.find_by_any_email(user.email)).to eq user
+ end
+
+ it 'finds by secondary email' do
+ email = create(:email, email: 'foo@example.com')
+ user = email.user
+
+ expect(User.find_by_any_email(email.email)).to eq user
+ end
+
+ it 'returns nil when nothing found' do
+ expect(User.find_by_any_email('')).to be_nil
+ end
+ end
+
describe 'search' do
let(:user1) { create(:user, username: 'James', email: 'james@testing.com') }
let(:user2) { create(:user, username: 'jameson', email: 'jameson@example.com') }
@@ -409,21 +472,25 @@ describe User do
it 'is false when LDAP is disabled' do
# Create a condition which would otherwise cause 'true' to be returned
- user.stub(ldap_user?: true)
+ allow(user).to receive(:ldap_user?).and_return(true)
user.last_credential_check_at = nil
expect(user.requires_ldap_check?).to be_falsey
end
context 'when LDAP is enabled' do
- before { Gitlab.config.ldap.stub(enabled: true) }
+ before do
+ allow(Gitlab.config.ldap).to receive(:enabled).and_return(true)
+ end
it 'is false for non-LDAP users' do
- user.stub(ldap_user?: false)
+ allow(user).to receive(:ldap_user?).and_return(false)
expect(user.requires_ldap_check?).to be_falsey
end
context 'and when the user is an LDAP user' do
- before { user.stub(ldap_user?: true) }
+ before do
+ allow(user).to receive(:ldap_user?).and_return(true)
+ end
it 'is true when the user has never had an LDAP check before' do
user.last_credential_check_at = nil
diff --git a/spec/models/wiki_page_spec.rb b/spec/models/wiki_page_spec.rb
index fceb7668cac..dc84a14bb40 100644
--- a/spec/models/wiki_page_spec.rb
+++ b/spec/models/wiki_page_spec.rb
@@ -43,7 +43,7 @@ describe WikiPage do
describe "validations" do
before do
- subject.attributes = {title: 'title', content: 'content'}
+ subject.attributes = { title: 'title', content: 'content' }
end
it "validates presence of title" do
@@ -58,7 +58,7 @@ describe WikiPage do
end
before do
- @wiki_attr = {title: "Index", content: "Home Page", format: "markdown"}
+ @wiki_attr = { title: "Index", content: "Home Page", format: "markdown" }
end
describe "#create" do
@@ -82,7 +82,7 @@ describe WikiPage do
let(:title) { 'Index v1.2.3' }
before do
- @wiki_attr = {title: title, content: "Home Page", format: "markdown"}
+ @wiki_attr = { title: title, content: "Home Page", format: "markdown" }
end
describe "#create" do
@@ -196,7 +196,7 @@ describe WikiPage do
end
def commit_details
- commit = {name: user.name, email: user.email, message: "test commit"}
+ commit = { name: user.name, email: user.email, message: "test commit" }
end
def create_page(name, content)
diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb
new file mode 100644
index 00000000000..671fd6c8666
--- /dev/null
+++ b/spec/rails_helper.rb
@@ -0,0 +1 @@
+require "spec_helper"
diff --git a/spec/requests/api/api_helpers_spec.rb b/spec/requests/api/api_helpers_spec.rb
index 20cb30a39bb..4048c297013 100644
--- a/spec/requests/api/api_helpers_spec.rb
+++ b/spec/requests/api/api_helpers_spec.rb
@@ -47,7 +47,7 @@ describe API, api: true do
it "should return nil for a user without access" do
env[API::APIHelpers::PRIVATE_TOKEN_HEADER] = user.private_token
- Gitlab::UserAccess.stub(allowed?: false)
+ allow(Gitlab::UserAccess).to receive(:allowed?).and_return(false)
expect(current_user).to be_nil
end
@@ -72,13 +72,13 @@ describe API, api: true do
it "should throw an error when the current user is not an admin and attempting to sudo" do
set_env(user, admin.id)
- expect { current_user }.to raise_error
+ expect { current_user }.to raise_error(Exception)
set_param(user, admin.id)
- expect { current_user }.to raise_error
+ expect { current_user }.to raise_error(Exception)
set_env(user, admin.username)
- expect { current_user }.to raise_error
+ expect { current_user }.to raise_error(Exception)
set_param(user, admin.username)
- expect { current_user }.to raise_error
+ expect { current_user }.to raise_error(Exception)
end
it "should throw an error when the user cannot be found for a given id" do
@@ -86,10 +86,10 @@ describe API, api: true do
expect(user.id).not_to eq(id)
expect(admin.id).not_to eq(id)
set_env(admin, id)
- expect { current_user }.to raise_error
+ expect { current_user }.to raise_error(Exception)
set_param(admin, id)
- expect { current_user }.to raise_error
+ expect { current_user }.to raise_error(Exception)
end
it "should throw an error when the user cannot be found for a given username" do
@@ -97,10 +97,10 @@ describe API, api: true do
expect(user.username).not_to eq(username)
expect(admin.username).not_to eq(username)
set_env(admin, username)
- expect { current_user }.to raise_error
+ expect { current_user }.to raise_error(Exception)
set_param(admin, username)
- expect { current_user }.to raise_error
+ expect { current_user }.to raise_error(Exception)
end
it "should handle sudo's to oneself" do
diff --git a/spec/requests/api/branches_spec.rb b/spec/requests/api/branches_spec.rb
index f40d68b75a4..cb6e5e89625 100644
--- a/spec/requests/api/branches_spec.rb
+++ b/spec/requests/api/branches_spec.rb
@@ -141,7 +141,9 @@ describe API::API, api: true do
end
describe "DELETE /projects/:id/repository/branches/:branch" do
- before { Repository.any_instance.stub(rm_branch: true) }
+ before do
+ allow_any_instance_of(Repository).to receive(:rm_branch).and_return(true)
+ end
it "should remove branch" do
delete api("/projects/#{project.id}/repository/branches/#{branch_name}", user)
diff --git a/spec/requests/api/doorkeeper_access_spec.rb b/spec/requests/api/doorkeeper_access_spec.rb
index 39949a90422..0afc3e79339 100644
--- a/spec/requests/api/doorkeeper_access_spec.rb
+++ b/spec/requests/api/doorkeeper_access_spec.rb
@@ -4,20 +4,20 @@ 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 }
+ 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
+ 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"
+ get api("/user"), access_token: "123a"
expect(response.status).to eq(401)
end
end
diff --git a/spec/requests/api/files_spec.rb b/spec/requests/api/files_spec.rb
index 15f547e128d..6c7860511e8 100644
--- a/spec/requests/api/files_spec.rb
+++ b/spec/requests/api/files_spec.rb
@@ -39,14 +39,14 @@ describe API::API, api: true do
end
describe "POST /projects/:id/repository/files" do
- let(:valid_params) {
+ let(:valid_params) do
{
file_path: 'newfile.rb',
branch_name: 'master',
content: 'puts 8',
commit_message: 'Added newfile'
}
- }
+ end
it "should create a new file in project repo" do
post api("/projects/#{project.id}/repository/files", user), valid_params
@@ -60,9 +60,8 @@ describe API::API, api: true do
end
it "should return a 400 if editor fails to create file" do
- Repository.any_instance.stub(
- commit_file: false,
- )
+ allow_any_instance_of(Repository).to receive(:commit_file).
+ and_return(false)
post api("/projects/#{project.id}/repository/files", user), valid_params
expect(response.status).to eq(400)
@@ -70,14 +69,14 @@ describe API::API, api: true do
end
describe "PUT /projects/:id/repository/files" do
- let(:valid_params) {
+ let(:valid_params) do
{
file_path: file_path,
branch_name: 'master',
content: 'puts 8',
commit_message: 'Changed file'
}
- }
+ end
it "should update existing file in project repo" do
put api("/projects/#{project.id}/repository/files", user), valid_params
@@ -92,13 +91,13 @@ describe API::API, api: true do
end
describe "DELETE /projects/:id/repository/files" do
- let(:valid_params) {
+ let(:valid_params) do
{
file_path: file_path,
branch_name: 'master',
commit_message: 'Changed file'
}
- }
+ end
it "should delete existing file in project repo" do
delete api("/projects/#{project.id}/repository/files", user), valid_params
@@ -112,9 +111,7 @@ describe API::API, api: true do
end
it "should return a 400 if satellite fails to create file" do
- Repository.any_instance.stub(
- remove_file: false,
- )
+ allow_any_instance_of(Repository).to receive(:remove_file).and_return(false)
delete api("/projects/#{project.id}/repository/files", user), valid_params
expect(response.status).to eq(400)
diff --git a/spec/requests/api/fork_spec.rb b/spec/requests/api/fork_spec.rb
index 7a784796031..3fe7efff5ba 100644
--- a/spec/requests/api/fork_spec.rb
+++ b/spec/requests/api/fork_spec.rb
@@ -6,15 +6,14 @@ describe API::API, api: true do
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)
- }
+
+ let(:project) do
+ create(:project, creator_id: user.id, namespace: user.namespace)
+ end
+
+ let(:project_user2) do
+ create(:project_member, user: user2, project: project, access_level: ProjectMember::GUEST)
+ end
describe 'POST /projects/fork/:id' do
before { project_user2 }
diff --git a/spec/requests/api/group_members_spec.rb b/spec/requests/api/group_members_spec.rb
index 8ba6876a95b..dd5baa44cb2 100644
--- a/spec/requests/api/group_members_spec.rb
+++ b/spec/requests/api/group_members_spec.rb
@@ -61,10 +61,9 @@ describe API::API, api: true 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 do
+ post api("/groups/#{group_no_members.id}/members", owner), user_id: new_user.id, access_level: GroupMember::MASTER
+ end.to change { group_no_members.members.count }.by(1)
expect(response.status).to eq(201)
expect(json_response['name']).to eq(new_user.name)
@@ -74,10 +73,9 @@ describe API::API, api: true do
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 do
+ post api("/groups/#{group_with_members.id}/members", guest), user_id: new_user.id, access_level: GroupMember::MASTER
+ end.not_to change { group_with_members.members.count }
expect(response.status).to eq(403)
end
@@ -178,9 +176,9 @@ describe API::API, api: true do
context "when a member of the group" do
it "should delete guest's membership of group" do
- expect {
+ expect do
delete api("/groups/#{group_with_members.id}/members/#{guest.id}", owner)
- }.to change { group_with_members.members.count }.by(-1)
+ end.to change { group_with_members.members.count }.by(-1)
expect(response.status).to eq(200)
end
diff --git a/spec/requests/api/groups_spec.rb b/spec/requests/api/groups_spec.rb
index 62b42d63fc2..c5a4ac7e4c4 100644
--- a/spec/requests/api/groups_spec.rb
+++ b/spec/requests/api/groups_spec.rb
@@ -109,18 +109,18 @@ describe API::API, api: true do
end
it "should not create group, duplicate" do
- post api("/groups", user3), {name: 'Duplicate Test', path: group2.path}
+ post api("/groups", user3), { name: 'Duplicate Test', path: group2.path }
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", user3), {path: group2.path}
+ post api("/groups", user3), { path: group2.path }
expect(response.status).to eq(400)
end
it "should return 400 bad request error if path not given" do
- post api("/groups", user3), {name: 'test'}
+ post api("/groups", user3), { name: 'test' }
expect(response.status).to eq(400)
end
end
@@ -167,7 +167,8 @@ describe API::API, api: true do
describe "POST /groups/:id/projects/:project_id" do
let(:project) { create(:project) }
before(:each) do
- Projects::TransferService.any_instance.stub(execute: true)
+ allow_any_instance_of(Projects::TransferService).
+ to receive(:execute).and_return(true)
allow(Project).to receive(:find).and_return(project)
end
diff --git a/spec/requests/api/merge_requests_spec.rb b/spec/requests/api/merge_requests_spec.rb
index 38c67bc9971..7030c105b58 100644
--- a/spec/requests/api/merge_requests_spec.rb
+++ b/spec/requests/api/merge_requests_spec.rb
@@ -8,9 +8,10 @@ describe API::API, api: true do
let!(:merge_request_closed) { create(:merge_request, state: "closed", author: user, assignee: user, source_project: project, target_project: project, title: "Closed test") }
let!(:merge_request_merged) { create(:merge_request, state: "merged", author: user, assignee: user, source_project: project, target_project: project, title: "Merged test") }
let!(:note) { create(:note_on_merge_request, author: user, project: project, noteable: merge_request, note: "a comment on a MR") }
- before {
+
+ before do
project.team << [user, :reporters]
- }
+ end
describe "GET /projects/:id/merge_requests" do
context "when unauthenticated" do
@@ -49,9 +50,8 @@ describe API::API, api: true do
get api("/projects/#{project.id}/merge_requests?state=closed", user)
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)
+ expect(json_response.length).to eq(1)
+ expect(json_response.first['title']).to eq(merge_request_closed.title)
end
it "should return an array of merged merge_requests" do
@@ -301,14 +301,20 @@ describe API::API, api: true do
describe "PUT /projects/:id/merge_request/:merge_request_id/merge" do
it "should return merge_request in case of success" do
- MergeRequest.any_instance.stub(can_be_merged?: true, automerge!: true)
+ allow_any_instance_of(MergeRequest).
+ to receive_messages(can_be_merged?: true, automerge!: true)
+
put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
+
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)
+ allow_any_instance_of(MergeRequest).
+ to receive(:can_be_merged?).and_return(false)
+
put api("/projects/#{project.id}/merge_request/#{merge_request.id}/merge", user)
+
expect(response.status).to eq(405)
expect(json_response['message']).to eq('Branch cannot be merged')
end
diff --git a/spec/requests/api/project_hooks_spec.rb b/spec/requests/api/project_hooks_spec.rb
index 81fe68de662..5037575d355 100644
--- a/spec/requests/api/project_hooks_spec.rb
+++ b/spec/requests/api/project_hooks_spec.rb
@@ -61,10 +61,9 @@ describe API::API, 'ProjectHooks', api: true do
describe "POST /projects/:id/hooks" do
it "should add hook to project" do
- expect {
- post api("/projects/#{project.id}/hooks", user),
- url: "http://example.com", issues_events: true
- }.to change {project.hooks.count}.by(1)
+ expect do
+ post api("/projects/#{project.id}/hooks", user), url: "http://example.com", issues_events: true
+ end.to change {project.hooks.count}.by(1)
expect(response.status).to eq(201)
end
@@ -105,9 +104,9 @@ describe API::API, 'ProjectHooks', api: true do
describe "DELETE /projects/:id/hooks/:hook_id" do
it "should delete hook from project" do
- expect {
+ expect do
delete api("/projects/#{project.id}/hooks/#{hook.id}", user)
- }.to change {project.hooks.count}.by(-1)
+ end.to change {project.hooks.count}.by(-1)
expect(response.status).to eq(200)
end
diff --git a/spec/requests/api/project_members_spec.rb b/spec/requests/api/project_members_spec.rb
index 8419a364ed1..6358f6a2a4a 100644
--- a/spec/requests/api/project_members_spec.rb
+++ b/spec/requests/api/project_members_spec.rb
@@ -53,10 +53,9 @@ describe API::API, api: true do
describe "POST /projects/:id/members" do
it "should add user to project team" do
- expect {
- post api("/projects/#{project.id}/members", user), user_id: user2.id,
- access_level: ProjectMember::DEVELOPER
- }.to change { ProjectMember.count }.by(1)
+ expect do
+ post api("/projects/#{project.id}/members", user), user_id: user2.id, access_level: ProjectMember::DEVELOPER
+ end.to change { ProjectMember.count }.by(1)
expect(response.status).to eq(201)
expect(json_response['username']).to eq(user2.username)
@@ -64,12 +63,12 @@ describe API::API, api: true do
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: ProjectMember::DEVELOPER
- expect {
- post api("/projects/#{project.id}/members", user), user_id: user2.id,
- access_level: ProjectMember::DEVELOPER
- }.not_to change { ProjectMember.count }
+ post api("/projects/#{project.id}/members", user),
+ user_id: user2.id,
+ access_level: ProjectMember::DEVELOPER
+ expect do
+ post api("/projects/#{project.id}/members", user), user_id: user2.id, access_level: ProjectMember::DEVELOPER
+ end.not_to change { ProjectMember.count }
expect(response.status).to eq(201)
expect(json_response['username']).to eq(user2.username)
@@ -123,16 +122,16 @@ describe API::API, api: true do
before { project_member2 }
it "should remove user from project team" do
- expect {
+ expect do
delete api("/projects/#{project.id}/members/#{user3.id}", user)
- }.to change { ProjectMember.count }.by(-1)
+ end.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 {
+ expect do
delete api("/projects/#{project.id}/members/#{user3.id}", user)
- }.to_not change { ProjectMember.count }
+ end.to_not change { ProjectMember.count }
end
it "should return 200 if team member already removed" do
@@ -142,9 +141,9 @@ describe API::API, api: true do
end
it "should return 200 OK when the user was not member" do
- expect {
+ expect do
delete api("/projects/#{project.id}/members/1000000", user)
- }.to change { ProjectMember.count }.by(0)
+ end.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)
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index 8fb1509c8b2..5bd8206b890 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -81,7 +81,7 @@ describe API::API, api: true do
end
it 'should return the correct order when sorted by id' do
- get api('/projects', user), { order_by: 'id', sort: 'desc'}
+ 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)
@@ -89,8 +89,8 @@ describe API::API, api: true do
it 'returns projects in the correct order when ci_enabled_first parameter is passed' do
[project, project2, project3].each{ |project| project.build_missing_services }
- project2.gitlab_ci_service.update(active: true, token: "token", project_url: "url")
- get api('/projects', user), { ci_enabled_first: 'true'}
+ project2.gitlab_ci_service.update(active: true, token: "token", project_url: "http://ci.example.com/projects/1")
+ get api('/projects', user), { ci_enabled_first: 'true' }
expect(response.status).to eq(200)
expect(json_response).to be_an Array
expect(json_response.first['id']).to eq(project2.id)
@@ -121,15 +121,13 @@ describe API::API, api: true 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)
+ expect(json_response).to satisfy do |response|
+ response.one? do |entry|
+ entry['name'] == project.name &&
+ entry['owner']['username'] == user.username
+ end
+ end
end
end
end
@@ -138,9 +136,8 @@ describe API::API, api: true 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'
- }.to change {Project.count}.by(0)
+ expect { post api('/projects', user2), name: 'foo' }.
+ to change {Project.count}.by(0)
expect(response.status).to eq(403)
end
end
@@ -158,7 +155,7 @@ describe API::API, api: true do
end
it 'should not create new project without name and return 400' do
- expect { post api('/projects', user) }.to_not change { Project.count }
+ expect { post api('/projects', user) }.not_to change { Project.count }
expect(response.status).to eq(400)
end
@@ -223,9 +220,7 @@ describe API::API, api: true do
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])
- )
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
end
it 'should not allow a non-admin to use a restricted visibility level' do
@@ -257,7 +252,7 @@ describe API::API, api: true do
it 'should respond with 400 on failure and not project' do
expect { post api("/projects/user/#{user.id}", admin) }.
- to_not change { Project.count }
+ not_to change { Project.count }
expect(response.status).to eq(400)
expect(json_response['message']['name']).to eq([
@@ -474,9 +469,9 @@ describe API::API, api: true do
before { snippet }
it 'should delete existing project snippet' do
- expect {
+ expect do
delete api("/projects/#{project.id}/snippets/#{snippet.id}", user)
- }.to change { Snippet.count }.by(-1)
+ end.to change { Snippet.count }.by(-1)
expect(response.status).to eq(200)
end
@@ -548,9 +543,9 @@ describe API::API, api: true do
it 'should create new ssh key' do
key_attrs = attributes_for :key
- expect {
+ expect do
post api("/projects/#{project.id}/keys", user), key_attrs
- }.to change{ project.deploy_keys.count }.by(1)
+ end.to change{ project.deploy_keys.count }.by(1)
end
end
@@ -558,9 +553,9 @@ describe API::API, api: true do
before { deploy_key }
it 'should delete existing key' do
- expect {
+ expect do
delete api("/projects/#{project.id}/keys/#{deploy_key.id}", user)
- }.to change{ project.deploy_keys.count }.by(-1)
+ end.to change{ project.deploy_keys.count }.by(-1)
end
it 'should return 404 Not Found with invalid ID' do
diff --git a/spec/requests/api/services_spec.rb b/spec/requests/api/services_spec.rb
index 51c543578df..6d29a28580a 100644
--- a/spec/requests/api/services_spec.rb
+++ b/spec/requests/api/services_spec.rb
@@ -7,7 +7,7 @@ describe API::API, api: true do
describe "POST /projects/:id/services/gitlab-ci" 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"
+ put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'secrettoken', project_url: "http://ci.example.com/projects/1"
expect(response.status).to eq(200)
end
@@ -17,6 +17,18 @@ describe API::API, api: true do
expect(response.status).to eq(400)
end
+
+ it "should return if the format of token is invalid" do
+ put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'token-with dashes and spaces%', project_url: "http://ci.example.com/projects/1", active: true
+
+ expect(response.status).to eq(404)
+ end
+
+ it "should return if the format of token is invalid" do
+ put api("/projects/#{project.id}/services/gitlab-ci", user), token: 'token-with dashes and spaces%', project_url: "ftp://ci.example/projects/1", active: true
+
+ expect(response.status).to eq(404)
+ end
end
describe "DELETE /projects/:id/services/gitlab-ci" do
diff --git a/spec/requests/api/settings_spec.rb b/spec/requests/api/settings_spec.rb
new file mode 100644
index 00000000000..c815a8e1d73
--- /dev/null
+++ b/spec/requests/api/settings_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+describe API::API, 'Settings', api: true do
+ include ApiHelpers
+
+ let(:user) { create(:user) }
+ let(:admin) { create(:admin) }
+
+
+ describe "GET /application/settings" do
+ it "should return application settings" do
+ get api("/application/settings", admin)
+ expect(response.status).to eq(200)
+ expect(json_response).to be_an Hash
+ expect(json_response['default_projects_limit']).to eq(42)
+ expect(json_response['signin_enabled']).to be_truthy
+ end
+ end
+
+ describe "PUT /application/settings" do
+ it "should update application settings" do
+ put api("/application/settings", admin),
+ default_projects_limit: 3, signin_enabled: false
+ expect(response.status).to eq(200)
+ expect(json_response['default_projects_limit']).to eq(3)
+ expect(json_response['signin_enabled']).to be_falsey
+ end
+ end
+end
diff --git a/spec/requests/api/system_hooks_spec.rb b/spec/requests/api/system_hooks_spec.rb
index a9d86bbce6c..3e676515488 100644
--- a/spec/requests/api/system_hooks_spec.rb
+++ b/spec/requests/api/system_hooks_spec.rb
@@ -36,9 +36,9 @@ describe API::API, api: true do
describe "POST /hooks" do
it "should create new hook" do
- expect {
+ expect do
post api("/hooks", admin), url: 'http://example.com'
- }.to change { SystemHook.count }.by(1)
+ end.to change { SystemHook.count }.by(1)
end
it "should respond with 400 if url not given" do
@@ -47,9 +47,9 @@ describe API::API, api: true do
end
it "should not create new hook without url" do
- expect {
+ expect do
post api("/hooks", admin)
- }.to_not change { SystemHook.count }
+ end.to_not change { SystemHook.count }
end
end
@@ -68,9 +68,9 @@ describe API::API, api: true do
describe "DELETE /hooks/:id" do
it "should delete a hook" do
- expect {
+ expect do
delete api("/hooks/#{hook.id}", admin)
- }.to change { SystemHook.count }.by(-1)
+ end.to change { SystemHook.count }.by(-1)
end
it "should return success if hook id not found" do
diff --git a/spec/requests/api/users_spec.rb b/spec/requests/api/users_spec.rb
index 327f3e6d23c..c4dd1f76cf2 100644
--- a/spec/requests/api/users_spec.rb
+++ b/spec/requests/api/users_spec.rb
@@ -21,9 +21,9 @@ describe API::API, api: true do
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)
+ expect(json_response.detect do |user|
+ user['username'] == username
+ end['username']).to eq(username)
end
end
@@ -35,6 +35,7 @@ describe API::API, api: true do
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'
+ expect(json_response.first.keys).to include 'two_factor_enabled'
end
end
end
@@ -62,9 +63,9 @@ describe API::API, api: true do
before{ admin }
it "should create user" do
- expect {
+ expect do
post api("/users", admin), attributes_for(:user, projects_limit: 3)
- }.to change { User.count }.by(1)
+ end.to change { User.count }.by(1)
end
it "should create user with correct attributes" do
@@ -103,9 +104,9 @@ describe API::API, api: true do
it "should not create user with invalid email" do
post api('/users', admin),
- email: 'invalid email',
- password: 'password',
- name: 'test'
+ email: 'invalid email',
+ password: 'password',
+ name: 'test'
expect(response.status).to eq(400)
end
@@ -131,21 +132,21 @@ describe API::API, api: true do
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
+ 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)'])
+ to eq(['is too short (minimum is 8 characters)'])
expect(json_response['message']['bio']).
- to eq(['is too long (maximum is 255 characters)'])
+ 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'])
+ to eq(['must be greater than or equal to 0'])
expect(json_response['message']['username']).
- to eq([Gitlab::Regex.send(:namespace_regex_message)])
+ to eq([Gitlab::Regex.send(:namespace_regex_message)])
end
it "shouldn't available for non admin users" do
@@ -156,20 +157,20 @@ describe API::API, api: true do
context 'with existing user' do
before do
post api('/users', admin),
- email: 'test@example.com',
- password: 'password',
- username: 'test',
- name: 'foo'
+ email: 'test@example.com',
+ password: 'password',
+ username: 'test',
+ name: 'foo'
end
it 'should return 409 conflict error if user with same email exists' do
- expect {
+ expect do
post api('/users', admin),
- name: 'foo',
- email: 'test@example.com',
- password: 'password',
- username: 'foo'
- }.to change { User.count }.by(0)
+ name: 'foo',
+ email: 'test@example.com',
+ password: 'password',
+ username: 'foo'
+ end.to change { User.count }.by(0)
expect(response.status).to eq(409)
expect(json_response['message']).to eq('Email has already been taken')
end
@@ -177,10 +178,10 @@ describe API::API, api: true do
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'
+ 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')
@@ -203,7 +204,7 @@ describe API::API, api: true do
before { admin }
it "should update user with new bio" do
- put api("/users/#{user.id}", admin), {bio: 'new test bio'}
+ put api("/users/#{user.id}", admin), { bio: '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')
@@ -224,14 +225,14 @@ describe API::API, api: true do
end
it "should update admin status" do
- put api("/users/#{user.id}", admin), {admin: true}
+ put api("/users/#{user.id}", admin), { admin: 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}
+ put api("/users/#{admin_user.id}", admin), { can_create_group: false }
expect(response.status).to eq(200)
expect(json_response['is_admin']).to eq(true)
expect(admin_user.reload.admin).to eq(true)
@@ -239,7 +240,7 @@ describe API::API, api: true do
end
it "should not allow invalid update" do
- put api("/users/#{user.id}", admin), {email: 'invalid email'}
+ put api("/users/#{user.id}", admin), { email: 'invalid email' }
expect(response.status).to eq(400)
expect(user.reload.email).not_to eq('invalid email')
end
@@ -250,36 +251,36 @@ describe API::API, api: true do
end
it "should return 404 for non-existing user" do
- put api("/users/999999", admin), {bio: 'update should fail'}
+ put api("/users/999999", admin), { bio: 'update should fail' }
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
+ 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)'])
+ to eq(['is too short (minimum is 8 characters)'])
expect(json_response['message']['bio']).
- to eq(['is too long (maximum is 255 characters)'])
+ 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'])
+ to eq(['must be greater than or equal to 0'])
expect(json_response['message']['username']).
- to eq([Gitlab::Regex.send(:namespace_regex_message)])
+ to eq([Gitlab::Regex.send(:namespace_regex_message)])
end
context "with existing user" do
- before {
+ before do
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 = User.all.last
- }
+ end
it 'should return 409 conflict error if email address exists' do
put api("/users/#{@user.id}", admin), email: 'test@example.com'
@@ -313,9 +314,9 @@ describe API::API, api: true do
it "should create ssh key" do
key_attrs = attributes_for :key
- expect {
+ expect do
post api("/users/#{user.id}/keys", admin), key_attrs
- }.to change{ user.keys.count }.by(1)
+ end.to change{ user.keys.count }.by(1)
end
end
@@ -361,9 +362,9 @@ describe API::API, api: true do
it 'should delete existing key' do
user.keys << key
user.save
- expect {
+ expect do
delete api("/users/#{user.id}/keys/#{key.id}", admin)
- }.to change { user.keys.count }.by(-1)
+ end.to change { user.keys.count }.by(-1)
expect(response.status).to eq(200)
end
@@ -475,9 +476,9 @@ describe API::API, api: true do
describe "POST /user/keys" do
it "should create ssh key" do
key_attrs = attributes_for :key
- expect {
+ expect do
post api("/user/keys", user), key_attrs
- }.to change{ user.keys.count }.by(1)
+ end.to change{ user.keys.count }.by(1)
expect(response.status).to eq(201)
end
@@ -508,9 +509,9 @@ describe API::API, api: true do
it "should delete existed key" do
user.keys << key
user.save
- expect {
+ expect do
delete api("/user/keys/#{key.id}", user)
- }.to change{user.keys.count}.by(-1)
+ end.to change{user.keys.count}.by(-1)
expect(response.status).to eq(200)
end
@@ -526,4 +527,55 @@ describe API::API, api: true do
expect(response.status).to eq(401)
end
end
+
+ describe 'PUT /user/:id/block' do
+ before { admin }
+ it 'should block existing user' do
+ put api("/users/#{user.id}/block", admin)
+ expect(response.status).to eq(200)
+ expect(user.reload.state).to eq('blocked')
+ end
+
+ it 'should not be available for non admin users' do
+ put api("/users/#{user.id}/block", user)
+ expect(response.status).to eq(403)
+ expect(user.reload.state).to eq('active')
+ end
+
+ it 'should return a 404 error if user id not found' do
+ put api('/users/9999/block', admin)
+ expect(response.status).to eq(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+ end
+
+ describe 'PUT /user/:id/unblock' do
+ before { admin }
+ it 'should unblock existing user' do
+ put api("/users/#{user.id}/unblock", admin)
+ expect(response.status).to eq(200)
+ expect(user.reload.state).to eq('active')
+ end
+
+ it 'should unblock a blocked user' do
+ put api("/users/#{user.id}/block", admin)
+ expect(response.status).to eq(200)
+ expect(user.reload.state).to eq('blocked')
+ put api("/users/#{user.id}/unblock", admin)
+ expect(response.status).to eq(200)
+ expect(user.reload.state).to eq('active')
+ end
+
+ it 'should not be available for non admin users' do
+ put api("/users/#{user.id}/unblock", user)
+ expect(response.status).to eq(403)
+ expect(user.reload.state).to eq('active')
+ end
+
+ it 'should return a 404 error if user id not found' do
+ put api('/users/9999/block', admin)
+ expect(response.status).to eq(404)
+ expect(json_response['message']).to eq('404 User Not Found')
+ end
+ end
end
diff --git a/spec/routing/admin_routing_spec.rb b/spec/routing/admin_routing_spec.rb
index bf8abcfb00f..cd16a8e6322 100644
--- a/spec/routing/admin_routing_spec.rb
+++ b/spec/routing/admin_routing_spec.rb
@@ -118,4 +118,3 @@ describe Admin::DashboardController, "routing" do
expect(get("/admin")).to route_to('admin/dashboard#index')
end
end
-
diff --git a/spec/routing/routing_spec.rb b/spec/routing/routing_spec.rb
index f268e4755d1..dd045826692 100644
--- a/spec/routing/routing_spec.rb
+++ b/spec/routing/routing_spec.rb
@@ -108,8 +108,8 @@ describe ProfilesController, "routing" do
expect(get("/profile/account")).to route_to('profiles/accounts#show')
end
- it "to #history" do
- expect(get("/profile/history")).to route_to('profiles#history')
+ it "to #audit_log" do
+ expect(get("/profile/audit_log")).to route_to('profiles#audit_log')
end
it "to #reset_private_token" do
@@ -248,4 +248,3 @@ describe "Groups", "routing" do
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
index f168a913976..0ec70c51b3a 100644
--- a/spec/services/archive_repository_service_spec.rb
+++ b/spec/services/archive_repository_service_spec.rb
@@ -17,9 +17,7 @@ describe ArchiveRepositoryService do
end
it "raises an error" do
- expect {
- subject.execute(timeout: 0.0)
- }.to raise_error
+ expect { subject.execute(timeout: 0.0) }.to raise_error(RuntimeError)
end
end
@@ -90,4 +88,3 @@ describe ArchiveRepositoryService do
end
end
end
-
diff --git a/spec/services/create_snippet_service_spec.rb b/spec/services/create_snippet_service_spec.rb
index 08689c15ca8..8edabe9450b 100644
--- a/spec/services/create_snippet_service_spec.rb
+++ b/spec/services/create_snippet_service_spec.rb
@@ -14,11 +14,7 @@ describe CreateSnippetService do
context 'When public visibility is restricted' do
before do
- allow_any_instance_of(ApplicationSetting).to(
- receive(:restricted_visibility_levels).and_return(
- [Gitlab::VisibilityLevel::PUBLIC]
- )
- )
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
@opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
end
diff --git a/spec/services/git_push_service_spec.rb b/spec/services/git_push_service_spec.rb
index d0941fa2e07..62cef9db534 100644
--- a/spec/services/git_push_service_spec.rb
+++ b/spec/services/git_push_service_spec.rb
@@ -3,9 +3,9 @@ require 'spec_helper'
describe GitPushService do
include RepoHelpers
- let (:user) { create :user }
- let (:project) { create :project }
- let (:service) { GitPushService.new }
+ let(:user) { create :user }
+ let(:project) { create :project }
+ let(:service) { GitPushService.new }
before do
@blankrev = Gitlab::Git::BLANK_SHA
@@ -124,7 +124,7 @@ describe GitPushService do
end
it "when pushing a branch for the first time with default branch protection disabled" do
- ApplicationSetting.any_instance.stub(default_branch_protection: 0)
+ stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_NONE)
expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master")
@@ -133,7 +133,7 @@ describe GitPushService do
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)
+ stub_application_setting(default_branch_protection: Gitlab::Access::PROTECTION_DEV_CAN_PUSH)
expect(project).to receive(:execute_hooks)
expect(project.default_branch).to eq("master")
@@ -154,32 +154,35 @@ describe GitPushService do
let(:commit) { project.commit }
before do
- commit.stub({
+ allow(commit).to receive_messages(
safe_message: "this commit \n mentions ##{issue.id}",
references: [issue],
author_name: commit_author.name,
author_email: commit_author.email
- })
- project.repository.stub(commits_between: [commit])
+ )
+ allow(project.repository).to receive(:commits_between).and_return([commit])
end
it "creates a note if a pushed commit mentions an issue" do
- expect(Note).to receive(:create_cross_reference_note).with(issue, commit, commit_author)
+ expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, commit_author)
service.execute(project, user, @oldrev, @newrev, @ref)
end
it "only creates a cross-reference note if one doesn't already exist" do
- Note.create_cross_reference_note(issue, commit, user)
+ SystemNoteService.cross_reference(issue, commit, user)
- expect(Note).not_to receive(:create_cross_reference_note).with(issue, commit, commit_author)
+ expect(SystemNoteService).not_to receive(:cross_reference).with(issue, commit, commit_author)
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')
- expect(Note).to receive(:create_cross_reference_note).with(issue, commit, user)
+ allow(commit).to receive_messages(
+ author_name: 'unknown name',
+ author_email: 'unknown@email.com'
+ )
+ expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, user)
service.execute(project, user, @oldrev, @newrev, @ref)
end
@@ -188,7 +191,7 @@ describe GitPushService do
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])
- expect(Note).to receive(:create_cross_reference_note).with(issue, commit, commit_author)
+ expect(SystemNoteService).to receive(:cross_reference).with(issue, commit, commit_author)
service.execute(project, user, @blankrev, @newrev, 'refs/heads/other')
end
@@ -201,14 +204,15 @@ describe GitPushService do
let(:closing_commit) { project.commit }
before do
- closing_commit.stub({
+ allow(closing_commit).to receive_messages(
issue_closing_regex: /^([Cc]loses|[Ff]ixes) #\d+/,
safe_message: "this is some work.\n\ncloses ##{issue.iid}",
author_name: commit_author.name,
author_email: commit_author.email
- })
+ )
- project.repository.stub(commits_between: [closing_commit])
+ allow(project.repository).to receive(:commits_between).
+ and_return([closing_commit])
end
it "closes issues with commit messages" do
@@ -218,18 +222,18 @@ describe GitPushService do
end
it "doesn't create cross-reference notes for a closing reference" do
- expect {
+ expect do
service.execute(project, user, @oldrev, @newrev, @ref)
- }.not_to change { Note.where(project_id: project.id, system: true, commit_id: closing_commit.id).count }
+ end.not_to change { Note.where(project_id: project.id, system: true, commit_id: closing_commit.id).count }
end
it "doesn't close issues when pushed to non-default branches" do
- project.stub(default_branch: 'durf')
+ allow(project).to receive(:default_branch).and_return('durf')
# The push still shouldn't create cross-reference notes.
- expect {
+ expect do
service.execute(project, user, @oldrev, @newrev, 'refs/heads/hurf')
- }.not_to change { Note.where(project_id: project.id, system: true).count }
+ end.not_to change { Note.where(project_id: project.id, system: true).count }
expect(Issue.find(issue.id)).to be_opened
end
@@ -238,9 +242,9 @@ describe GitPushService do
allow(project).to receive(:default_issues_tracker?).and_return(false)
# The push still shouldn't create cross-reference notes.
- expect {
+ expect do
service.execute(project, user, @oldrev, @newrev, 'refs/heads/hurf')
- }.not_to change { Note.where(project_id: project.id, system: true).count }
+ end.not_to change { Note.where(project_id: project.id, system: true).count }
end
end
diff --git a/spec/services/git_tag_push_service_spec.rb b/spec/services/git_tag_push_service_spec.rb
index 76f69b396e0..eed50c7ebac 100644
--- a/spec/services/git_tag_push_service_spec.rb
+++ b/spec/services/git_tag_push_service_spec.rb
@@ -3,9 +3,9 @@ require 'spec_helper'
describe GitTagPushService do
include RepoHelpers
- let (:user) { create :user }
- let (:project) { create :project }
- let (:service) { GitTagPushService.new }
+ let(:user) { create :user }
+ let(:project) { create :project }
+ let(:service) { GitTagPushService.new }
before do
@oldrev = Gitlab::Git::BLANK_SHA
diff --git a/spec/services/issues/bulk_update_service_spec.rb b/spec/services/issues/bulk_update_service_spec.rb
index a97c55011c9..4c62fbafd73 100644
--- a/spec/services/issues/bulk_update_service_spec.rb
+++ b/spec/services/issues/bulk_update_service_spec.rb
@@ -1,9 +1,7 @@
require 'spec_helper'
describe Issues::BulkUpdateService do
- let(:issue) {
- create(:issue, project: @project)
- }
+ let(:issue) { create(:issue, project: @project) }
before do
@user = create :user
@@ -26,14 +24,14 @@ describe Issues::BulkUpdateService do
}
end
- it {
+ it do
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
end
@@ -49,14 +47,14 @@ describe Issues::BulkUpdateService do
}
end
- it {
+ it do
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
end
@@ -70,13 +68,13 @@ describe Issues::BulkUpdateService do
}
end
- it {
+ it do
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)
- }
+ end
it 'allows mass-unassigning' do
@project.issues.first.update_attribute(:assignee, @new_assignee)
@@ -109,13 +107,13 @@ describe Issues::BulkUpdateService do
}
end
- it {
+ it do
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
end
diff --git a/spec/services/merge_requests/refresh_service_spec.rb b/spec/services/merge_requests/refresh_service_spec.rb
index 0f9b65678df..9516e7936d8 100644
--- a/spec/services/merge_requests/refresh_service_spec.rb
+++ b/spec/services/merge_requests/refresh_service_spec.rb
@@ -13,12 +13,14 @@ describe MergeRequests::RefreshService do
@project = create(:project, namespace: group)
@fork_project = Projects::ForkService.new(@project, @user).execute
- @merge_request = create(:merge_request, source_project: @project,
+ @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,
+ @fork_merge_request = create(:merge_request,
+ source_project: @fork_project,
source_branch: 'master',
target_branch: 'feature',
target_project: @project)
diff --git a/spec/services/notes/create_service_spec.rb b/spec/services/notes/create_service_spec.rb
index 0dc3b412783..f2ea0805b2f 100644
--- a/spec/services/notes/create_service_spec.rb
+++ b/spec/services/notes/create_service_spec.rb
@@ -25,4 +25,3 @@ describe Notes::CreateService do
end
end
end
-
diff --git a/spec/services/notification_service_spec.rb b/spec/services/notification_service_spec.rb
index 62a99d15952..253e5823499 100644
--- a/spec/services/notification_service_spec.rb
+++ b/spec/services/notification_service_spec.rb
@@ -58,7 +58,7 @@ describe NotificationService do
end
it 'filters out "mentioned in" notes' do
- mentioned_note = Note.create_cross_reference_note(mentioned_issue, issue, issue.author)
+ mentioned_note = SystemNoteService.cross_reference(mentioned_issue, issue, issue.author)
expect(Notify).not_to receive(:note_issue_email)
notification.new_note(mentioned_note)
@@ -130,7 +130,7 @@ describe NotificationService do
end
it 'filters out "mentioned in" notes' do
- mentioned_note = Note.create_cross_reference_note(mentioned_issue, issue, issue.author)
+ mentioned_note = SystemNoteService.cross_reference(mentioned_issue, issue, issue.author)
expect(Notify).not_to receive(:note_issue_email)
notification.new_note(mentioned_note)
diff --git a/spec/services/projects/create_service_spec.rb b/spec/services/projects/create_service_spec.rb
index 337dae592dd..97b206c9854 100644
--- a/spec/services/projects/create_service_spec.rb
+++ b/spec/services/projects/create_service_spec.rb
@@ -58,9 +58,7 @@ describe Projects::CreateService do
context 'restricted visibility level' do
before do
- allow_any_instance_of(ApplicationSetting).to(
- receive(:restricted_visibility_levels).and_return([20])
- )
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
@opts.merge!(
visibility_level: Gitlab::VisibilityLevel.options['Public']
diff --git a/spec/services/projects/fork_service_spec.rb b/spec/services/projects/fork_service_spec.rb
index f158ac87e2b..439a492cea9 100644
--- a/spec/services/projects/fork_service_spec.rb
+++ b/spec/services/projects/fork_service_spec.rb
@@ -5,8 +5,10 @@ describe Projects::ForkService 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,
+ @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)
diff --git a/spec/services/projects/transfer_service_spec.rb b/spec/services/projects/transfer_service_spec.rb
index 79acba78bda..bb7da33b12e 100644
--- a/spec/services/projects/transfer_service_spec.rb
+++ b/spec/services/projects/transfer_service_spec.rb
@@ -8,7 +8,7 @@ describe Projects::TransferService do
context 'namespace -> namespace' do
before do
group.add_owner(user)
- @result = transfer_project(project, user, new_namespace_id: group.id)
+ @result = transfer_project(project, user, group)
end
it { expect(@result).to be_truthy }
@@ -17,7 +17,7 @@ describe Projects::TransferService do
context 'namespace -> no namespace' do
before do
- @result = transfer_project(project, user, new_namespace_id: nil)
+ @result = transfer_project(project, user, nil)
end
it { expect(@result).to eq false }
@@ -26,14 +26,14 @@ describe Projects::TransferService do
context 'namespace -> not allowed namespace' do
before do
- @result = transfer_project(project, user, new_namespace_id: group.id)
+ @result = transfer_project(project, user, group)
end
it { expect(@result).to eq false }
it { expect(project.namespace).to eq(user.namespace) }
end
- def transfer_project(project, user, params)
- Projects::TransferService.new(project, user, params).execute
+ def transfer_project(project, user, new_namespace)
+ Projects::TransferService.new(project, user).execute(new_namespace)
end
end
diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb
index ea5b8813105..b347fa15f87 100644
--- a/spec/services/projects/update_service_spec.rb
+++ b/spec/services/projects/update_service_spec.rb
@@ -11,7 +11,7 @@ describe Projects::UpdateService do
context 'should be private when updated to private' do
before do
- @created_private = @project.private?
+ @created_private = @project.private?
@opts.merge!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
update_project(@project, @user, @opts)
@@ -47,9 +47,7 @@ describe Projects::UpdateService do
context 'respect configured visibility restrictions setting' do
before(:each) do
- allow_any_instance_of(ApplicationSetting).to(
- receive(:restricted_visibility_levels).and_return([20])
- )
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
end
context 'should be private when updated to private' do
diff --git a/spec/services/projects/upload_service_spec.rb b/spec/services/projects/upload_service_spec.rb
index e5c47015a03..7aa26857649 100644
--- a/spec/services/projects/upload_service_spec.rb
+++ b/spec/services/projects/upload_service_spec.rb
@@ -22,7 +22,7 @@ describe Projects::UploadService do
it { expect(@link_to_file['url']).to match('banana_sample.gif') }
end
- context 'for valid png file' do
+ context 'for valid png file' do
before do
png = fixture_file_upload(Rails.root + 'spec/fixtures/dk.png',
'image/png')
@@ -38,7 +38,7 @@ describe Projects::UploadService do
it { expect(@link_to_file['url']).to match('dk.png') }
end
- context 'for valid jpg file' do
+ 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)
diff --git a/spec/services/system_hooks_service_spec.rb b/spec/services/system_hooks_service_spec.rb
index 199ac996608..48c49e2f717 100644
--- a/spec/services/system_hooks_service_spec.rb
+++ b/spec/services/system_hooks_service_spec.rb
@@ -1,12 +1,12 @@
require 'spec_helper'
describe SystemHooksService do
- let (:user) { create :user }
- let (:project) { create :project }
- let (:project_member) { create :project_member }
- let (:key) { create(:key, user: user) }
- let (:group) { create(:group) }
- let (:group_member) { create(:group_member) }
+ let(:user) { create :user }
+ let(:project) { create :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 { expect(event_data(user, :create)).to include(:event_name, :name, :created_at, :email, :user_id) }
diff --git a/spec/services/test_hook_service_spec.rb b/spec/services/test_hook_service_spec.rb
index d2b505f55a2..226196eedae 100644
--- a/spec/services/test_hook_service_spec.rb
+++ b/spec/services/test_hook_service_spec.rb
@@ -1,9 +1,9 @@
require 'spec_helper'
describe TestHookService do
- let (:user) { create :user }
- let (:project) { create :project }
- let (:hook) { create :project_hook, project: project }
+ let(:user) { create :user }
+ let(:project) { create :project }
+ let(:hook) { create :project_hook, project: project }
describe :execute do
it "should execute successfully" do
diff --git a/spec/services/update_snippet_service_spec.rb b/spec/services/update_snippet_service_spec.rb
index 841ef9bfed1..d7c516e3934 100644
--- a/spec/services/update_snippet_service_spec.rb
+++ b/spec/services/update_snippet_service_spec.rb
@@ -14,11 +14,7 @@ describe UpdateSnippetService do
context 'When public visibility is restricted' do
before do
- allow_any_instance_of(ApplicationSetting).to(
- receive(:restricted_visibility_levels).and_return(
- [Gitlab::VisibilityLevel::PUBLIC]
- )
- )
+ stub_application_setting(restricted_visibility_levels: [Gitlab::VisibilityLevel::PUBLIC])
@snippet = create_snippet(@project, @user, @opts)
@opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 9c8004ab555..d0f1873ee2d 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -1,8 +1,18 @@
+if ENV['SIMPLECOV']
+ require 'simplecov'
+ SimpleCov.start :rails
+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 'shoulda/matchers'
-require 'email_spec'
require 'sidekiq/testing/inline'
# Requires supporting ruby files with custom matchers and macros, etc,
@@ -14,12 +24,12 @@ RSpec.configure do |config|
config.use_instantiated_fixtures = false
config.mock_with :rspec
- config.include LoginHelpers, type: :feature
- config.include LoginHelpers, type: :request
- config.include FactoryGirl::Syntax::Methods
config.include Devise::TestHelpers, type: :controller
-
+ config.include LoginHelpers, type: :feature
+ config.include LoginHelpers, type: :request
+ config.include StubConfiguration
config.include TestEnv
+
config.infer_spec_type_from_file_location!
config.raise_errors_for_deprecations!
diff --git a/spec/support/capybara.rb b/spec/support/capybara.rb
index 3e41aec425a..fed1ab6ee33 100644
--- a/spec/support/capybara.rb
+++ b/spec/support/capybara.rb
@@ -19,36 +19,3 @@ unless ENV['CI'] || ENV['CI_SERVER']
# Keep only the screenshots generated from the last failing test suite
Capybara::Screenshot.prune_strategy = :keep_last_run
end
-
-module CapybaraHelpers
- # Execute a block a certain number of times before considering it a failure
- #
- # The given block is called, and if it raises a `Capybara::ExpectationNotMet`
- # error, we wait `interval` seconds and then try again, until `retries` is
- # met.
- #
- # This allows for better handling of timing-sensitive expectations in a
- # sketchy CI environment, for example.
- #
- # interval - Delay between retries in seconds (default: 0.5)
- # retries - Number of times to execute before failing (default: 5)
- def allowing_for_delay(interval: 0.5, retries: 5)
- tries = 0
-
- begin
- yield
- rescue Capybara::ExpectationNotMet => ex
- if tries <= retries
- tries += 1
- sleep interval
- retry
- else
- raise ex
- end
- end
- end
-end
-
-RSpec.configure do |config|
- config.include CapybaraHelpers, type: :feature
-end
diff --git a/spec/support/capybara_helpers.rb b/spec/support/capybara_helpers.rb
new file mode 100644
index 00000000000..9b5c3065eed
--- /dev/null
+++ b/spec/support/capybara_helpers.rb
@@ -0,0 +1,34 @@
+module CapybaraHelpers
+ # Execute a block a certain number of times before considering it a failure
+ #
+ # The given block is called, and if it raises a `Capybara::ExpectationNotMet`
+ # error, we wait `interval` seconds and then try again, until `retries` is
+ # met.
+ #
+ # This allows for better handling of timing-sensitive expectations in a
+ # sketchy CI environment, for example.
+ #
+ # interval - Delay between retries in seconds (default: 0.5)
+ # retries - Number of times to execute before failing (default: 5)
+ def allowing_for_delay(interval: 0.5, retries: 5)
+ tries = 0
+
+ begin
+ sleep interval
+
+ yield
+ rescue Capybara::ExpectationNotMet => ex
+ if tries <= retries
+ tries += 1
+ sleep interval
+ retry
+ else
+ raise ex
+ end
+ end
+ end
+end
+
+RSpec.configure do |config|
+ config.include CapybaraHelpers, type: :feature
+end
diff --git a/spec/support/coverage.rb b/spec/support/coverage.rb
deleted file mode 100644
index a54bf03380c..00000000000
--- a/spec/support/coverage.rb
+++ /dev/null
@@ -1,8 +0,0 @@
-if ENV['SIMPLECOV']
- require 'simplecov'
-end
-
-if ENV['COVERALLS']
- require 'coveralls'
- Coveralls.wear_merged!
-end
diff --git a/spec/support/db_cleaner.rb b/spec/support/db_cleaner.rb
index cca7652093a..e0dbc9aa84c 100644
--- a/spec/support/db_cleaner.rb
+++ b/spec/support/db_cleaner.rb
@@ -1,21 +1,3 @@
-# RSpec.configure do |config|
-
-# config.around(:each) do |example|
-# DatabaseCleaner.strategy = :transaction
-# DatabaseCleaner.clean_with(:truncation)
-# DatabaseCleaner.cleaning do
-# example.run
-# end
-# end
-
-# config.around(:each, js: true) do |example|
-# DatabaseCleaner.strategy = :truncation
-# DatabaseCleaner.clean_with(:truncation)
-# DatabaseCleaner.cleaning do
-# example.run
-# end
-# end
-# end
RSpec.configure do |config|
config.before(:suite) do
DatabaseCleaner.clean_with(:truncation)
@@ -25,7 +7,7 @@ RSpec.configure do |config|
DatabaseCleaner.strategy = :transaction
end
- config.before(:each, :js => true) do
+ config.before(:each, js: true) do
DatabaseCleaner.strategy = :truncation
end
@@ -36,15 +18,4 @@ 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/factory_girl.rb b/spec/support/factory_girl.rb
new file mode 100644
index 00000000000..eec437fb3aa
--- /dev/null
+++ b/spec/support/factory_girl.rb
@@ -0,0 +1,3 @@
+RSpec.configure do |config|
+ config.include FactoryGirl::Syntax::Methods
+end
diff --git a/spec/support/login_helpers.rb b/spec/support/login_helpers.rb
index 1bd68552012..ffe30a4246c 100644
--- a/spec/support/login_helpers.rb
+++ b/spec/support/login_helpers.rb
@@ -39,4 +39,9 @@ module LoginHelpers
def logout
find(:css, ".fa.fa-sign-out").click
end
+
+ # Logout without JavaScript driver
+ def logout_direct
+ page.driver.submit :delete, '/users/sign_out', {}
+ end
end
diff --git a/spec/support/matchers.rb b/spec/support/matchers.rb
index f8cce2ea5a3..a2f853e3e70 100644
--- a/spec/support/matchers.rb
+++ b/spec/support/matchers.rb
@@ -1,30 +1,43 @@
RSpec::Matchers.define :be_valid_commit do
match do |actual|
- actual != nil
- actual.id == ValidCommit::ID
- actual.message == ValidCommit::MESSAGE
- actual.author_name == ValidCommit::AUTHOR_FULL_NAME
+ actual &&
+ actual.id == ValidCommit::ID &&
+ actual.message == ValidCommit::MESSAGE &&
+ actual.author_name == ValidCommit::AUTHOR_FULL_NAME
end
end
+def emulate_user(user)
+ user = case user
+ when :user then create(:user)
+ when :visitor then nil
+ when :admin then create(:admin)
+ else user
+ end
+ login_with(user) if user
+end
+
RSpec::Matchers.define :be_allowed_for do |user|
match do |url|
- include UrlAccess
- url_allowed?(user, url)
+ emulate_user(user)
+ visit url
+ status_code != 404 && current_path != new_user_session_path
end
end
RSpec::Matchers.define :be_denied_for do |user|
match do |url|
- include UrlAccess
- url_denied?(user, url)
+ emulate_user(user)
+ visit url
+ status_code == 404 || current_path == new_user_session_path
end
end
-RSpec::Matchers.define :be_404_for do |user|
+RSpec::Matchers.define :be_not_found_for do |user|
match do |url|
- include UrlAccess
- url_404?(user, url)
+ emulate_user(user)
+ visit url
+ status_code == 404
end
end
@@ -33,38 +46,12 @@ RSpec::Matchers.define :include_module do |expected|
described_class.included_modules.include?(expected)
end
- failure_message_for_should do
- "expected #{described_class} to include the #{expected} module"
+ description do
+ "includes the #{expected} module"
end
-end
-module UrlAccess
- def url_allowed?(user, url)
- emulate_user(user)
- visit url
- (status_code != 404 && current_path != new_user_session_path)
- end
-
- def url_denied?(user, url)
- emulate_user(user)
- visit url
- (status_code == 404 || current_path == new_user_session_path)
- end
-
- def url_404?(user, url)
- emulate_user(user)
- visit url
- status_code == 404
- end
-
- def emulate_user(user)
- user = case user
- when :user then create(:user)
- when :visitor then nil
- when :admin then create(:admin)
- else user
- end
- login_with(user) if user
+ failure_message do
+ "expected #{described_class} to include the #{expected} module"
end
end
diff --git a/spec/support/mentionable_shared_examples.rb b/spec/support/mentionable_shared_examples.rb
index d29c8a55c82..f0717e61781 100644
--- a/spec/support/mentionable_shared_examples.rb
+++ b/spec/support/mentionable_shared_examples.rb
@@ -80,7 +80,7 @@ shared_examples 'a mentionable' do
ext_issue, ext_mr, ext_commit]
mentioned_objects.each do |referenced|
- expect(Note).to receive(:create_cross_reference_note).
+ expect(SystemNoteService).to receive(:cross_reference).
with(referenced, subject.local_reference, author)
end
@@ -88,7 +88,7 @@ shared_examples 'a mentionable' do
end
it 'detects existing cross-references' do
- Note.create_cross_reference_note(mentioned_issue, subject.local_reference, author)
+ SystemNoteService.cross_reference(mentioned_issue, subject.local_reference, author)
expect(subject).to have_mentioned(mentioned_issue)
expect(subject).not_to have_mentioned(mentioned_mr)
@@ -132,17 +132,17 @@ shared_examples 'an editable mentionable' do
# These three objects were already referenced, and should not receive new
# notes
[mentioned_issue, mentioned_commit, ext_issue].each do |oldref|
- expect(Note).not_to receive(:create_cross_reference_note).
+ expect(SystemNoteService).not_to receive(:cross_reference).
with(oldref, any_args)
end
# These two issues are new and should receive reference notes
new_issues.each do |newref|
- expect(Note).to receive(:create_cross_reference_note).
+ expect(SystemNoteService).to receive(:cross_reference).
with(newref, subject.local_reference, author)
end
set_mentionable_text.call(new_text)
- subject.notice_added_references(project, author)
+ subject.create_new_cross_references!(project, author)
end
end
diff --git a/spec/support/select2_helper.rb b/spec/support/select2_helper.rb
index 691f84f39d4..04d25b5e9e9 100644
--- a/spec/support/select2_helper.rb
+++ b/spec/support/select2_helper.rb
@@ -12,9 +12,9 @@
module Select2Helper
def select2(value, options={})
- raise "Must pass a hash containing 'from'" if not options.is_a?(Hash) or not options.has_key?(:from)
+ raise ArgumentError, 'options must be a Hash' unless options.kind_of?(Hash)
- selector = options[:from]
+ selector = options.fetch(:from)
if options[:multiple]
execute_script("$('#{selector}').select2('val', ['#{value}'], true);")
diff --git a/spec/support/stub_configuration.rb b/spec/support/stub_configuration.rb
new file mode 100644
index 00000000000..e4004ec8f79
--- /dev/null
+++ b/spec/support/stub_configuration.rb
@@ -0,0 +1,40 @@
+module StubConfiguration
+ def stub_application_setting(messages)
+ add_predicates(messages)
+
+ # Stubbing both of these because we're not yet consistent with how we access
+ # current application settings
+ allow_any_instance_of(ApplicationSetting).to receive_messages(messages)
+ allow(Gitlab::CurrentSettings.current_application_settings).
+ to receive_messages(messages)
+ end
+
+ def stub_config_setting(messages)
+ allow(Gitlab.config.gitlab).to receive_messages(messages)
+ end
+
+ def stub_gravatar_setting(messages)
+ allow(Gitlab.config.gravatar).to receive_messages(messages)
+ end
+
+ private
+
+ # Modifies stubbed messages to also stub possible predicate versions
+ #
+ # Examples:
+ #
+ # add_predicates(foo: true)
+ # # => {foo: true, foo?: true}
+ #
+ # add_predicates(signup_enabled?: false)
+ # # => {signup_enabled? false}
+ def add_predicates(messages)
+ # Only modify keys that aren't already predicates
+ keys = messages.keys.map(&:to_s).reject { |k| k.end_with?('?') }
+
+ keys.each do |key|
+ predicate = key + '?'
+ messages[predicate.to_sym] = messages[key.to_sym]
+ end
+ end
+end
diff --git a/spec/support/test_env.rb b/spec/support/test_env.rb
index 6d4a8067910..8bdd6b43cdd 100644
--- a/spec/support/test_env.rb
+++ b/spec/support/test_env.rb
@@ -41,11 +41,13 @@ module TestEnv
end
def disable_mailer
- NotificationService.any_instance.stub(mailer: double.as_null_object)
+ allow_any_instance_of(NotificationService).to receive(:mailer).
+ and_return(double.as_null_object)
end
def enable_mailer
- allow_any_instance_of(NotificationService).to receive(:mailer).and_call_original
+ allow_any_instance_of(NotificationService).to receive(:mailer).
+ and_call_original
end
# Clean /tmp/tests
diff --git a/spec/tasks/gitlab/backup_rake_spec.rb b/spec/tasks/gitlab/backup_rake_spec.rb
index a59f74c2121..cdcfeba8d1f 100644
--- a/spec/tasks/gitlab/backup_rake_spec.rb
+++ b/spec/tasks/gitlab/backup_rake_spec.rb
@@ -23,30 +23,33 @@ describe 'gitlab:app namespace rake task' do
context 'gitlab version' do
before do
- Dir.stub glob: []
- allow(Dir).to receive :chdir
- File.stub exists?: true
- Kernel.stub system: true
- FileUtils.stub cp_r: true
- FileUtils.stub mv: true
- Rake::Task["gitlab:shell:setup"].stub invoke: true
+ allow(Dir).to receive(:glob).and_return([])
+ allow(Dir).to receive(:chdir)
+ allow(File).to receive(:exists?).and_return(true)
+ allow(Kernel).to receive(:system).and_return(true)
+ allow(FileUtils).to receive(:cp_r).and_return(true)
+ allow(FileUtils).to receive(:mv).and_return(true)
+ allow(Rake::Task["gitlab:shell:setup"]).
+ to receive(:invoke).and_return(true)
end
let(:gitlab_version) { Gitlab::VERSION }
it 'should fail on mismatch' do
- YAML.stub load_file: {gitlab_version: "not #{gitlab_version}" }
- expect { run_rake_task('gitlab:backup:restore') }.to(
- raise_error SystemExit
- )
+ allow(YAML).to receive(:load_file).
+ and_return({ gitlab_version: "not #{gitlab_version}" })
+
+ 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}
- 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
+ allow(YAML).to receive(:load_file).
+ and_return({ gitlab_version: gitlab_version })
+ 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') }.not_to raise_error
end
end
@@ -140,13 +143,14 @@ describe 'gitlab:app namespace rake task' do
end
it 'does not invoke repositories restore' do
- Rake::Task["gitlab:shell:setup"].stub invoke: true
+ allow(Rake::Task["gitlab:shell:setup"]).
+ to receive(:invoke).and_return(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
+ expect { run_rake_task('gitlab:backup:restore') }.not_to 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
index 22e746870dc..37feb5e6faf 100644
--- a/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
+++ b/spec/tasks/gitlab/mail_google_schema_whitelisting.rb
@@ -21,7 +21,7 @@ describe 'gitlab:mail_google_schema_whitelisting rake task' do
end
it 'should run the task without errors' do
- expect { run_rake_task }.to_not raise_error
+ expect { run_rake_task }.not_to raise_error
end
end
end
diff --git a/spec/workers/post_receive_spec.rb b/spec/workers/post_receive_spec.rb
index df1a2b84a53..46eae9ab081 100644
--- a/spec/workers/post_receive_spec.rb
+++ b/spec/workers/post_receive_spec.rb
@@ -30,7 +30,7 @@ describe PostReceive do
end
it "asks the project to trigger all hooks" do
- Project.stub(find_with_namespace: project)
+ allow(Project).to receive(:find_with_namespace).and_return(project)
expect(project).to receive(:execute_hooks).twice
expect(project).to receive(:execute_services).twice
expect(project).to receive(:update_merge_requests)
diff --git a/spec/workers/repository_archive_worker_spec.rb b/spec/workers/repository_archive_worker_spec.rb
index c2362058cfd..a914d0ac8dc 100644
--- a/spec/workers/repository_archive_worker_spec.rb
+++ b/spec/workers/repository_archive_worker_spec.rb
@@ -77,4 +77,3 @@ describe RepositoryArchiveWorker do
end
end
end
-
diff --git a/vendor/assets/javascripts/jquery.nicescroll.min.js b/vendor/assets/javascripts/jquery.nicescroll.min.js
new file mode 100644
index 00000000000..5440b6a0da0
--- /dev/null
+++ b/vendor/assets/javascripts/jquery.nicescroll.min.js
@@ -0,0 +1,118 @@
+/* jquery.nicescroll 3.6.0 InuYaksa*2014 MIT http://nicescroll.areaaperta.com */(function(f){"function"===typeof define&&define.amd?define(["jquery"],f):f(jQuery)})(function(f){var y=!1,D=!1,N=0,O=2E3,x=0,H=["webkit","ms","moz","o"],s=window.requestAnimationFrame||!1,t=window.cancelAnimationFrame||!1;if(!s)for(var P in H){var E=H[P];s||(s=window[E+"RequestAnimationFrame"]);t||(t=window[E+"CancelAnimationFrame"]||window[E+"CancelRequestAnimationFrame"])}var v=window.MutationObserver||window.WebKitMutationObserver||!1,I={zindex:"auto",cursoropacitymin:0,cursoropacitymax:1,cursorcolor:"#424242",
+cursorwidth:"5px",cursorborder:"1px solid #fff",cursorborderradius:"5px",scrollspeed:60,mousescrollstep:24,touchbehavior:!1,hwacceleration:!0,usetransition:!0,boxzoom:!1,dblclickzoom:!0,gesturezoom:!0,grabcursorenabled:!0,autohidemode:!0,background:"",iframeautoresize:!0,cursorminheight:32,preservenativescrolling:!0,railoffset:!1,railhoffset:!1,bouncescroll:!0,spacebarenabled:!0,railpadding:{top:0,right:0,left:0,bottom:0},disableoutline:!0,horizrailenabled:!0,railalign:"right",railvalign:"bottom",
+enabletranslate3d:!0,enablemousewheel:!0,enablekeyboard:!0,smoothscroll:!0,sensitiverail:!0,enablemouselockapi:!0,cursorfixedheight:!1,directionlockdeadzone:6,hidecursordelay:400,nativeparentscrolling:!0,enablescrollonselection:!0,overflowx:!0,overflowy:!0,cursordragspeed:.3,rtlmode:"auto",cursordragontouch:!1,oneaxismousemode:"auto",scriptpath:function(){var f=document.getElementsByTagName("script"),f=f[f.length-1].src.split("?")[0];return 0<f.split("/").length?f.split("/").slice(0,-1).join("/")+
+"/":""}(),preventmultitouchscrolling:!0},F=!1,Q=function(){if(F)return F;var f=document.createElement("DIV"),c=f.style,h=navigator.userAgent,m=navigator.platform,d={haspointerlock:"pointerLockElement"in document||"webkitPointerLockElement"in document||"mozPointerLockElement"in document};d.isopera="opera"in window;d.isopera12=d.isopera&&"getUserMedia"in navigator;d.isoperamini="[object OperaMini]"===Object.prototype.toString.call(window.operamini);d.isie="all"in document&&"attachEvent"in f&&!d.isopera;
+d.isieold=d.isie&&!("msInterpolationMode"in c);d.isie7=d.isie&&!d.isieold&&(!("documentMode"in document)||7==document.documentMode);d.isie8=d.isie&&"documentMode"in document&&8==document.documentMode;d.isie9=d.isie&&"performance"in window&&9<=document.documentMode;d.isie10=d.isie&&"performance"in window&&10==document.documentMode;d.isie11="msRequestFullscreen"in f&&11<=document.documentMode;d.isie9mobile=/iemobile.9/i.test(h);d.isie9mobile&&(d.isie9=!1);d.isie7mobile=!d.isie9mobile&&d.isie7&&/iemobile/i.test(h);
+d.ismozilla="MozAppearance"in c;d.iswebkit="WebkitAppearance"in c;d.ischrome="chrome"in window;d.ischrome22=d.ischrome&&d.haspointerlock;d.ischrome26=d.ischrome&&"transition"in c;d.cantouch="ontouchstart"in document.documentElement||"ontouchstart"in window;d.hasmstouch=window.MSPointerEvent||!1;d.hasw3ctouch=window.PointerEvent||!1;d.ismac=/^mac$/i.test(m);d.isios=d.cantouch&&/iphone|ipad|ipod/i.test(m);d.isios4=d.isios&&!("seal"in Object);d.isios7=d.isios&&"webkitHidden"in document;d.isandroid=/android/i.test(h);
+d.haseventlistener="addEventListener"in f;d.trstyle=!1;d.hastransform=!1;d.hastranslate3d=!1;d.transitionstyle=!1;d.hastransition=!1;d.transitionend=!1;m=["transform","msTransform","webkitTransform","MozTransform","OTransform"];for(h=0;h<m.length;h++)if("undefined"!=typeof c[m[h]]){d.trstyle=m[h];break}d.hastransform=!!d.trstyle;d.hastransform&&(c[d.trstyle]="translate3d(1px,2px,3px)",d.hastranslate3d=/translate3d/.test(c[d.trstyle]));d.transitionstyle=!1;d.prefixstyle="";d.transitionend=!1;for(var m=
+"transition webkitTransition msTransition MozTransition OTransition OTransition KhtmlTransition".split(" "),n=" -webkit- -ms- -moz- -o- -o -khtml-".split(" "),p="transitionend webkitTransitionEnd msTransitionEnd transitionend otransitionend oTransitionEnd KhtmlTransitionEnd".split(" "),h=0;h<m.length;h++)if(m[h]in c){d.transitionstyle=m[h];d.prefixstyle=n[h];d.transitionend=p[h];break}d.ischrome26&&(d.prefixstyle=n[1]);d.hastransition=d.transitionstyle;a:{h=["-webkit-grab","-moz-grab","grab"];if(d.ischrome&&
+!d.ischrome22||d.isie)h=[];for(m=0;m<h.length;m++)if(n=h[m],c.cursor=n,c.cursor==n){c=n;break a}c="url(//mail.google.com/mail/images/2/openhand.cur),n-resize"}d.cursorgrabvalue=c;d.hasmousecapture="setCapture"in f;d.hasMutationObserver=!1!==v;return F=d},R=function(k,c){function h(){var b=a.doc.css(e.trstyle);return b&&"matrix"==b.substr(0,6)?b.replace(/^.*\((.*)\)$/g,"$1").replace(/px/g,"").split(/, +/):!1}function m(){var b=a.win;if("zIndex"in b)return b.zIndex();for(;0<b.length&&9!=b[0].nodeType;){var g=
+b.css("zIndex");if(!isNaN(g)&&0!=g)return parseInt(g);b=b.parent()}return!1}function d(b,g,q){g=b.css(g);b=parseFloat(g);return isNaN(b)?(b=w[g]||0,q=3==b?q?a.win.outerHeight()-a.win.innerHeight():a.win.outerWidth()-a.win.innerWidth():1,a.isie8&&b&&(b+=1),q?b:0):b}function n(b,g,q,c){a._bind(b,g,function(a){a=a?a:window.event;var c={original:a,target:a.target||a.srcElement,type:"wheel",deltaMode:"MozMousePixelScroll"==a.type?0:1,deltaX:0,deltaZ:0,preventDefault:function(){a.preventDefault?a.preventDefault():
+a.returnValue=!1;return!1},stopImmediatePropagation:function(){a.stopImmediatePropagation?a.stopImmediatePropagation():a.cancelBubble=!0}};"mousewheel"==g?(c.deltaY=-.025*a.wheelDelta,a.wheelDeltaX&&(c.deltaX=-.025*a.wheelDeltaX)):c.deltaY=a.detail;return q.call(b,c)},c)}function p(b,g,c){var d,e;0==b.deltaMode?(d=-Math.floor(a.opt.mousescrollstep/54*b.deltaX),e=-Math.floor(a.opt.mousescrollstep/54*b.deltaY)):1==b.deltaMode&&(d=-Math.floor(b.deltaX*a.opt.mousescrollstep),e=-Math.floor(b.deltaY*a.opt.mousescrollstep));
+g&&a.opt.oneaxismousemode&&0==d&&e&&(d=e,e=0,c&&(0>d?a.getScrollLeft()>=a.page.maxw:0>=a.getScrollLeft())&&(e=d,d=0));d&&(a.scrollmom&&a.scrollmom.stop(),a.lastdeltax+=d,a.debounced("mousewheelx",function(){var b=a.lastdeltax;a.lastdeltax=0;a.rail.drag||a.doScrollLeftBy(b)},15));if(e){if(a.opt.nativeparentscrolling&&c&&!a.ispage&&!a.zoomactive)if(0>e){if(a.getScrollTop()>=a.page.maxh)return!0}else if(0>=a.getScrollTop())return!0;a.scrollmom&&a.scrollmom.stop();a.lastdeltay+=e;a.debounced("mousewheely",
+function(){var b=a.lastdeltay;a.lastdeltay=0;a.rail.drag||a.doScrollBy(b)},15)}b.stopImmediatePropagation();return b.preventDefault()}var a=this;this.version="3.6.0";this.name="nicescroll";this.me=c;this.opt={doc:f("body"),win:!1};f.extend(this.opt,I);this.opt.snapbackspeed=80;if(k)for(var G in a.opt)"undefined"!=typeof k[G]&&(a.opt[G]=k[G]);this.iddoc=(this.doc=a.opt.doc)&&this.doc[0]?this.doc[0].id||"":"";this.ispage=/^BODY|HTML/.test(a.opt.win?a.opt.win[0].nodeName:this.doc[0].nodeName);this.haswrapper=
+!1!==a.opt.win;this.win=a.opt.win||(this.ispage?f(window):this.doc);this.docscroll=this.ispage&&!this.haswrapper?f(window):this.win;this.body=f("body");this.iframe=this.isfixed=this.viewport=!1;this.isiframe="IFRAME"==this.doc[0].nodeName&&"IFRAME"==this.win[0].nodeName;this.istextarea="TEXTAREA"==this.win[0].nodeName;this.forcescreen=!1;this.canshowonmouseevent="scroll"!=a.opt.autohidemode;this.page=this.view=this.onzoomout=this.onzoomin=this.onscrollcancel=this.onscrollend=this.onscrollstart=this.onclick=
+this.ongesturezoom=this.onkeypress=this.onmousewheel=this.onmousemove=this.onmouseup=this.onmousedown=!1;this.scroll={x:0,y:0};this.scrollratio={x:0,y:0};this.cursorheight=20;this.scrollvaluemax=0;this.isrtlmode="auto"==this.opt.rtlmode?"rtl"==(this.win[0]==window?this.body:this.win).css("direction"):!0===this.opt.rtlmode;this.observerbody=this.observerremover=this.observer=this.scrollmom=this.scrollrunning=!1;do this.id="ascrail"+O++;while(document.getElementById(this.id));this.hasmousefocus=this.hasfocus=
+this.zoomactive=this.zoom=this.selectiondrag=this.cursorfreezed=this.cursor=this.rail=!1;this.visibility=!0;this.hidden=this.locked=this.railslocked=!1;this.cursoractive=!0;this.wheelprevented=!1;this.overflowx=a.opt.overflowx;this.overflowy=a.opt.overflowy;this.nativescrollingarea=!1;this.checkarea=0;this.events=[];this.saved={};this.delaylist={};this.synclist={};this.lastdeltay=this.lastdeltax=0;this.detected=Q();var e=f.extend({},this.detected);this.ishwscroll=(this.canhwscroll=e.hastransform&&
+a.opt.hwacceleration)&&a.haswrapper;this.hasreversehr=this.isrtlmode&&!e.iswebkit;this.istouchcapable=!1;!e.cantouch||e.isios||e.isandroid||!e.iswebkit&&!e.ismozilla||(this.istouchcapable=!0,e.cantouch=!1);a.opt.enablemouselockapi||(e.hasmousecapture=!1,e.haspointerlock=!1);this.debounced=function(b,g,c){var d=a.delaylist[b];a.delaylist[b]=g;d||setTimeout(function(){var g=a.delaylist[b];a.delaylist[b]=!1;g.call(a)},c)};var r=!1;this.synched=function(b,g){a.synclist[b]=g;(function(){r||(s(function(){r=
+!1;for(var b in a.synclist){var g=a.synclist[b];g&&g.call(a);a.synclist[b]=!1}}),r=!0)})();return b};this.unsynched=function(b){a.synclist[b]&&(a.synclist[b]=!1)};this.css=function(b,g){for(var c in g)a.saved.css.push([b,c,b.css(c)]),b.css(c,g[c])};this.scrollTop=function(b){return"undefined"==typeof b?a.getScrollTop():a.setScrollTop(b)};this.scrollLeft=function(b){return"undefined"==typeof b?a.getScrollLeft():a.setScrollLeft(b)};var A=function(a,g,c,d,e,f,h){this.st=a;this.ed=g;this.spd=c;this.p1=
+d||0;this.p2=e||1;this.p3=f||0;this.p4=h||1;this.ts=(new Date).getTime();this.df=this.ed-this.st};A.prototype={B2:function(a){return 3*a*a*(1-a)},B3:function(a){return 3*a*(1-a)*(1-a)},B4:function(a){return(1-a)*(1-a)*(1-a)},getNow:function(){var a=1-((new Date).getTime()-this.ts)/this.spd,g=this.B2(a)+this.B3(a)+this.B4(a);return 0>a?this.ed:this.st+Math.round(this.df*g)},update:function(a,g){this.st=this.getNow();this.ed=a;this.spd=g;this.ts=(new Date).getTime();this.df=this.ed-this.st;return this}};
+if(this.ishwscroll){this.doc.translate={x:0,y:0,tx:"0px",ty:"0px"};e.hastranslate3d&&e.isios&&this.doc.css("-webkit-backface-visibility","hidden");this.getScrollTop=function(b){if(!b){if(b=h())return 16==b.length?-b[13]:-b[5];if(a.timerscroll&&a.timerscroll.bz)return a.timerscroll.bz.getNow()}return a.doc.translate.y};this.getScrollLeft=function(b){if(!b){if(b=h())return 16==b.length?-b[12]:-b[4];if(a.timerscroll&&a.timerscroll.bh)return a.timerscroll.bh.getNow()}return a.doc.translate.x};this.notifyScrollEvent=
+function(a){var g=document.createEvent("UIEvents");g.initUIEvent("scroll",!1,!0,window,1);g.niceevent=!0;a.dispatchEvent(g)};var K=this.isrtlmode?1:-1;e.hastranslate3d&&a.opt.enabletranslate3d?(this.setScrollTop=function(b,g){a.doc.translate.y=b;a.doc.translate.ty=-1*b+"px";a.doc.css(e.trstyle,"translate3d("+a.doc.translate.tx+","+a.doc.translate.ty+",0px)");g||a.notifyScrollEvent(a.win[0])},this.setScrollLeft=function(b,g){a.doc.translate.x=b;a.doc.translate.tx=b*K+"px";a.doc.css(e.trstyle,"translate3d("+
+a.doc.translate.tx+","+a.doc.translate.ty+",0px)");g||a.notifyScrollEvent(a.win[0])}):(this.setScrollTop=function(b,g){a.doc.translate.y=b;a.doc.translate.ty=-1*b+"px";a.doc.css(e.trstyle,"translate("+a.doc.translate.tx+","+a.doc.translate.ty+")");g||a.notifyScrollEvent(a.win[0])},this.setScrollLeft=function(b,g){a.doc.translate.x=b;a.doc.translate.tx=b*K+"px";a.doc.css(e.trstyle,"translate("+a.doc.translate.tx+","+a.doc.translate.ty+")");g||a.notifyScrollEvent(a.win[0])})}else this.getScrollTop=
+function(){return a.docscroll.scrollTop()},this.setScrollTop=function(b){return a.docscroll.scrollTop(b)},this.getScrollLeft=function(){return a.detected.ismozilla&&a.isrtlmode?Math.abs(a.docscroll.scrollLeft()):a.docscroll.scrollLeft()},this.setScrollLeft=function(b){return a.docscroll.scrollLeft(a.detected.ismozilla&&a.isrtlmode?-b:b)};this.getTarget=function(a){return a?a.target?a.target:a.srcElement?a.srcElement:!1:!1};this.hasParent=function(a,g){if(!a)return!1;for(var c=a.target||a.srcElement||
+a||!1;c&&c.id!=g;)c=c.parentNode||!1;return!1!==c};var w={thin:1,medium:3,thick:5};this.getDocumentScrollOffset=function(){return{top:window.pageYOffset||document.documentElement.scrollTop,left:window.pageXOffset||document.documentElement.scrollLeft}};this.getOffset=function(){if(a.isfixed){var b=a.win.offset(),g=a.getDocumentScrollOffset();b.top-=g.top;b.left-=g.left;return b}b=a.win.offset();if(!a.viewport)return b;g=a.viewport.offset();return{top:b.top-g.top,left:b.left-g.left}};this.updateScrollBar=
+function(b){if(a.ishwscroll)a.rail.css({height:a.win.innerHeight()-(a.opt.railpadding.top+a.opt.railpadding.bottom)}),a.railh&&a.railh.css({width:a.win.innerWidth()-(a.opt.railpadding.left+a.opt.railpadding.right)});else{var g=a.getOffset(),c=g.top,e=g.left-(a.opt.railpadding.left+a.opt.railpadding.right),c=c+d(a.win,"border-top-width",!0),e=e+(a.rail.align?a.win.outerWidth()-d(a.win,"border-right-width")-a.rail.width:d(a.win,"border-left-width")),f=a.opt.railoffset;f&&(f.top&&(c+=f.top),a.rail.align&&
+f.left&&(e+=f.left));a.railslocked||a.rail.css({top:c,left:e,height:(b?b.h:a.win.innerHeight())-(a.opt.railpadding.top+a.opt.railpadding.bottom)});a.zoom&&a.zoom.css({top:c+1,left:1==a.rail.align?e-20:e+a.rail.width+4});if(a.railh&&!a.railslocked){c=g.top;e=g.left;if(f=a.opt.railhoffset)f.top&&(c+=f.top),f.left&&(e+=f.left);b=a.railh.align?c+d(a.win,"border-top-width",!0)+a.win.innerHeight()-a.railh.height:c+d(a.win,"border-top-width",!0);e+=d(a.win,"border-left-width");a.railh.css({top:b-(a.opt.railpadding.top+
+a.opt.railpadding.bottom),left:e,width:a.railh.width})}}};this.doRailClick=function(b,g,c){var e;a.railslocked||(a.cancelEvent(b),g?(g=c?a.doScrollLeft:a.doScrollTop,e=c?(b.pageX-a.railh.offset().left-a.cursorwidth/2)*a.scrollratio.x:(b.pageY-a.rail.offset().top-a.cursorheight/2)*a.scrollratio.y,g(e)):(g=c?a.doScrollLeftBy:a.doScrollBy,e=c?a.scroll.x:a.scroll.y,b=c?b.pageX-a.railh.offset().left:b.pageY-a.rail.offset().top,c=c?a.view.w:a.view.h,g(e>=b?c:-c)))};a.hasanimationframe=s;a.hascancelanimationframe=
+t;a.hasanimationframe?a.hascancelanimationframe||(t=function(){a.cancelAnimationFrame=!0}):(s=function(a){return setTimeout(a,15-Math.floor(+new Date/1E3)%16)},t=clearInterval);this.init=function(){a.saved.css=[];if(e.isie7mobile||e.isoperamini)return!0;e.hasmstouch&&a.css(a.ispage?f("html"):a.win,{"-ms-touch-action":"none"});a.zindex="auto";a.zindex=a.ispage||"auto"!=a.opt.zindex?a.opt.zindex:m()||"auto";!a.ispage&&"auto"!=a.zindex&&a.zindex>x&&(x=a.zindex);a.isie&&0==a.zindex&&"auto"==a.opt.zindex&&
+(a.zindex="auto");if(!a.ispage||!e.cantouch&&!e.isieold&&!e.isie9mobile){var b=a.docscroll;a.ispage&&(b=a.haswrapper?a.win:a.doc);e.isie9mobile||a.css(b,{"overflow-y":"hidden"});a.ispage&&e.isie7&&("BODY"==a.doc[0].nodeName?a.css(f("html"),{"overflow-y":"hidden"}):"HTML"==a.doc[0].nodeName&&a.css(f("body"),{"overflow-y":"hidden"}));!e.isios||a.ispage||a.haswrapper||a.css(f("body"),{"-webkit-overflow-scrolling":"touch"});var g=f(document.createElement("div"));g.css({position:"relative",top:0,"float":"right",
+width:a.opt.cursorwidth,height:"0px","background-color":a.opt.cursorcolor,border:a.opt.cursorborder,"background-clip":"padding-box","-webkit-border-radius":a.opt.cursorborderradius,"-moz-border-radius":a.opt.cursorborderradius,"border-radius":a.opt.cursorborderradius});g.hborder=parseFloat(g.outerHeight()-g.innerHeight());g.addClass("nicescroll-cursors");a.cursor=g;var c=f(document.createElement("div"));c.attr("id",a.id);c.addClass("nicescroll-rails nicescroll-rails-vr");var d,h,k=["left","right",
+"top","bottom"],J;for(J in k)h=k[J],(d=a.opt.railpadding[h])?c.css("padding-"+h,d+"px"):a.opt.railpadding[h]=0;c.append(g);c.width=Math.max(parseFloat(a.opt.cursorwidth),g.outerWidth());c.css({width:c.width+"px",zIndex:a.zindex,background:a.opt.background,cursor:"default"});c.visibility=!0;c.scrollable=!0;c.align="left"==a.opt.railalign?0:1;a.rail=c;g=a.rail.drag=!1;!a.opt.boxzoom||a.ispage||e.isieold||(g=document.createElement("div"),a.bind(g,"click",a.doZoom),a.bind(g,"mouseenter",function(){a.zoom.css("opacity",
+a.opt.cursoropacitymax)}),a.bind(g,"mouseleave",function(){a.zoom.css("opacity",a.opt.cursoropacitymin)}),a.zoom=f(g),a.zoom.css({cursor:"pointer","z-index":a.zindex,backgroundImage:"url("+a.opt.scriptpath+"zoomico.png)",height:18,width:18,backgroundPosition:"0px 0px"}),a.opt.dblclickzoom&&a.bind(a.win,"dblclick",a.doZoom),e.cantouch&&a.opt.gesturezoom&&(a.ongesturezoom=function(b){1.5<b.scale&&a.doZoomIn(b);.8>b.scale&&a.doZoomOut(b);return a.cancelEvent(b)},a.bind(a.win,"gestureend",a.ongesturezoom)));
+a.railh=!1;var l;a.opt.horizrailenabled&&(a.css(b,{"overflow-x":"hidden"}),g=f(document.createElement("div")),g.css({position:"absolute",top:0,height:a.opt.cursorwidth,width:"0px","background-color":a.opt.cursorcolor,border:a.opt.cursorborder,"background-clip":"padding-box","-webkit-border-radius":a.opt.cursorborderradius,"-moz-border-radius":a.opt.cursorborderradius,"border-radius":a.opt.cursorborderradius}),e.isieold&&g.css({overflow:"hidden"}),g.wborder=parseFloat(g.outerWidth()-g.innerWidth()),
+g.addClass("nicescroll-cursors"),a.cursorh=g,l=f(document.createElement("div")),l.attr("id",a.id+"-hr"),l.addClass("nicescroll-rails nicescroll-rails-hr"),l.height=Math.max(parseFloat(a.opt.cursorwidth),g.outerHeight()),l.css({height:l.height+"px",zIndex:a.zindex,background:a.opt.background}),l.append(g),l.visibility=!0,l.scrollable=!0,l.align="top"==a.opt.railvalign?0:1,a.railh=l,a.railh.drag=!1);a.ispage?(c.css({position:"fixed",top:"0px",height:"100%"}),c.align?c.css({right:"0px"}):c.css({left:"0px"}),
+a.body.append(c),a.railh&&(l.css({position:"fixed",left:"0px",width:"100%"}),l.align?l.css({bottom:"0px"}):l.css({top:"0px"}),a.body.append(l))):(a.ishwscroll?("static"==a.win.css("position")&&a.css(a.win,{position:"relative"}),b="HTML"==a.win[0].nodeName?a.body:a.win,f(b).scrollTop(0).scrollLeft(0),a.zoom&&(a.zoom.css({position:"absolute",top:1,right:0,"margin-right":c.width+4}),b.append(a.zoom)),c.css({position:"absolute",top:0}),c.align?c.css({right:0}):c.css({left:0}),b.append(c),l&&(l.css({position:"absolute",
+left:0,bottom:0}),l.align?l.css({bottom:0}):l.css({top:0}),b.append(l))):(a.isfixed="fixed"==a.win.css("position"),b=a.isfixed?"fixed":"absolute",a.isfixed||(a.viewport=a.getViewport(a.win[0])),a.viewport&&(a.body=a.viewport,0==/fixed|absolute/.test(a.viewport.css("position"))&&a.css(a.viewport,{position:"relative"})),c.css({position:b}),a.zoom&&a.zoom.css({position:b}),a.updateScrollBar(),a.body.append(c),a.zoom&&a.body.append(a.zoom),a.railh&&(l.css({position:b}),a.body.append(l))),e.isios&&a.css(a.win,
+{"-webkit-tap-highlight-color":"rgba(0,0,0,0)","-webkit-touch-callout":"none"}),e.isie&&a.opt.disableoutline&&a.win.attr("hideFocus","true"),e.iswebkit&&a.opt.disableoutline&&a.win.css({outline:"none"}));!1===a.opt.autohidemode?(a.autohidedom=!1,a.rail.css({opacity:a.opt.cursoropacitymax}),a.railh&&a.railh.css({opacity:a.opt.cursoropacitymax})):!0===a.opt.autohidemode||"leave"===a.opt.autohidemode?(a.autohidedom=f().add(a.rail),e.isie8&&(a.autohidedom=a.autohidedom.add(a.cursor)),a.railh&&(a.autohidedom=
+a.autohidedom.add(a.railh)),a.railh&&e.isie8&&(a.autohidedom=a.autohidedom.add(a.cursorh))):"scroll"==a.opt.autohidemode?(a.autohidedom=f().add(a.rail),a.railh&&(a.autohidedom=a.autohidedom.add(a.railh))):"cursor"==a.opt.autohidemode?(a.autohidedom=f().add(a.cursor),a.railh&&(a.autohidedom=a.autohidedom.add(a.cursorh))):"hidden"==a.opt.autohidemode&&(a.autohidedom=!1,a.hide(),a.railslocked=!1);if(e.isie9mobile)a.scrollmom=new L(a),a.onmangotouch=function(){var b=a.getScrollTop(),c=a.getScrollLeft();
+if(b==a.scrollmom.lastscrolly&&c==a.scrollmom.lastscrollx)return!0;var g=b-a.mangotouch.sy,e=c-a.mangotouch.sx;if(0!=Math.round(Math.sqrt(Math.pow(e,2)+Math.pow(g,2)))){var d=0>g?-1:1,f=0>e?-1:1,q=+new Date;a.mangotouch.lazy&&clearTimeout(a.mangotouch.lazy);80<q-a.mangotouch.tm||a.mangotouch.dry!=d||a.mangotouch.drx!=f?(a.scrollmom.stop(),a.scrollmom.reset(c,b),a.mangotouch.sy=b,a.mangotouch.ly=b,a.mangotouch.sx=c,a.mangotouch.lx=c,a.mangotouch.dry=d,a.mangotouch.drx=f,a.mangotouch.tm=q):(a.scrollmom.stop(),
+a.scrollmom.update(a.mangotouch.sx-e,a.mangotouch.sy-g),a.mangotouch.tm=q,g=Math.max(Math.abs(a.mangotouch.ly-b),Math.abs(a.mangotouch.lx-c)),a.mangotouch.ly=b,a.mangotouch.lx=c,2<g&&(a.mangotouch.lazy=setTimeout(function(){a.mangotouch.lazy=!1;a.mangotouch.dry=0;a.mangotouch.drx=0;a.mangotouch.tm=0;a.scrollmom.doMomentum(30)},100)))}},c=a.getScrollTop(),l=a.getScrollLeft(),a.mangotouch={sy:c,ly:c,dry:0,sx:l,lx:l,drx:0,lazy:!1,tm:0},a.bind(a.docscroll,"scroll",a.onmangotouch);else{if(e.cantouch||
+a.istouchcapable||a.opt.touchbehavior||e.hasmstouch){a.scrollmom=new L(a);a.ontouchstart=function(b){if(b.pointerType&&2!=b.pointerType&&"touch"!=b.pointerType)return!1;a.hasmoving=!1;if(!a.railslocked){var c;if(e.hasmstouch)for(c=b.target?b.target:!1;c;){var g=f(c).getNiceScroll();if(0<g.length&&g[0].me==a.me)break;if(0<g.length)return!1;if("DIV"==c.nodeName&&c.id==a.id)break;c=c.parentNode?c.parentNode:!1}a.cancelScroll();if((c=a.getTarget(b))&&/INPUT/i.test(c.nodeName)&&/range/i.test(c.type))return a.stopPropagation(b);
+!("clientX"in b)&&"changedTouches"in b&&(b.clientX=b.changedTouches[0].clientX,b.clientY=b.changedTouches[0].clientY);a.forcescreen&&(g=b,b={original:b.original?b.original:b},b.clientX=g.screenX,b.clientY=g.screenY);a.rail.drag={x:b.clientX,y:b.clientY,sx:a.scroll.x,sy:a.scroll.y,st:a.getScrollTop(),sl:a.getScrollLeft(),pt:2,dl:!1};if(a.ispage||!a.opt.directionlockdeadzone)a.rail.drag.dl="f";else{var g=f(window).width(),d=f(window).height(),q=Math.max(document.body.scrollWidth,document.documentElement.scrollWidth),
+h=Math.max(document.body.scrollHeight,document.documentElement.scrollHeight),d=Math.max(0,h-d),g=Math.max(0,q-g);a.rail.drag.ck=!a.rail.scrollable&&a.railh.scrollable?0<d?"v":!1:a.rail.scrollable&&!a.railh.scrollable?0<g?"h":!1:!1;a.rail.drag.ck||(a.rail.drag.dl="f")}a.opt.touchbehavior&&a.isiframe&&e.isie&&(g=a.win.position(),a.rail.drag.x+=g.left,a.rail.drag.y+=g.top);a.hasmoving=!1;a.lastmouseup=!1;a.scrollmom.reset(b.clientX,b.clientY);if(!e.cantouch&&!this.istouchcapable&&!b.pointerType){if(!c||
+!/INPUT|SELECT|TEXTAREA/i.test(c.nodeName))return!a.ispage&&e.hasmousecapture&&c.setCapture(),a.opt.touchbehavior?(c.onclick&&!c._onclick&&(c._onclick=c.onclick,c.onclick=function(b){if(a.hasmoving)return!1;c._onclick.call(this,b)}),a.cancelEvent(b)):a.stopPropagation(b);/SUBMIT|CANCEL|BUTTON/i.test(f(c).attr("type"))&&(pc={tg:c,click:!1},a.preventclick=pc)}}};a.ontouchend=function(b){if(!a.rail.drag)return!0;if(2==a.rail.drag.pt){if(b.pointerType&&2!=b.pointerType&&"touch"!=b.pointerType)return!1;
+a.scrollmom.doMomentum();a.rail.drag=!1;if(a.hasmoving&&(a.lastmouseup=!0,a.hideCursor(),e.hasmousecapture&&document.releaseCapture(),!e.cantouch))return a.cancelEvent(b)}else if(1==a.rail.drag.pt)return a.onmouseup(b)};var n=a.opt.touchbehavior&&a.isiframe&&!e.hasmousecapture;a.ontouchmove=function(b,c){if(!a.rail.drag||b.targetTouches&&a.opt.preventmultitouchscrolling&&1<b.targetTouches.length||b.pointerType&&2!=b.pointerType&&"touch"!=b.pointerType)return!1;if(2==a.rail.drag.pt){if(e.cantouch&&
+e.isios&&"undefined"==typeof b.original)return!0;a.hasmoving=!0;a.preventclick&&!a.preventclick.click&&(a.preventclick.click=a.preventclick.tg.onclick||!1,a.preventclick.tg.onclick=a.onpreventclick);b=f.extend({original:b},b);"changedTouches"in b&&(b.clientX=b.changedTouches[0].clientX,b.clientY=b.changedTouches[0].clientY);if(a.forcescreen){var g=b;b={original:b.original?b.original:b};b.clientX=g.screenX;b.clientY=g.screenY}var d,g=d=0;n&&!c&&(d=a.win.position(),g=-d.left,d=-d.top);var q=b.clientY+
+d;d=q-a.rail.drag.y;var h=b.clientX+g,u=h-a.rail.drag.x,k=a.rail.drag.st-d;a.ishwscroll&&a.opt.bouncescroll?0>k?k=Math.round(k/2):k>a.page.maxh&&(k=a.page.maxh+Math.round((k-a.page.maxh)/2)):(0>k&&(q=k=0),k>a.page.maxh&&(k=a.page.maxh,q=0));var l;a.railh&&a.railh.scrollable&&(l=a.isrtlmode?u-a.rail.drag.sl:a.rail.drag.sl-u,a.ishwscroll&&a.opt.bouncescroll?0>l?l=Math.round(l/2):l>a.page.maxw&&(l=a.page.maxw+Math.round((l-a.page.maxw)/2)):(0>l&&(h=l=0),l>a.page.maxw&&(l=a.page.maxw,h=0)));g=!1;if(a.rail.drag.dl)g=
+!0,"v"==a.rail.drag.dl?l=a.rail.drag.sl:"h"==a.rail.drag.dl&&(k=a.rail.drag.st);else{d=Math.abs(d);var u=Math.abs(u),z=a.opt.directionlockdeadzone;if("v"==a.rail.drag.ck){if(d>z&&u<=.3*d)return a.rail.drag=!1,!0;u>z&&(a.rail.drag.dl="f",f("body").scrollTop(f("body").scrollTop()))}else if("h"==a.rail.drag.ck){if(u>z&&d<=.3*u)return a.rail.drag=!1,!0;d>z&&(a.rail.drag.dl="f",f("body").scrollLeft(f("body").scrollLeft()))}}a.synched("touchmove",function(){a.rail.drag&&2==a.rail.drag.pt&&(a.prepareTransition&&
+a.prepareTransition(0),a.rail.scrollable&&a.setScrollTop(k),a.scrollmom.update(h,q),a.railh&&a.railh.scrollable?(a.setScrollLeft(l),a.showCursor(k,l)):a.showCursor(k),e.isie10&&document.selection.clear())});e.ischrome&&a.istouchcapable&&(g=!1);if(g)return a.cancelEvent(b)}else if(1==a.rail.drag.pt)return a.onmousemove(b)}}a.onmousedown=function(b,c){if(!a.rail.drag||1==a.rail.drag.pt){if(a.railslocked)return a.cancelEvent(b);a.cancelScroll();a.rail.drag={x:b.clientX,y:b.clientY,sx:a.scroll.x,sy:a.scroll.y,
+pt:1,hr:!!c};var g=a.getTarget(b);!a.ispage&&e.hasmousecapture&&g.setCapture();a.isiframe&&!e.hasmousecapture&&(a.saved.csspointerevents=a.doc.css("pointer-events"),a.css(a.doc,{"pointer-events":"none"}));a.hasmoving=!1;return a.cancelEvent(b)}};a.onmouseup=function(b){if(a.rail.drag){if(1!=a.rail.drag.pt)return!0;e.hasmousecapture&&document.releaseCapture();a.isiframe&&!e.hasmousecapture&&a.doc.css("pointer-events",a.saved.csspointerevents);a.rail.drag=!1;a.hasmoving&&a.triggerScrollEnd();return a.cancelEvent(b)}};
+a.onmousemove=function(b){if(a.rail.drag&&1==a.rail.drag.pt){if(e.ischrome&&0==b.which)return a.onmouseup(b);a.cursorfreezed=!0;a.hasmoving=!0;if(a.rail.drag.hr){a.scroll.x=a.rail.drag.sx+(b.clientX-a.rail.drag.x);0>a.scroll.x&&(a.scroll.x=0);var c=a.scrollvaluemaxw;a.scroll.x>c&&(a.scroll.x=c)}else a.scroll.y=a.rail.drag.sy+(b.clientY-a.rail.drag.y),0>a.scroll.y&&(a.scroll.y=0),c=a.scrollvaluemax,a.scroll.y>c&&(a.scroll.y=c);a.synched("mousemove",function(){a.rail.drag&&1==a.rail.drag.pt&&(a.showCursor(),
+a.rail.drag.hr?a.hasreversehr?a.doScrollLeft(a.scrollvaluemaxw-Math.round(a.scroll.x*a.scrollratio.x),a.opt.cursordragspeed):a.doScrollLeft(Math.round(a.scroll.x*a.scrollratio.x),a.opt.cursordragspeed):a.doScrollTop(Math.round(a.scroll.y*a.scrollratio.y),a.opt.cursordragspeed))});return a.cancelEvent(b)}};if(e.cantouch||a.opt.touchbehavior)a.onpreventclick=function(b){if(a.preventclick)return a.preventclick.tg.onclick=a.preventclick.click,a.preventclick=!1,a.cancelEvent(b)},a.bind(a.win,"mousedown",
+a.ontouchstart),a.onclick=e.isios?!1:function(b){return a.lastmouseup?(a.lastmouseup=!1,a.cancelEvent(b)):!0},a.opt.grabcursorenabled&&e.cursorgrabvalue&&(a.css(a.ispage?a.doc:a.win,{cursor:e.cursorgrabvalue}),a.css(a.rail,{cursor:e.cursorgrabvalue}));else{var p=function(b){if(a.selectiondrag){if(b){var c=a.win.outerHeight();b=b.pageY-a.selectiondrag.top;0<b&&b<c&&(b=0);b>=c&&(b-=c);a.selectiondrag.df=b}0!=a.selectiondrag.df&&(a.doScrollBy(2*-Math.floor(a.selectiondrag.df/6)),a.debounced("doselectionscroll",
+function(){p()},50))}};a.hasTextSelected="getSelection"in document?function(){return 0<document.getSelection().rangeCount}:"selection"in document?function(){return"None"!=document.selection.type}:function(){return!1};a.onselectionstart=function(b){a.ispage||(a.selectiondrag=a.win.offset())};a.onselectionend=function(b){a.selectiondrag=!1};a.onselectiondrag=function(b){a.selectiondrag&&a.hasTextSelected()&&a.debounced("selectionscroll",function(){p(b)},250)}}e.hasw3ctouch?(a.css(a.rail,{"touch-action":"none"}),
+a.css(a.cursor,{"touch-action":"none"}),a.bind(a.win,"pointerdown",a.ontouchstart),a.bind(document,"pointerup",a.ontouchend),a.bind(document,"pointermove",a.ontouchmove)):e.hasmstouch?(a.css(a.rail,{"-ms-touch-action":"none"}),a.css(a.cursor,{"-ms-touch-action":"none"}),a.bind(a.win,"MSPointerDown",a.ontouchstart),a.bind(document,"MSPointerUp",a.ontouchend),a.bind(document,"MSPointerMove",a.ontouchmove),a.bind(a.cursor,"MSGestureHold",function(a){a.preventDefault()}),a.bind(a.cursor,"contextmenu",
+function(a){a.preventDefault()})):this.istouchcapable&&(a.bind(a.win,"touchstart",a.ontouchstart),a.bind(document,"touchend",a.ontouchend),a.bind(document,"touchcancel",a.ontouchend),a.bind(document,"touchmove",a.ontouchmove));if(a.opt.cursordragontouch||!e.cantouch&&!a.opt.touchbehavior)a.rail.css({cursor:"default"}),a.railh&&a.railh.css({cursor:"default"}),a.jqbind(a.rail,"mouseenter",function(){if(!a.ispage&&!a.win.is(":visible"))return!1;a.canshowonmouseevent&&a.showCursor();a.rail.active=!0}),
+a.jqbind(a.rail,"mouseleave",function(){a.rail.active=!1;a.rail.drag||a.hideCursor()}),a.opt.sensitiverail&&(a.bind(a.rail,"click",function(b){a.doRailClick(b,!1,!1)}),a.bind(a.rail,"dblclick",function(b){a.doRailClick(b,!0,!1)}),a.bind(a.cursor,"click",function(b){a.cancelEvent(b)}),a.bind(a.cursor,"dblclick",function(b){a.cancelEvent(b)})),a.railh&&(a.jqbind(a.railh,"mouseenter",function(){if(!a.ispage&&!a.win.is(":visible"))return!1;a.canshowonmouseevent&&a.showCursor();a.rail.active=!0}),a.jqbind(a.railh,
+"mouseleave",function(){a.rail.active=!1;a.rail.drag||a.hideCursor()}),a.opt.sensitiverail&&(a.bind(a.railh,"click",function(b){a.doRailClick(b,!1,!0)}),a.bind(a.railh,"dblclick",function(b){a.doRailClick(b,!0,!0)}),a.bind(a.cursorh,"click",function(b){a.cancelEvent(b)}),a.bind(a.cursorh,"dblclick",function(b){a.cancelEvent(b)})));e.cantouch||a.opt.touchbehavior?(a.bind(e.hasmousecapture?a.win:document,"mouseup",a.ontouchend),a.bind(document,"mousemove",a.ontouchmove),a.onclick&&a.bind(document,"click",
+a.onclick),a.opt.cursordragontouch&&(a.bind(a.cursor,"mousedown",a.onmousedown),a.bind(a.cursor,"mouseup",a.onmouseup),a.cursorh&&a.bind(a.cursorh,"mousedown",function(b){a.onmousedown(b,!0)}),a.cursorh&&a.bind(a.cursorh,"mouseup",a.onmouseup))):(a.bind(e.hasmousecapture?a.win:document,"mouseup",a.onmouseup),a.bind(document,"mousemove",a.onmousemove),a.onclick&&a.bind(document,"click",a.onclick),a.bind(a.cursor,"mousedown",a.onmousedown),a.bind(a.cursor,"mouseup",a.onmouseup),a.railh&&(a.bind(a.cursorh,
+"mousedown",function(b){a.onmousedown(b,!0)}),a.bind(a.cursorh,"mouseup",a.onmouseup)),!a.ispage&&a.opt.enablescrollonselection&&(a.bind(a.win[0],"mousedown",a.onselectionstart),a.bind(document,"mouseup",a.onselectionend),a.bind(a.cursor,"mouseup",a.onselectionend),a.cursorh&&a.bind(a.cursorh,"mouseup",a.onselectionend),a.bind(document,"mousemove",a.onselectiondrag)),a.zoom&&(a.jqbind(a.zoom,"mouseenter",function(){a.canshowonmouseevent&&a.showCursor();a.rail.active=!0}),a.jqbind(a.zoom,"mouseleave",
+function(){a.rail.active=!1;a.rail.drag||a.hideCursor()})));a.opt.enablemousewheel&&(a.isiframe||a.bind(e.isie&&a.ispage?document:a.win,"mousewheel",a.onmousewheel),a.bind(a.rail,"mousewheel",a.onmousewheel),a.railh&&a.bind(a.railh,"mousewheel",a.onmousewheelhr));a.ispage||e.cantouch||/HTML|^BODY/.test(a.win[0].nodeName)||(a.win.attr("tabindex")||a.win.attr({tabindex:N++}),a.jqbind(a.win,"focus",function(b){y=a.getTarget(b).id||!0;a.hasfocus=!0;a.canshowonmouseevent&&a.noticeCursor()}),a.jqbind(a.win,
+"blur",function(b){y=!1;a.hasfocus=!1}),a.jqbind(a.win,"mouseenter",function(b){D=a.getTarget(b).id||!0;a.hasmousefocus=!0;a.canshowonmouseevent&&a.noticeCursor()}),a.jqbind(a.win,"mouseleave",function(){D=!1;a.hasmousefocus=!1;a.rail.drag||a.hideCursor()}))}a.onkeypress=function(b){if(a.railslocked&&0==a.page.maxh)return!0;b=b?b:window.e;var c=a.getTarget(b);if(c&&/INPUT|TEXTAREA|SELECT|OPTION/.test(c.nodeName)&&(!c.getAttribute("type")&&!c.type||!/submit|button|cancel/i.tp)||f(c).attr("contenteditable"))return!0;
+if(a.hasfocus||a.hasmousefocus&&!y||a.ispage&&!y&&!D){c=b.keyCode;if(a.railslocked&&27!=c)return a.cancelEvent(b);var g=b.ctrlKey||!1,d=b.shiftKey||!1,e=!1;switch(c){case 38:case 63233:a.doScrollBy(72);e=!0;break;case 40:case 63235:a.doScrollBy(-72);e=!0;break;case 37:case 63232:a.railh&&(g?a.doScrollLeft(0):a.doScrollLeftBy(72),e=!0);break;case 39:case 63234:a.railh&&(g?a.doScrollLeft(a.page.maxw):a.doScrollLeftBy(-72),e=!0);break;case 33:case 63276:a.doScrollBy(a.view.h);e=!0;break;case 34:case 63277:a.doScrollBy(-a.view.h);
+e=!0;break;case 36:case 63273:a.railh&&g?a.doScrollPos(0,0):a.doScrollTo(0);e=!0;break;case 35:case 63275:a.railh&&g?a.doScrollPos(a.page.maxw,a.page.maxh):a.doScrollTo(a.page.maxh);e=!0;break;case 32:a.opt.spacebarenabled&&(d?a.doScrollBy(a.view.h):a.doScrollBy(-a.view.h),e=!0);break;case 27:a.zoomactive&&(a.doZoom(),e=!0)}if(e)return a.cancelEvent(b)}};a.opt.enablekeyboard&&a.bind(document,e.isopera&&!e.isopera12?"keypress":"keydown",a.onkeypress);a.bind(document,"keydown",function(b){b.ctrlKey&&
+(a.wheelprevented=!0)});a.bind(document,"keyup",function(b){b.ctrlKey||(a.wheelprevented=!1)});a.bind(window,"blur",function(b){a.wheelprevented=!1});a.bind(window,"resize",a.lazyResize);a.bind(window,"orientationchange",a.lazyResize);a.bind(window,"load",a.lazyResize);if(e.ischrome&&!a.ispage&&!a.haswrapper){var r=a.win.attr("style"),c=parseFloat(a.win.css("width"))+1;a.win.css("width",c);a.synched("chromefix",function(){a.win.attr("style",r)})}a.onAttributeChange=function(b){a.lazyResize(a.isieold?
+250:30)};!1!==v&&(a.observerbody=new v(function(b){b.forEach(function(b){if("attributes"==b.type)return f("body").hasClass("modal-open")?a.hide():a.show()});if(document.body.scrollHeight!=a.page.maxh)return a.lazyResize(30)}),a.observerbody.observe(document.body,{childList:!0,subtree:!0,characterData:!1,attributes:!0,attributeFilter:["class"]}));a.ispage||a.haswrapper||(!1!==v?(a.observer=new v(function(b){b.forEach(a.onAttributeChange)}),a.observer.observe(a.win[0],{childList:!0,characterData:!1,
+attributes:!0,subtree:!1}),a.observerremover=new v(function(b){b.forEach(function(b){if(0<b.removedNodes.length)for(var c in b.removedNodes)if(a&&b.removedNodes[c]==a.win[0])return a.remove()})}),a.observerremover.observe(a.win[0].parentNode,{childList:!0,characterData:!1,attributes:!1,subtree:!1})):(a.bind(a.win,e.isie&&!e.isie9?"propertychange":"DOMAttrModified",a.onAttributeChange),e.isie9&&a.win[0].attachEvent("onpropertychange",a.onAttributeChange),a.bind(a.win,"DOMNodeRemoved",function(b){b.target==
+a.win[0]&&a.remove()})));!a.ispage&&a.opt.boxzoom&&a.bind(window,"resize",a.resizeZoom);a.istextarea&&a.bind(a.win,"mouseup",a.lazyResize);a.lazyResize(30)}if("IFRAME"==this.doc[0].nodeName){var M=function(){a.iframexd=!1;var b;try{b="contentDocument"in this?this.contentDocument:this.contentWindow.document}catch(c){a.iframexd=!0,b=!1}if(a.iframexd)return"console"in window&&console.log("NiceScroll error: policy restriced iframe"),!0;a.forcescreen=!0;a.isiframe&&(a.iframe={doc:f(b),html:a.doc.contents().find("html")[0],
+body:a.doc.contents().find("body")[0]},a.getContentSize=function(){return{w:Math.max(a.iframe.html.scrollWidth,a.iframe.body.scrollWidth),h:Math.max(a.iframe.html.scrollHeight,a.iframe.body.scrollHeight)}},a.docscroll=f(a.iframe.body));if(!e.isios&&a.opt.iframeautoresize&&!a.isiframe){a.win.scrollTop(0);a.doc.height("");var g=Math.max(b.getElementsByTagName("html")[0].scrollHeight,b.body.scrollHeight);a.doc.height(g)}a.lazyResize(30);e.isie7&&a.css(f(a.iframe.html),{"overflow-y":"hidden"});a.css(f(a.iframe.body),
+{"overflow-y":"hidden"});e.isios&&a.haswrapper&&a.css(f(b.body),{"-webkit-transform":"translate3d(0,0,0)"});"contentWindow"in this?a.bind(this.contentWindow,"scroll",a.onscroll):a.bind(b,"scroll",a.onscroll);a.opt.enablemousewheel&&a.bind(b,"mousewheel",a.onmousewheel);a.opt.enablekeyboard&&a.bind(b,e.isopera?"keypress":"keydown",a.onkeypress);if(e.cantouch||a.opt.touchbehavior)a.bind(b,"mousedown",a.ontouchstart),a.bind(b,"mousemove",function(b){return a.ontouchmove(b,!0)}),a.opt.grabcursorenabled&&
+e.cursorgrabvalue&&a.css(f(b.body),{cursor:e.cursorgrabvalue});a.bind(b,"mouseup",a.ontouchend);a.zoom&&(a.opt.dblclickzoom&&a.bind(b,"dblclick",a.doZoom),a.ongesturezoom&&a.bind(b,"gestureend",a.ongesturezoom))};this.doc[0].readyState&&"complete"==this.doc[0].readyState&&setTimeout(function(){M.call(a.doc[0],!1)},500);a.bind(this.doc,"load",M)}};this.showCursor=function(b,c){a.cursortimeout&&(clearTimeout(a.cursortimeout),a.cursortimeout=0);if(a.rail){a.autohidedom&&(a.autohidedom.stop().css({opacity:a.opt.cursoropacitymax}),
+a.cursoractive=!0);a.rail.drag&&1==a.rail.drag.pt||("undefined"!=typeof b&&!1!==b&&(a.scroll.y=Math.round(1*b/a.scrollratio.y)),"undefined"!=typeof c&&(a.scroll.x=Math.round(1*c/a.scrollratio.x)));a.cursor.css({height:a.cursorheight,top:a.scroll.y});if(a.cursorh){var d=a.hasreversehr?a.scrollvaluemaxw-a.scroll.x:a.scroll.x;!a.rail.align&&a.rail.visibility?a.cursorh.css({width:a.cursorwidth,left:d+a.rail.width}):a.cursorh.css({width:a.cursorwidth,left:d});a.cursoractive=!0}a.zoom&&a.zoom.stop().css({opacity:a.opt.cursoropacitymax})}};
+this.hideCursor=function(b){a.cursortimeout||!a.rail||!a.autohidedom||a.hasmousefocus&&"leave"==a.opt.autohidemode||(a.cursortimeout=setTimeout(function(){a.rail.active&&a.showonmouseevent||(a.autohidedom.stop().animate({opacity:a.opt.cursoropacitymin}),a.zoom&&a.zoom.stop().animate({opacity:a.opt.cursoropacitymin}),a.cursoractive=!1);a.cursortimeout=0},b||a.opt.hidecursordelay))};this.noticeCursor=function(b,c,d){a.showCursor(c,d);a.rail.active||a.hideCursor(b)};this.getContentSize=a.ispage?function(){return{w:Math.max(document.body.scrollWidth,
+document.documentElement.scrollWidth),h:Math.max(document.body.scrollHeight,document.documentElement.scrollHeight)}}:a.haswrapper?function(){return{w:a.doc.outerWidth()+parseInt(a.win.css("paddingLeft"))+parseInt(a.win.css("paddingRight")),h:a.doc.outerHeight()+parseInt(a.win.css("paddingTop"))+parseInt(a.win.css("paddingBottom"))}}:function(){return{w:a.docscroll[0].scrollWidth,h:a.docscroll[0].scrollHeight}};this.onResize=function(b,c){if(!a||!a.win)return!1;if(!a.haswrapper&&!a.ispage){if("none"==
+a.win.css("display"))return a.visibility&&a.hideRail().hideRailHr(),!1;a.hidden||a.visibility||a.showRail().showRailHr()}var d=a.page.maxh,e=a.page.maxw,f=a.view.h,h=a.view.w;a.view={w:a.ispage?a.win.width():parseInt(a.win[0].clientWidth),h:a.ispage?a.win.height():parseInt(a.win[0].clientHeight)};a.page=c?c:a.getContentSize();a.page.maxh=Math.max(0,a.page.h-a.view.h);a.page.maxw=Math.max(0,a.page.w-a.view.w);if(a.page.maxh==d&&a.page.maxw==e&&a.view.w==h&&a.view.h==f){if(a.ispage)return a;d=a.win.offset();
+if(a.lastposition&&(e=a.lastposition,e.top==d.top&&e.left==d.left))return a;a.lastposition=d}0==a.page.maxh?(a.hideRail(),a.scrollvaluemax=0,a.scroll.y=0,a.scrollratio.y=0,a.cursorheight=0,a.setScrollTop(0),a.rail.scrollable=!1):(a.page.maxh-=a.opt.railpadding.top+a.opt.railpadding.bottom,a.rail.scrollable=!0);0==a.page.maxw?(a.hideRailHr(),a.scrollvaluemaxw=0,a.scroll.x=0,a.scrollratio.x=0,a.cursorwidth=0,a.setScrollLeft(0),a.railh.scrollable=!1):(a.page.maxw-=a.opt.railpadding.left+a.opt.railpadding.right,
+a.railh.scrollable=!0);a.railslocked=a.locked||0==a.page.maxh&&0==a.page.maxw;if(a.railslocked)return a.ispage||a.updateScrollBar(a.view),!1;a.hidden||a.visibility?a.hidden||a.railh.visibility||a.showRailHr():a.showRail().showRailHr();a.istextarea&&a.win.css("resize")&&"none"!=a.win.css("resize")&&(a.view.h-=20);a.cursorheight=Math.min(a.view.h,Math.round(a.view.h/a.page.h*a.view.h));a.cursorheight=a.opt.cursorfixedheight?a.opt.cursorfixedheight:Math.max(a.opt.cursorminheight,a.cursorheight);a.cursorwidth=
+Math.min(a.view.w,Math.round(a.view.w/a.page.w*a.view.w));a.cursorwidth=a.opt.cursorfixedheight?a.opt.cursorfixedheight:Math.max(a.opt.cursorminheight,a.cursorwidth);a.scrollvaluemax=a.view.h-a.cursorheight-a.cursor.hborder-(a.opt.railpadding.top+a.opt.railpadding.bottom);a.railh&&(a.railh.width=0<a.page.maxh?a.view.w-a.rail.width:a.view.w,a.scrollvaluemaxw=a.railh.width-a.cursorwidth-a.cursorh.wborder-(a.opt.railpadding.left+a.opt.railpadding.right));a.ispage||a.updateScrollBar(a.view);a.scrollratio=
+{x:a.page.maxw/a.scrollvaluemaxw,y:a.page.maxh/a.scrollvaluemax};a.getScrollTop()>a.page.maxh?a.doScrollTop(a.page.maxh):(a.scroll.y=Math.round(a.getScrollTop()*(1/a.scrollratio.y)),a.scroll.x=Math.round(a.getScrollLeft()*(1/a.scrollratio.x)),a.cursoractive&&a.noticeCursor());a.scroll.y&&0==a.getScrollTop()&&a.doScrollTo(Math.floor(a.scroll.y*a.scrollratio.y));return a};this.resize=a.onResize;this.lazyResize=function(b){b=isNaN(b)?30:b;a.debounced("resize",a.resize,b);return a};this.jqbind=function(b,
+c,d){a.events.push({e:b,n:c,f:d,q:!0});f(b).bind(c,d)};this.bind=function(b,c,d,f){var h="jquery"in b?b[0]:b;"mousewheel"==c?window.addEventListener||"onwheel"in document?a._bind(h,"wheel",d,f||!1):(b="undefined"!=typeof document.onmousewheel?"mousewheel":"DOMMouseScroll",n(h,b,d,f||!1),"DOMMouseScroll"==b&&n(h,"MozMousePixelScroll",d,f||!1)):h.addEventListener?(e.cantouch&&/mouseup|mousedown|mousemove/.test(c)&&a._bind(h,"mousedown"==c?"touchstart":"mouseup"==c?"touchend":"touchmove",function(a){if(a.touches){if(2>
+a.touches.length){var b=a.touches.length?a.touches[0]:a;b.original=a;d.call(this,b)}}else a.changedTouches&&(b=a.changedTouches[0],b.original=a,d.call(this,b))},f||!1),a._bind(h,c,d,f||!1),e.cantouch&&"mouseup"==c&&a._bind(h,"touchcancel",d,f||!1)):a._bind(h,c,function(b){(b=b||window.event||!1)&&b.srcElement&&(b.target=b.srcElement);"pageY"in b||(b.pageX=b.clientX+document.documentElement.scrollLeft,b.pageY=b.clientY+document.documentElement.scrollTop);return!1===d.call(h,b)||!1===f?a.cancelEvent(b):
+!0})};e.haseventlistener?(this._bind=function(b,c,d,e){a.events.push({e:b,n:c,f:d,b:e,q:!1});b.addEventListener(c,d,e||!1)},this.cancelEvent=function(a){if(!a)return!1;a=a.original?a.original:a;a.preventDefault();a.stopPropagation();a.preventManipulation&&a.preventManipulation();return!1},this.stopPropagation=function(a){if(!a)return!1;a=a.original?a.original:a;a.stopPropagation();return!1},this._unbind=function(a,c,d,e){a.removeEventListener(c,d,e)}):(this._bind=function(b,c,d,e){a.events.push({e:b,
+n:c,f:d,b:e,q:!1});b.attachEvent?b.attachEvent("on"+c,d):b["on"+c]=d},this.cancelEvent=function(a){a=window.event||!1;if(!a)return!1;a.cancelBubble=!0;a.cancel=!0;return a.returnValue=!1},this.stopPropagation=function(a){a=window.event||!1;if(!a)return!1;a.cancelBubble=!0;return!1},this._unbind=function(a,c,d,e){a.detachEvent?a.detachEvent("on"+c,d):a["on"+c]=!1});this.unbindAll=function(){for(var b=0;b<a.events.length;b++){var c=a.events[b];c.q?c.e.unbind(c.n,c.f):a._unbind(c.e,c.n,c.f,c.b)}};this.showRail=
+function(){0==a.page.maxh||!a.ispage&&"none"==a.win.css("display")||(a.visibility=!0,a.rail.visibility=!0,a.rail.css("display","block"));return a};this.showRailHr=function(){if(!a.railh)return a;0==a.page.maxw||!a.ispage&&"none"==a.win.css("display")||(a.railh.visibility=!0,a.railh.css("display","block"));return a};this.hideRail=function(){a.visibility=!1;a.rail.visibility=!1;a.rail.css("display","none");return a};this.hideRailHr=function(){if(!a.railh)return a;a.railh.visibility=!1;a.railh.css("display",
+"none");return a};this.show=function(){a.hidden=!1;a.railslocked=!1;return a.showRail().showRailHr()};this.hide=function(){a.hidden=!0;a.railslocked=!0;return a.hideRail().hideRailHr()};this.toggle=function(){return a.hidden?a.show():a.hide()};this.remove=function(){a.stop();a.cursortimeout&&clearTimeout(a.cursortimeout);a.doZoomOut();a.unbindAll();e.isie9&&a.win[0].detachEvent("onpropertychange",a.onAttributeChange);!1!==a.observer&&a.observer.disconnect();!1!==a.observerremover&&a.observerremover.disconnect();
+!1!==a.observerbody&&a.observerbody.disconnect();a.events=null;a.cursor&&a.cursor.remove();a.cursorh&&a.cursorh.remove();a.rail&&a.rail.remove();a.railh&&a.railh.remove();a.zoom&&a.zoom.remove();for(var b=0;b<a.saved.css.length;b++){var c=a.saved.css[b];c[0].css(c[1],"undefined"==typeof c[2]?"":c[2])}a.saved=!1;a.me.data("__nicescroll","");var d=f.nicescroll;d.each(function(b){if(this&&this.id===a.id){delete d[b];for(var c=++b;c<d.length;c++,b++)d[b]=d[c];d.length--;d.length&&delete d[d.length]}});
+for(var h in a)a[h]=null,delete a[h];a=null};this.scrollstart=function(b){this.onscrollstart=b;return a};this.scrollend=function(b){this.onscrollend=b;return a};this.scrollcancel=function(b){this.onscrollcancel=b;return a};this.zoomin=function(b){this.onzoomin=b;return a};this.zoomout=function(b){this.onzoomout=b;return a};this.isScrollable=function(a){a=a.target?a.target:a;if("OPTION"==a.nodeName)return!0;for(;a&&1==a.nodeType&&!/^BODY|HTML/.test(a.nodeName);){var c=f(a),c=c.css("overflowY")||c.css("overflowX")||
+c.css("overflow")||"";if(/scroll|auto/.test(c))return a.clientHeight!=a.scrollHeight;a=a.parentNode?a.parentNode:!1}return!1};this.getViewport=function(a){for(a=a&&a.parentNode?a.parentNode:!1;a&&1==a.nodeType&&!/^BODY|HTML/.test(a.nodeName);){var c=f(a);if(/fixed|absolute/.test(c.css("position")))return c;var d=c.css("overflowY")||c.css("overflowX")||c.css("overflow")||"";if(/scroll|auto/.test(d)&&a.clientHeight!=a.scrollHeight||0<c.getNiceScroll().length)return c;a=a.parentNode?a.parentNode:!1}return!1};
+this.triggerScrollEnd=function(){if(a.onscrollend){var b=a.getScrollLeft(),c=a.getScrollTop();a.onscrollend.call(a,{type:"scrollend",current:{x:b,y:c},end:{x:b,y:c}})}};this.onmousewheel=function(b){if(!a.wheelprevented){if(a.railslocked)return a.debounced("checkunlock",a.resize,250),!0;if(a.rail.drag)return a.cancelEvent(b);"auto"==a.opt.oneaxismousemode&&0!=b.deltaX&&(a.opt.oneaxismousemode=!1);if(a.opt.oneaxismousemode&&0==b.deltaX&&!a.rail.scrollable)return a.railh&&a.railh.scrollable?a.onmousewheelhr(b):
+!0;var c=+new Date,d=!1;a.opt.preservenativescrolling&&a.checkarea+600<c&&(a.nativescrollingarea=a.isScrollable(b),d=!0);a.checkarea=c;if(a.nativescrollingarea)return!0;if(b=p(b,!1,d))a.checkarea=0;return b}};this.onmousewheelhr=function(b){if(!a.wheelprevented){if(a.railslocked||!a.railh.scrollable)return!0;if(a.rail.drag)return a.cancelEvent(b);var c=+new Date,d=!1;a.opt.preservenativescrolling&&a.checkarea+600<c&&(a.nativescrollingarea=a.isScrollable(b),d=!0);a.checkarea=c;return a.nativescrollingarea?
+!0:a.railslocked?a.cancelEvent(b):p(b,!0,d)}};this.stop=function(){a.cancelScroll();a.scrollmon&&a.scrollmon.stop();a.cursorfreezed=!1;a.scroll.y=Math.round(a.getScrollTop()*(1/a.scrollratio.y));a.noticeCursor();return a};this.getTransitionSpeed=function(b){var c=Math.round(10*a.opt.scrollspeed);b=Math.min(c,Math.round(b/20*a.opt.scrollspeed));return 20<b?b:0};a.opt.smoothscroll?a.ishwscroll&&e.hastransition&&a.opt.usetransition&&a.opt.smoothscroll?(this.prepareTransition=function(b,c){var d=c?20<
+b?b:0:a.getTransitionSpeed(b),f=d?e.prefixstyle+"transform "+d+"ms ease-out":"";a.lasttransitionstyle&&a.lasttransitionstyle==f||(a.lasttransitionstyle=f,a.doc.css(e.transitionstyle,f));return d},this.doScrollLeft=function(b,c){var d=a.scrollrunning?a.newscrolly:a.getScrollTop();a.doScrollPos(b,d,c)},this.doScrollTop=function(b,c){var d=a.scrollrunning?a.newscrollx:a.getScrollLeft();a.doScrollPos(d,b,c)},this.doScrollPos=function(b,c,d){var f=a.getScrollTop(),h=a.getScrollLeft();(0>(a.newscrolly-
+f)*(c-f)||0>(a.newscrollx-h)*(b-h))&&a.cancelScroll();0==a.opt.bouncescroll&&(0>c?c=0:c>a.page.maxh&&(c=a.page.maxh),0>b?b=0:b>a.page.maxw&&(b=a.page.maxw));if(a.scrollrunning&&b==a.newscrollx&&c==a.newscrolly)return!1;a.newscrolly=c;a.newscrollx=b;a.newscrollspeed=d||!1;if(a.timer)return!1;a.timer=setTimeout(function(){var d=a.getScrollTop(),f=a.getScrollLeft(),h,k;h=b-f;k=c-d;h=Math.round(Math.sqrt(Math.pow(h,2)+Math.pow(k,2)));h=a.newscrollspeed&&1<a.newscrollspeed?a.newscrollspeed:a.getTransitionSpeed(h);
+a.newscrollspeed&&1>=a.newscrollspeed&&(h*=a.newscrollspeed);a.prepareTransition(h,!0);a.timerscroll&&a.timerscroll.tm&&clearInterval(a.timerscroll.tm);0<h&&(!a.scrollrunning&&a.onscrollstart&&a.onscrollstart.call(a,{type:"scrollstart",current:{x:f,y:d},request:{x:b,y:c},end:{x:a.newscrollx,y:a.newscrolly},speed:h}),e.transitionend?a.scrollendtrapped||(a.scrollendtrapped=!0,a.bind(a.doc,e.transitionend,a.onScrollTransitionEnd,!1)):(a.scrollendtrapped&&clearTimeout(a.scrollendtrapped),a.scrollendtrapped=
+setTimeout(a.onScrollTransitionEnd,h)),a.timerscroll={bz:new A(d,a.newscrolly,h,0,0,.58,1),bh:new A(f,a.newscrollx,h,0,0,.58,1)},a.cursorfreezed||(a.timerscroll.tm=setInterval(function(){a.showCursor(a.getScrollTop(),a.getScrollLeft())},60)));a.synched("doScroll-set",function(){a.timer=0;a.scrollendtrapped&&(a.scrollrunning=!0);a.setScrollTop(a.newscrolly);a.setScrollLeft(a.newscrollx);if(!a.scrollendtrapped)a.onScrollTransitionEnd()})},50)},this.cancelScroll=function(){if(!a.scrollendtrapped)return!0;
+var b=a.getScrollTop(),c=a.getScrollLeft();a.scrollrunning=!1;e.transitionend||clearTimeout(e.transitionend);a.scrollendtrapped=!1;a._unbind(a.doc[0],e.transitionend,a.onScrollTransitionEnd);a.prepareTransition(0);a.setScrollTop(b);a.railh&&a.setScrollLeft(c);a.timerscroll&&a.timerscroll.tm&&clearInterval(a.timerscroll.tm);a.timerscroll=!1;a.cursorfreezed=!1;a.showCursor(b,c);return a},this.onScrollTransitionEnd=function(){a.scrollendtrapped&&a._unbind(a.doc[0],e.transitionend,a.onScrollTransitionEnd);
+a.scrollendtrapped=!1;a.prepareTransition(0);a.timerscroll&&a.timerscroll.tm&&clearInterval(a.timerscroll.tm);a.timerscroll=!1;var b=a.getScrollTop(),c=a.getScrollLeft();a.setScrollTop(b);a.railh&&a.setScrollLeft(c);a.noticeCursor(!1,b,c);a.cursorfreezed=!1;0>b?b=0:b>a.page.maxh&&(b=a.page.maxh);0>c?c=0:c>a.page.maxw&&(c=a.page.maxw);if(b!=a.newscrolly||c!=a.newscrollx)return a.doScrollPos(c,b,a.opt.snapbackspeed);a.onscrollend&&a.scrollrunning&&a.triggerScrollEnd();a.scrollrunning=!1}):(this.doScrollLeft=
+function(b,c){var d=a.scrollrunning?a.newscrolly:a.getScrollTop();a.doScrollPos(b,d,c)},this.doScrollTop=function(b,c){var d=a.scrollrunning?a.newscrollx:a.getScrollLeft();a.doScrollPos(d,b,c)},this.doScrollPos=function(b,c,d){function e(){if(a.cancelAnimationFrame)return!0;a.scrollrunning=!0;if(n=1-n)return a.timer=s(e)||1;var b=0,c,d,g=d=a.getScrollTop();if(a.dst.ay){g=a.bzscroll?a.dst.py+a.bzscroll.getNow()*a.dst.ay:a.newscrolly;c=g-d;if(0>c&&g<a.newscrolly||0<c&&g>a.newscrolly)g=a.newscrolly;
+a.setScrollTop(g);g==a.newscrolly&&(b=1)}else b=1;d=c=a.getScrollLeft();if(a.dst.ax){d=a.bzscroll?a.dst.px+a.bzscroll.getNow()*a.dst.ax:a.newscrollx;c=d-c;if(0>c&&d<a.newscrollx||0<c&&d>a.newscrollx)d=a.newscrollx;a.setScrollLeft(d);d==a.newscrollx&&(b+=1)}else b+=1;2==b?(a.timer=0,a.cursorfreezed=!1,a.bzscroll=!1,a.scrollrunning=!1,0>g?g=0:g>a.page.maxh&&(g=a.page.maxh),0>d?d=0:d>a.page.maxw&&(d=a.page.maxw),d!=a.newscrollx||g!=a.newscrolly?a.doScrollPos(d,g):a.onscrollend&&a.triggerScrollEnd()):
+a.timer=s(e)||1}c="undefined"==typeof c||!1===c?a.getScrollTop(!0):c;if(a.timer&&a.newscrolly==c&&a.newscrollx==b)return!0;a.timer&&t(a.timer);a.timer=0;var f=a.getScrollTop(),h=a.getScrollLeft();(0>(a.newscrolly-f)*(c-f)||0>(a.newscrollx-h)*(b-h))&&a.cancelScroll();a.newscrolly=c;a.newscrollx=b;a.bouncescroll&&a.rail.visibility||(0>a.newscrolly?a.newscrolly=0:a.newscrolly>a.page.maxh&&(a.newscrolly=a.page.maxh));a.bouncescroll&&a.railh.visibility||(0>a.newscrollx?a.newscrollx=0:a.newscrollx>a.page.maxw&&
+(a.newscrollx=a.page.maxw));a.dst={};a.dst.x=b-h;a.dst.y=c-f;a.dst.px=h;a.dst.py=f;var k=Math.round(Math.sqrt(Math.pow(a.dst.x,2)+Math.pow(a.dst.y,2)));a.dst.ax=a.dst.x/k;a.dst.ay=a.dst.y/k;var l=0,m=k;0==a.dst.x?(l=f,m=c,a.dst.ay=1,a.dst.py=0):0==a.dst.y&&(l=h,m=b,a.dst.ax=1,a.dst.px=0);k=a.getTransitionSpeed(k);d&&1>=d&&(k*=d);a.bzscroll=0<k?a.bzscroll?a.bzscroll.update(m,k):new A(l,m,k,0,1,0,1):!1;if(!a.timer){(f==a.page.maxh&&c>=a.page.maxh||h==a.page.maxw&&b>=a.page.maxw)&&a.checkContentSize();
+var n=1;a.cancelAnimationFrame=!1;a.timer=1;a.onscrollstart&&!a.scrollrunning&&a.onscrollstart.call(a,{type:"scrollstart",current:{x:h,y:f},request:{x:b,y:c},end:{x:a.newscrollx,y:a.newscrolly},speed:k});e();(f==a.page.maxh&&c>=f||h==a.page.maxw&&b>=h)&&a.checkContentSize();a.noticeCursor()}},this.cancelScroll=function(){a.timer&&t(a.timer);a.timer=0;a.bzscroll=!1;a.scrollrunning=!1;return a}):(this.doScrollLeft=function(b,c){var d=a.getScrollTop();a.doScrollPos(b,d,c)},this.doScrollTop=function(b,
+c){var d=a.getScrollLeft();a.doScrollPos(d,b,c)},this.doScrollPos=function(b,c,d){var e=b>a.page.maxw?a.page.maxw:b;0>e&&(e=0);var f=c>a.page.maxh?a.page.maxh:c;0>f&&(f=0);a.synched("scroll",function(){a.setScrollTop(f);a.setScrollLeft(e)})},this.cancelScroll=function(){});this.doScrollBy=function(b,c){var d=0,d=c?Math.floor((a.scroll.y-b)*a.scrollratio.y):(a.timer?a.newscrolly:a.getScrollTop(!0))-b;if(a.bouncescroll){var e=Math.round(a.view.h/2);d<-e?d=-e:d>a.page.maxh+e&&(d=a.page.maxh+e)}a.cursorfreezed=
+!1;e=a.getScrollTop(!0);if(0>d&&0>=e)return a.noticeCursor();if(d>a.page.maxh&&e>=a.page.maxh)return a.checkContentSize(),a.noticeCursor();a.doScrollTop(d)};this.doScrollLeftBy=function(b,c){var d=0,d=c?Math.floor((a.scroll.x-b)*a.scrollratio.x):(a.timer?a.newscrollx:a.getScrollLeft(!0))-b;if(a.bouncescroll){var e=Math.round(a.view.w/2);d<-e?d=-e:d>a.page.maxw+e&&(d=a.page.maxw+e)}a.cursorfreezed=!1;e=a.getScrollLeft(!0);if(0>d&&0>=e||d>a.page.maxw&&e>=a.page.maxw)return a.noticeCursor();a.doScrollLeft(d)};
+this.doScrollTo=function(b,c){c&&Math.round(b*a.scrollratio.y);a.cursorfreezed=!1;a.doScrollTop(b)};this.checkContentSize=function(){var b=a.getContentSize();b.h==a.page.h&&b.w==a.page.w||a.resize(!1,b)};a.onscroll=function(b){a.rail.drag||a.cursorfreezed||a.synched("scroll",function(){a.scroll.y=Math.round(a.getScrollTop()*(1/a.scrollratio.y));a.railh&&(a.scroll.x=Math.round(a.getScrollLeft()*(1/a.scrollratio.x)));a.noticeCursor()})};a.bind(a.docscroll,"scroll",a.onscroll);this.doZoomIn=function(b){if(!a.zoomactive){a.zoomactive=
+!0;a.zoomrestore={style:{}};var c="position top left zIndex backgroundColor marginTop marginBottom marginLeft marginRight".split(" "),d=a.win[0].style,h;for(h in c){var k=c[h];a.zoomrestore.style[k]="undefined"!=typeof d[k]?d[k]:""}a.zoomrestore.style.width=a.win.css("width");a.zoomrestore.style.height=a.win.css("height");a.zoomrestore.padding={w:a.win.outerWidth()-a.win.width(),h:a.win.outerHeight()-a.win.height()};e.isios4&&(a.zoomrestore.scrollTop=f(window).scrollTop(),f(window).scrollTop(0));
+a.win.css({position:e.isios4?"absolute":"fixed",top:0,left:0,"z-index":x+100,margin:"0px"});c=a.win.css("backgroundColor");(""==c||/transparent|rgba\(0, 0, 0, 0\)|rgba\(0,0,0,0\)/.test(c))&&a.win.css("backgroundColor","#fff");a.rail.css({"z-index":x+101});a.zoom.css({"z-index":x+102});a.zoom.css("backgroundPosition","0px -18px");a.resizeZoom();a.onzoomin&&a.onzoomin.call(a);return a.cancelEvent(b)}};this.doZoomOut=function(b){if(a.zoomactive)return a.zoomactive=!1,a.win.css("margin",""),a.win.css(a.zoomrestore.style),
+e.isios4&&f(window).scrollTop(a.zoomrestore.scrollTop),a.rail.css({"z-index":a.zindex}),a.zoom.css({"z-index":a.zindex}),a.zoomrestore=!1,a.zoom.css("backgroundPosition","0px 0px"),a.onResize(),a.onzoomout&&a.onzoomout.call(a),a.cancelEvent(b)};this.doZoom=function(b){return a.zoomactive?a.doZoomOut(b):a.doZoomIn(b)};this.resizeZoom=function(){if(a.zoomactive){var b=a.getScrollTop();a.win.css({width:f(window).width()-a.zoomrestore.padding.w+"px",height:f(window).height()-a.zoomrestore.padding.h+"px"});
+a.onResize();a.setScrollTop(Math.min(a.page.maxh,b))}};this.init();f.nicescroll.push(this)},L=function(f){var c=this;this.nc=f;this.steptime=this.lasttime=this.speedy=this.speedx=this.lasty=this.lastx=0;this.snapy=this.snapx=!1;this.demuly=this.demulx=0;this.lastscrolly=this.lastscrollx=-1;this.timer=this.chky=this.chkx=0;this.time=function(){return+new Date};this.reset=function(f,k){c.stop();var d=c.time();c.steptime=0;c.lasttime=d;c.speedx=0;c.speedy=0;c.lastx=f;c.lasty=k;c.lastscrollx=-1;c.lastscrolly=
+-1};this.update=function(f,k){var d=c.time();c.steptime=d-c.lasttime;c.lasttime=d;var d=k-c.lasty,n=f-c.lastx,p=c.nc.getScrollTop(),a=c.nc.getScrollLeft(),p=p+d,a=a+n;c.snapx=0>a||a>c.nc.page.maxw;c.snapy=0>p||p>c.nc.page.maxh;c.speedx=n;c.speedy=d;c.lastx=f;c.lasty=k};this.stop=function(){c.nc.unsynched("domomentum2d");c.timer&&clearTimeout(c.timer);c.timer=0;c.lastscrollx=-1;c.lastscrolly=-1};this.doSnapy=function(f,k){var d=!1;0>k?(k=0,d=!0):k>c.nc.page.maxh&&(k=c.nc.page.maxh,d=!0);0>f?(f=0,d=
+!0):f>c.nc.page.maxw&&(f=c.nc.page.maxw,d=!0);d?c.nc.doScrollPos(f,k,c.nc.opt.snapbackspeed):c.nc.triggerScrollEnd()};this.doMomentum=function(f){var k=c.time(),d=f?k+f:c.lasttime;f=c.nc.getScrollLeft();var n=c.nc.getScrollTop(),p=c.nc.page.maxh,a=c.nc.page.maxw;c.speedx=0<a?Math.min(60,c.speedx):0;c.speedy=0<p?Math.min(60,c.speedy):0;d=d&&60>=k-d;if(0>n||n>p||0>f||f>a)d=!1;f=c.speedx&&d?c.speedx:!1;if(c.speedy&&d&&c.speedy||f){var s=Math.max(16,c.steptime);50<s&&(f=s/50,c.speedx*=f,c.speedy*=f,s=
+50);c.demulxy=0;c.lastscrollx=c.nc.getScrollLeft();c.chkx=c.lastscrollx;c.lastscrolly=c.nc.getScrollTop();c.chky=c.lastscrolly;var e=c.lastscrollx,r=c.lastscrolly,t=function(){var d=600<c.time()-k?.04:.02;c.speedx&&(e=Math.floor(c.lastscrollx-c.speedx*(1-c.demulxy)),c.lastscrollx=e,0>e||e>a)&&(d=.1);c.speedy&&(r=Math.floor(c.lastscrolly-c.speedy*(1-c.demulxy)),c.lastscrolly=r,0>r||r>p)&&(d=.1);c.demulxy=Math.min(1,c.demulxy+d);c.nc.synched("domomentum2d",function(){c.speedx&&(c.nc.getScrollLeft()!=
+c.chkx&&c.stop(),c.chkx=e,c.nc.setScrollLeft(e));c.speedy&&(c.nc.getScrollTop()!=c.chky&&c.stop(),c.chky=r,c.nc.setScrollTop(r));c.timer||(c.nc.hideCursor(),c.doSnapy(e,r))});1>c.demulxy?c.timer=setTimeout(t,s):(c.stop(),c.nc.hideCursor(),c.doSnapy(e,r))};t()}else c.doSnapy(c.nc.getScrollLeft(),c.nc.getScrollTop())}},w=f.fn.scrollTop;f.cssHooks.pageYOffset={get:function(k,c,h){return(c=f.data(k,"__nicescroll")||!1)&&c.ishwscroll?c.getScrollTop():w.call(k)},set:function(k,c){var h=f.data(k,"__nicescroll")||
+!1;h&&h.ishwscroll?h.setScrollTop(parseInt(c)):w.call(k,c);return this}};f.fn.scrollTop=function(k){if("undefined"==typeof k){var c=this[0]?f.data(this[0],"__nicescroll")||!1:!1;return c&&c.ishwscroll?c.getScrollTop():w.call(this)}return this.each(function(){var c=f.data(this,"__nicescroll")||!1;c&&c.ishwscroll?c.setScrollTop(parseInt(k)):w.call(f(this),k)})};var B=f.fn.scrollLeft;f.cssHooks.pageXOffset={get:function(k,c,h){return(c=f.data(k,"__nicescroll")||!1)&&c.ishwscroll?c.getScrollLeft():B.call(k)},
+set:function(k,c){var h=f.data(k,"__nicescroll")||!1;h&&h.ishwscroll?h.setScrollLeft(parseInt(c)):B.call(k,c);return this}};f.fn.scrollLeft=function(k){if("undefined"==typeof k){var c=this[0]?f.data(this[0],"__nicescroll")||!1:!1;return c&&c.ishwscroll?c.getScrollLeft():B.call(this)}return this.each(function(){var c=f.data(this,"__nicescroll")||!1;c&&c.ishwscroll?c.setScrollLeft(parseInt(k)):B.call(f(this),k)})};var C=function(k){var c=this;this.length=0;this.name="nicescrollarray";this.each=function(d){for(var f=
+0,h=0;f<c.length;f++)d.call(c[f],h++);return c};this.push=function(d){c[c.length]=d;c.length++};this.eq=function(d){return c[d]};if(k)for(var h=0;h<k.length;h++){var m=f.data(k[h],"__nicescroll")||!1;m&&(this[this.length]=m,this.length++)}return this};(function(f,c,h){for(var m=0;m<c.length;m++)h(f,c[m])})(C.prototype,"show hide toggle onResize resize remove stop doScrollPos".split(" "),function(f,c){f[c]=function(){var f=arguments;return this.each(function(){this[c].apply(this,f)})}});f.fn.getNiceScroll=
+function(k){return"undefined"==typeof k?new C(this):this[k]&&f.data(this[k],"__nicescroll")||!1};f.extend(f.expr[":"],{nicescroll:function(k){return f.data(k,"__nicescroll")?!0:!1}});f.fn.niceScroll=function(k,c){"undefined"!=typeof c||"object"!=typeof k||"jquery"in k||(c=k,k=!1);c=f.extend({},c);var h=new C;"undefined"==typeof c&&(c={});k&&(c.doc=f(k),c.win=f(this));var m=!("doc"in c);m||"win"in c||(c.win=f(this));this.each(function(){var d=f(this).data("__nicescroll")||!1;d||(c.doc=m?f(this):c.doc,
+d=new R(c,f(this)),f(this).data("__nicescroll",d));h.push(d)});return 1==h.length?h[0]:h};window.NiceScroll={getjQuery:function(){return f}};f.nicescroll||(f.nicescroll=new C,f.nicescroll.options=I)});
diff --git a/vendor/assets/javascripts/jquery.sticky-kit.min.js b/vendor/assets/javascripts/jquery.sticky-kit.min.js
deleted file mode 100644
index e8bb207c5a5..00000000000
--- a/vendor/assets/javascripts/jquery.sticky-kit.min.js
+++ /dev/null
@@ -1,9 +0,0 @@
-/*
- Sticky-kit v1.1.1 | WTFPL | Leaf Corcoran 2014 | http://leafo.net
-*/
-(function(){var k,e;k=this.jQuery||window.jQuery;e=k(window);k.fn.stick_in_parent=function(d){var v,y,n,p,h,C,s,G,q,H;null==d&&(d={});s=d.sticky_class;y=d.inner_scrolling;C=d.recalc_every;h=d.parent;p=d.offset_top;n=d.spacer;v=d.bottoming;null==p&&(p=0);null==h&&(h=void 0);null==y&&(y=!0);null==s&&(s="is_stuck");null==v&&(v=!0);G=function(a,d,q,z,D,t,r,E){var u,F,m,A,c,f,B,w,x,g,b;if(!a.data("sticky_kit")){a.data("sticky_kit",!0);f=a.parent();null!=h&&(f=f.closest(h));if(!f.length)throw"failed to find stick parent";
-u=m=!1;(g=null!=n?n&&a.closest(n):k("<div />"))&&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"))),l<D&&(m=!1,c=p,null==n&&("left"!==r&&"right"!==r||a.insertAfter(g),g.detach()),b={position:"",width:"",top:""},a.css(b).removeClass(s).trigger("sticky_kit:unstick")),
-y&&(b=e.height(),t+p>b&&!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<H;q++)d=this[q],G(k(d));return this}}).call(this);